Skip to content

Instantly share code, notes, and snippets.

@TAATHub
Last active April 7, 2025 13:29
Show Gist options
  • Select an option

  • Save TAATHub/a4eda1ab82725802d7265b99aaae894d to your computer and use it in GitHub Desktop.

Select an option

Save TAATHub/a4eda1ab82725802d7265b99aaae894d to your computer and use it in GitHub Desktop.
// See also: Pro SwiftUI, Chapter 4 Custom Layouts
import SwiftUI
struct RelativeHStack: Layout {
enum Alignment {
case top, center, bottom
}
var alignment: Alignment = .center
var spacing = 0.0
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let width = proposal.replacingUnspecifiedDimensions().width
let viewFrames = frames(for: subviews, in: width)
let height = viewFrames.max { $0.maxY < $1.maxY } ?? .zero
return CGSize(width: width, height: height.maxY)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let viewFrames = frames(for: subviews, in: bounds.width)
for index in subviews.indices {
let frame = viewFrames[index]
let y = switch alignment {
case .top: bounds.minY
case .center: bounds.midY
case .bottom: bounds.maxY
}
let position = CGPoint(x: bounds.minX + frame.minX, y: y)
let anchor: UnitPoint = switch alignment {
case .top: .topLeading
case .center: .leading
case .bottom: .bottomLeading
}
subviews[index].place(at: position, anchor: anchor, proposal: .init(frame.size))
}
}
private func frames(for subviews: Subviews, in totalWidth: Double) -> [CGRect] {
let totalSpacing = spacing * Double(subviews.count - 1)
let availableWidth = totalWidth - totalSpacing
let totalPriorities = subviews.reduce(0) { $0 + $1.priority }
var viewFrames = [CGRect]()
var x = 0.0
for subview in subviews {
let subviewWidth = availableWidth * subview.priority / totalPriorities
let proposal = ProposedViewSize(width: subviewWidth, height: nil)
let size = subview.sizeThatFits(proposal)
let frame = CGRect(x: x, y: 0, width: size.width, height: size.height)
viewFrames.append(frame)
x += size.width + spacing
}
return viewFrames
}
}
struct ContentView: View {
var body: some View {
NavigationStack {
VStack(spacing: 40) {
VStack(alignment: .leading, spacing: 4) {
Text("alignment: .top")
relativeHStackView(alignment: .top)
.border(.black)
}
VStack(alignment: .leading, spacing: 4) {
Text("alignment: .center")
relativeHStackView(alignment: .center)
.border(.black)
}
VStack(alignment: .leading, spacing: 4) {
Text("alignment: .bottom")
relativeHStackView(alignment: .bottom)
.border(.black)
}
}
.padding()
.navigationTitle("Relative Width Layout")
}
}
private func relativeHStackView(alignment: RelativeHStack.Alignment) -> some View {
RelativeHStack(alignment: alignment, spacing: 40) {
Group {
Text("1")
.frame(maxWidth: .infinity)
.frame(height: 40)
.background(.red)
.layoutPriority(1)
Text("2")
.frame(maxWidth: .infinity)
.frame(height: 60)
.background(.green)
.layoutPriority(2)
Text("3")
.frame(maxWidth: .infinity)
.frame(height: 80)
.background(.blue)
.layoutPriority(3)
}
.font(.title3)
.foregroundStyle(.white)
}
}
}
#Preview {
ContentView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment