Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - Get size of child?

Tags:

swiftui

Is there any way to get the size of a child view in SwiftUI?

I'm basically looking to do the UIKit equivalent of:

self.child.frame.origin.x -= self.child.intrinsicContentSize.width/2.0

I don't think a GeometryReader would work since that returns the available size in the parent.

[Edit] I've found it's possible to get and save the dimensions using .alignmentGuide(_, computeValue:) though that's definitely a hack.

LessonSliderText(text: self.textForProgress(self.progress), color: self.completedColor)
    .alignmentGuide(HorizontalAlignment.leading) { (dimensions) -> Length in
        self.textSize = CGSize(width: dimensions.width, height: dimensions.height)
        return 0
    }
    .offset(x: self.width*self.currentPercentage - self.textSize.width / 2.0)
    .offset(y: -self.textSize.height/2.0)
    .animation(nil)
    .opacity(self.isDragging ? 1.0 : 0.0)
    .animation(.basic())

What I'm trying to accomplish

like image 306
arsenius Avatar asked Jun 13 '19 04:06

arsenius


3 Answers

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)!")
        }
    }
}
like image 122
Wil Gieseler Avatar answered Nov 14 '22 00:11

Wil Gieseler


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()
    }
}     
like image 29
arsenius Avatar answered Nov 14 '22 00:11

arsenius


Here's a reusable variant of the accepted answer:

public protocol CGSizePreferenceKey: PreferenceKey where Value == CGSize {}

public extension CGSizePreferenceKey {
    static func reduce(value _: inout CGSize, nextValue: () -> CGSize) {
        _ = nextValue()
    }
}

public extension View {
    func onSizeChanged<Key: CGSizePreferenceKey>(
        _ key: Key.Type,
        perform action: @escaping (CGSize) -> Void) -> some View
    {
        self.background(GeometryReader { geo in
            Color.clear
                .preference(key: Key.self, value: geo.size)
        })
        .onPreferenceChange(key) { value in
            action(value)
        }
    }
}

Usage:

struct Example: View {
    var body: some View {
        Text("Hello, World!")
            .onSizeChanged(ExampleViewSize.self) { size in
                print("size: \(size)")
            }
    }
}

struct ExampleViewSize: CGSizePreferenceKey {
    static var defaultValue: CGSize = .zero
}
like image 2
tadija Avatar answered Nov 14 '22 00:11

tadija