Skip to content

Instantly share code, notes, and snippets.

@swiftui-lab
Created August 31, 2022 08:20
Show Gist options
  • Save swiftui-lab/a695ceb5977e32346df095fd16cf3f55 to your computer and use it in GitHub Desktop.
Save swiftui-lab/a695ceb5977e32346df095fd16cf3f55 to your computer and use it in GitHub Desktop.
An example of a composed layout
// Author: SwiftUI-Lab (swiftui-lab.com)
// Description: A demonstration of a composed Layout
// blog article: https://swiftui-lab.com/layout-protocol-part-2
import SwiftUI
struct ContentView: View {
let colors: [Color] = [.yellow, .orange, .red, .pink, .purple, .blue, .cyan, .green]
@State var viewCount: Double = 9
var body: some View {
let indices = Array<Int>(repeating: 0, count: Int(viewCount)).indices
VStack {
Spacer()
ComposedLayout {
ForEach(indices, id: \.self) { idx in
RoundedRectangle(cornerRadius: 8)
.fill(colors[idx%colors.count].opacity(0.8))
.frame(height: 50)
.overlay { Text("\(idx+1)") }
.padding(3)
}
}
.border(.foreground)
Spacer()
VStack {
Slider(value: $viewCount, in: 0...12, step: 1)
Text("View count: \(Int(viewCount))")
}
.padding(.vertical, 20)
}
.padding()
}
}
struct ComposedLayout: Layout {
private let hStack = AnyLayout(HStackLayout(spacing: 0))
private let vStack = AnyLayout(VStackLayout(spacing: 0))
struct Caches {
var topCache: AnyLayout.Cache
var centerCache: AnyLayout.Cache
var bottomCache: AnyLayout.Cache
}
func makeCache(subviews: Subviews) -> Caches {
Caches(topCache: hStack.makeCache(subviews: topViews(subviews: subviews)),
centerCache: vStack.makeCache(subviews: centerViews(subviews: subviews)),
bottomCache: hStack.makeCache(subviews: bottomViews(subviews: subviews)))
}
func topViews(subviews: LayoutSubviews) -> LayoutSubviews {
return subviews[..<min(subviews.count, 3)]
}
func centerViews(subviews: LayoutSubviews) -> LayoutSubviews {
return subviews.dropFirst(3).dropLast(3)
}
func bottomViews(subviews: LayoutSubviews) -> LayoutSubviews {
if subviews.count < 4 {
return subviews.dropLast(subviews.count) // return empty LayoutSubviews
} else {
return subviews[max(subviews.count - 3, 3)..<subviews.count]
}
}
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Caches) -> CGSize {
let tViews = topViews(subviews: subviews)
let cViews = centerViews(subviews: subviews)
let bViews = bottomViews(subviews: subviews)
let tSize = tViews.count == 0 ? .zero : hStack.sizeThatFits(proposal: proposal, subviews: tViews, cache: &cache.topCache)
let cSize = cViews.count == 0 ? .zero : vStack.sizeThatFits(proposal: proposal, subviews: cViews, cache: &cache.centerCache)
let bSize = bViews.count == 0 ? .zero : hStack.sizeThatFits(proposal: proposal, subviews: bViews, cache: &cache.bottomCache)
return CGSize(width: max(tSize.width, max(cSize.width, bSize.width)),
height: tSize.height + cSize.height + bSize.height)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Caches) {
let tViews = topViews(subviews: subviews)
let cViews = centerViews(subviews: subviews)
let bViews = bottomViews(subviews: subviews)
var bounds = bounds
if tViews.count > 0 {
let tSize = hStack.sizeThatFits(proposal: proposal, subviews: tViews, cache: &cache.topCache)
hStack.placeSubviews(in: bounds, proposal: proposal, subviews: tViews, cache: &cache.topCache)
bounds.origin = CGPoint(x: bounds.origin.x, y: bounds.origin.y + tSize.height)
}
if cViews.count > 0 {
let cSize = vStack.sizeThatFits(proposal: proposal, subviews: cViews, cache: &cache.centerCache)
vStack.placeSubviews(in: bounds, proposal: proposal, subviews: cViews, cache: &cache.centerCache)
bounds.origin = CGPoint(x: bounds.origin.x, y: bounds.origin.y + cSize.height)
}
if bViews.count > 0 {
hStack.placeSubviews(in: bounds, proposal: proposal, subviews: bViews, cache: &cache.bottomCache)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment