Created
December 11, 2021 13:05
-
-
Save globulus/5ac8aacfb68c3ee3007ac98a7dbf63f8 to your computer and use it in GitHub Desktop.
Weighted HStack and VStack in SwiftUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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