Skip to content

Instantly share code, notes, and snippets.

@globulus
Created December 11, 2021 13:05
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save globulus/5ac8aacfb68c3ee3007ac98a7dbf63f8 to your computer and use it in GitHub Desktop.
Save globulus/5ac8aacfb68c3ee3007ac98a7dbf63f8 to your computer and use it in GitHub Desktop.
Weighted HStack and VStack in SwiftUI
// Full recipe at https://swiftuirecipes.com/blog/weighted-layout-hstack-and-vstack-in-swiftui
import SwiftUI
class WeightedProxy {
let kind: Kind
var geo: GeometryProxy? = nil
private(set) var totalWeight: CGFloat = 0
init(kind: Kind) {
self.kind = kind
}
func register(with weight: CGFloat) {
totalWeight += weight
}
func dimensionForRelative(weight: CGFloat) -> CGFloat {
guard let geo = geo,
totalWeight > 0
else {
return 0
}
let dimension = (kind == .vertical) ? geo.size.height : geo.size.width
return dimension * weight / totalWeight
}
enum Kind {
case vertical, horizontal
}
}
struct Weighted: ViewModifier {
private let weight: CGFloat
private let proxy: WeightedProxy
init(_ weight: CGFloat, proxy: WeightedProxy) {
self.weight = weight
self.proxy = proxy
proxy.register(with: weight)
}
@ViewBuilder func body(content: Content) -> some View {
if proxy.kind == .vertical {
content.frame(height: proxy.dimensionForRelative(weight: weight))
} else {
content.frame(width: proxy.dimensionForRelative(weight: weight))
}
}
}
extension View {
func weighted(_ weight: CGFloat, proxy: WeightedProxy) -> some View {
self.modifier(Weighted(weight, proxy: proxy))
}
}
struct WeightedHStack<Content>: View where Content : View {
private let proxy = WeightedProxy(kind: .horizontal)
@State private var initialized = false
@ViewBuilder let content: (WeightedProxy) -> Content
var body: some View {
GeometryReader { geo in
HStack(spacing: 0) {
if initialized {
content(proxy)
} else {
Color.clear.onAppear {
proxy.geo = geo
initialized.toggle()
}
}
}
}
}
}
struct WeightedVStack<Content>: View where Content : View {
private let proxy = WeightedProxy(kind: .vertical)
@State private var initialized = false
@ViewBuilder let content: (WeightedProxy) -> Content
var body: some View {
GeometryReader { geo in
VStack(spacing: 0) {
if initialized {
content(proxy)
} else {
Color.clear.onAppear {
proxy.geo = geo
initialized.toggle()
}
}
}
}
}
}
struct WeightsTest: View {
var body: some View {
// VStack {
// WeightedHStack { proxy in
// Text("50%")
// .weighted(5, proxy: proxy)
// .background(Color.blue)
// Text("20%")
// .weighted(2, proxy: proxy)
// .background(Color.green)
// Text("30%")
// .weighted(3, proxy: proxy)
// .background(Color.red)
// }
// WeightedHStack { proxy in
// Text("15%")
// .weighted(0.15, proxy: proxy)
// .background(Color.brown)
// Text("15%")
// .weighted(0.15, proxy: proxy)
// .background(Color.cyan)
// Text("55%")
// .weighted(0.55, proxy: proxy)
// .background(Color.black)
// Text("15%")
// .weighted(0.15, proxy: proxy)
// .background(Color.pink)
// }
// WeightedHStack { proxy in
// Text("30%")
// .weighted(3, proxy: proxy)
// .background(Color.purple)
// Text("40%")
// .weighted(4, proxy: proxy)
// .background(Color.yellow)
// Text("30%")
// .weighted(3, proxy: proxy)
// .background(Color.indigo)
// }
// Spacer()
// }
WeightedVStack { proxy in
Text("20%")
.frame(minWidth: 0, maxWidth: .infinity)
.weighted(2, proxy: proxy)
.background(Color.green)
Text("50%")
.frame(minWidth: 0, maxWidth: .infinity)
.weighted(5, proxy: proxy)
.background(Color.red)
Text("30%")
.frame(minWidth: 0, maxWidth: .infinity)
.weighted(3, proxy: proxy)
.background(Color.cyan)
}
.padding()
.foregroundColor(.white)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment