Skip to content

Instantly share code, notes, and snippets.

@globulus
Created December 11, 2021 13:05
Show Gist options
  • Select an option

  • Save globulus/5ac8aacfb68c3ee3007ac98a7dbf63f8 to your computer and use it in GitHub Desktop.

Select an option

Save globulus/5ac8aacfb68c3ee3007ac98a7dbf63f8 to your computer and use it in GitHub Desktop.

Revisions

  1. globulus created this gist Dec 11, 2021.
    158 changes: 158 additions & 0 deletions Weighted.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    // 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)
    }
    }