Is there any way to get the size of a child view in SwiftUI?
I\'m basically looking to do the UIKit equivalent of:
Basically, the answer at this point is to use a GeometryReader
inside of the child's background(...)
modifier.
// This won't be valid until the first layout pass is complete
@State var childSize: CGSize = .zero
var body: some View {
ZStack {
Text("Hello World!")
.background(
GeometryReader { proxy in
Color.clear.
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.childSize = preferences
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
You can use AnchorPreferences to bubble information about a child view's geometry up to a parent view. See: https://swiftui-lab.com/communicating-with-the-view-tree-part-2/
Updated and generalized @arsenius code. Now you can easily bind a parent view's state variable.
struct ChildSizeReader<Content: View>: View {
@Binding var size: CGSize
let content: () -> Content
var body: some View {
ZStack {
content()
.background(
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.size = preferences
}
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}
Usage:
struct ChildSizeReaderExample: View {
@State var textSize: CGSize = .zero
var body: some View {
VStack {
ChildSizeReader(size: $textSize) {
Text("Hello I am some arbitrary text.")
}
Text("My size is \(textSize.debugDescription)!")
}
}
}
Here's a reusable variant of the accepted answer:
protocol SizeReaderKey: PreferenceKey where Value == CGSize {}
extension SizeReaderKey {
static func reduce(value _: inout CGSize, nextValue: () -> CGSize) {
_ = nextValue()
}
}
struct SizeReader<Key: SizeReaderKey>: ViewModifier {
func body(content: Content) -> some View {
content
.background(
GeometryReader { geo in
Color.clear
.preference(key: Key.self, value: geo.size)
}
)
}
}
extension View {
func onSizeChanged<Key: SizeReaderKey>(
_ key: Key.Type,
perform action: @escaping (CGSize) -> Void) -> some View
{
self
.modifier(SizeReader<Key>())
.onPreferenceChange(key) { value in
action(value)
}
}
}
Usage:
struct Example: View {
var body: some View {
Text("Hello, World!")
.onSizeChanged(CustomViewSizeKey.self) { size in
print("size: \(size)")
}
}
struct CustomViewSize: SizePreferenceKey {
static var defaultValue: CGSize = .zero
}
}