Last active
November 13, 2023 12:20
-
-
Save michaelevensen/807db6f862bbda61aa8a89b5059a830a to your computer and use it in GitHub Desktop.
A really handy extension to `View` which enables dynamic binding of `GeometryReader` properties like `Size`, `Frame` and `SafeAreaInsets` for a `View`. This is particularly handy when you want to share `GeometryReader` output between other `View`'s. All credit goes to @danielsaidi.
This file contains 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
// | |
// View+Geometry.swift | |
// SwiftUIKit | |
// | |
// Created by Daniel Saidi on 2020-03-26. | |
// Copyright © 2020 Daniel Saidi. All rights reserved. | |
// | |
import SwiftUI | |
public extension View { | |
/** | |
Bind the view's safe area to a binding. | |
*/ | |
func bindSafeAreaInsets(to binding: Binding<EdgeInsets>) -> some View { | |
background(safeAreaBindingView(for: binding)) | |
} | |
/** | |
Bind the view's size to a binding. | |
*/ | |
func bindSize(to binding: Binding<CGSize>) -> some View { | |
background(sizeBindingView(for: binding)) | |
} | |
/** | |
Bind the view's frame to a binding. | |
*/ | |
func bindFrame(to binding: Binding<CGRect>, | |
coordinateSpace: CoordinateSpace = .global) -> some View { | |
background(frameBindingView(for: binding, coordinateSpace: coordinateSpace)) | |
} | |
} | |
private extension View { | |
func changeStateAsync(_ action: @escaping () -> Void) { | |
DispatchQueue.main.async(execute: action) | |
} | |
func safeAreaBindingView(for binding: Binding<EdgeInsets>) -> some View { | |
GeometryReader { geo in | |
self.safeAreaBindingView(for: binding, geo: geo) | |
} | |
} | |
func safeAreaBindingView(for binding: Binding<EdgeInsets>, geo: GeometryProxy) -> some View { | |
changeStateAsync { binding.wrappedValue = geo.safeAreaInsets } | |
return Color.clear | |
} | |
func sizeBindingView(for binding: Binding<CGSize>) -> some View { | |
GeometryReader { geo in | |
self.sizeBindingView(for: binding, geo: geo) | |
} | |
} | |
func sizeBindingView(for binding: Binding<CGSize>, geo: GeometryProxy) -> some View { | |
changeStateAsync { binding.wrappedValue = geo.size } | |
return Color.clear | |
} | |
func frameBindingView(for binding: Binding<CGRect>, coordinateSpace: CoordinateSpace = .global) -> some View { | |
GeometryReader { geo in | |
self.frameBindingView(for: binding, geo: geo, coordinateSpace: coordinateSpace) | |
} | |
} | |
func frameBindingView(for binding: Binding<CGRect>, geo: GeometryProxy, coordinateSpace: CoordinateSpace) -> some View { | |
changeStateAsync { binding.wrappedValue = geo.frame(in: coordinateSpace) } | |
return Color.clear | |
} | |
} |
And like this if you want to specify CoordinateSpace
.
struct GeometryReaderExample: View {
@State private var stackFrame: CGRect
var body: some View {
ScrollView {
HStack {
ForEach(0..<100) { _ in
Text("Some Content..")
}
}.bindFrame(to: $stackFrame, coordinateSpace: .local)
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use it like this. Default
CoordinateSpace
is.global