Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Created October 24, 2022 20:10
Show Gist options
  • Save IanKeen/80b7b2911e110ce30e462b3d0b03e0ca to your computer and use it in GitHub Desktop.
Save IanKeen/80b7b2911e110ce30e462b3d0b03e0ca to your computer and use it in GitHub Desktop.
SwiftUI: Read the default spacing for use elsewhere
// Can also use like `ReadSpacing { spacing in ... }` if a state/binding isn't needed
struct Example: View {
@State private var spacing: CGFloat = 0
var body: some View {
VStack {
ReadSpacing(into: $spacing) {
Color.red.frame(width: 200, height: 100)
Color.red.frame(width: 200, height: 100)
}
}
.overlay(alignment: .bottom) {
Color.red.frame(width: 200, height: 100)
.padding(.top, spacing)
.alignmentGuide(VerticalAlignment.bottom) { $0[.top] }
}
}
}
struct ReadSpacing<Content: View>: View {
@State private var spacing: CGFloat = 0
@Binding private var outsideSpacing: CGFloat?
private let content: (CGFloat) -> Content
init(@ViewBuilder content: @escaping (CGFloat) -> Content) {
self._outsideSpacing = .constant(nil)
self.content = content
}
init(into spacing: Binding<CGFloat>, @ViewBuilder content: @escaping () -> Content) {
self._outsideSpacing = .init(spacing)
self.content = { _ in content() }
}
private var activeSpacing: Binding<CGFloat> {
.init(
get: { outsideSpacing ?? spacing },
set: { value in
outsideSpacing = value
spacing = value
}
)
}
var body: some View {
_VariadicView.Tree(ReadSpacingLayout(spacing: activeSpacing)) {
content(spacing)
}
}
}
private struct FrameKey: PreferenceKey {
static var defaultValue: [CGRect] = []
static func reduce(value: inout Value, nextValue: () -> Value) {
value.append(contentsOf: nextValue())
}
}
private struct ReadSpacingLayout: _VariadicView_MultiViewRoot {
private struct Container: _VariadicView_UnaryViewRoot {
func body(children: _VariadicView.Children) -> some View { children }
}
@Binding var spacing: CGFloat
@ViewBuilder
func body(children: _VariadicView.Children) -> some View {
_VariadicView.Tree(Container()) {
ForEach(children) { child in
child
.overlay {
GeometryReader { proxy in
Color.clear
.preference(key: FrameKey.self, value: [proxy.frame(in: .global)])
}
}
}
}
.onPreferenceChange(FrameKey.self) { values in
self.spacing = values.indices.reduce(0.0) { spacing, current in
if let previous = values.index(current, offsetBy: -1, limitedBy: values.startIndex) {
let value = values[current].minY - values[previous].maxY
if value > spacing { return value }
}
return spacing
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment