Skip to content

Instantly share code, notes, and snippets.

@CastIrony
Created June 9, 2022 05:33
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CastIrony/32ccb3ba142cb2e4a73ef7d99e9c64ec to your computer and use it in GitHub Desktop.
Save CastIrony/32ccb3ba142cb2e4a73ef7d99e9c64ec to your computer and use it in GitHub Desktop.
import SwiftUI
struct NewStopView: View {
let stop: Model.Stop
var body: some View {
Text("New Layout").font(.title)
RouteLayout(spacing: 8) {
ForEach(stop.routes ?? []) { route in
RouteView(route: route)
}
}
.padding(30)
}
}
struct RouteLayout: Layout {
let spacing: CGFloat
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
print("Proposed view size: [\(String(describing: proposal.width)), \(String(describing: proposal.height))]")
let bounds = CGRect(
origin: .zero,
size: proposal.replacingUnspecifiedDimensions(by: CGSize(width: 100, height: 100))
)
let (_, height) = buildLayout(in: bounds, subviews: subviews)
return CGSize(width: proposal.width ?? 100, height: height)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let (positions, _) = buildLayout(in: bounds, subviews: subviews)
for index in subviews.indices {
if let position = positions[index] {
subviews[index].place(at: position, proposal: .unspecified)
}
}
}
func buildLayout(in bounds: CGRect, subviews: Subviews) -> ([Subviews.Index : CGPoint], CGFloat) {
var cursorPosition = CGPoint(x: bounds.minX, y: bounds.minY)
var routeLabelPositions: [Subviews.Index : CGPoint] = [:]
var rowHeight: CGFloat = 0
for index in subviews.indices {
let labelSize = subviews[index].dimensions(in: .infinity) //routeLabelSizes[route.id] else { continue }
rowHeight = max(rowHeight, labelSize.height)
if cursorPosition.x + labelSize.width > bounds.width {
cursorPosition = CGPoint(x: bounds.minX, y: cursorPosition.y + rowHeight + spacing)
rowHeight = labelSize.height
}
routeLabelPositions[index] = cursorPosition
cursorPosition.x += labelSize.width + spacing
}
return (routeLabelPositions, cursorPosition.y + rowHeight)
}
}
struct NewStopView_Previews: PreviewProvider {
static var previews: some View {
NewStopView(stop: .dummy)
}
}
import SwiftUI
struct OldStopView: View {
let stop: Model.Stop
@State var routeLabelPositions: [Int : CGPoint] = [:]
@State var routeStackHeight: CGFloat = 10
var body: some View {
Text("Old Layout").font(.title)
GeometryReader { geometryProxy in
ZStack(alignment: .topLeading) {
ForEach(stop.routes ?? []) { route in
RouteView(route: route)
.measureSize(for: route.id)
.offset(x: routeLabelPositions[route.id]?.x ?? 0, y: routeLabelPositions[route.id]?.y ?? 0)
}
}
.onPreferenceChange(MeasureSizePreferenceKey.self) { routeLabelSizes in
self.layoutRouteLabels(sizes: routeLabelSizes, width: geometryProxy.size.width)
}
}
.frame(height: routeStackHeight)
.padding(30)
}
func layoutRouteLabels(sizes routeLabelSizes: [Int : CGSize], width: CGFloat, spacing: CGFloat = 8) {
var cursorPosition = CGPoint.zero
var routeLabelPositions: [Int : CGPoint] = [:]
var rowHeight: CGFloat = 0
for route in stop.routes ?? [] {
guard let labelSize = routeLabelSizes[route.id] else { continue }
rowHeight = max(rowHeight, labelSize.height)
if cursorPosition.x + labelSize.width > width {
cursorPosition = CGPoint(x: 0, y: cursorPosition.y + rowHeight + spacing)
rowHeight = labelSize.height
}
routeLabelPositions[route.id] = cursorPosition
cursorPosition.x += labelSize.width + spacing
}
self.routeLabelPositions = routeLabelPositions
self.routeStackHeight = cursorPosition.y + rowHeight
}
}
struct OldStopView_Previews: PreviewProvider {
static var previews: some View {
OldStopView(stop: .dummy)
}
}
// MARK: - Dictionary Preference Key
protocol DictionaryPreferenceKey: PreferenceKey {
associatedtype K: Hashable
associatedtype V
}
extension DictionaryPreferenceKey {
static var defaultValue: [K : V] { [:] }
static func reduce(value: inout [K : V], nextValue: () -> [K : V]) {
value = value.merging(nextValue(), uniquingKeysWith: { (_, last) in last })
}
}
// MARK: - Measure Size Preference
extension View {
func measureSize<K: Hashable>(for key: K) -> some View {
background(
GeometryReader {
Color.clear
.preference(
key: MeasureSizePreferenceKey.self,
value: [key: $0.size]
)
}
)
}
}
struct MeasureSizePreferenceKey<K: Hashable, V>: DictionaryPreferenceKey { }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment