SwiftUI-获取孩子的大小?

时间:2019-06-13 04:28:01

标签: swiftui

有什么方法可以在SwiftUI中获得子视图的大小吗?

我基本上是想做等效于以下内容的UIKit:

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

我认为GeometryReader不起作用,因为它会返回父级中的可用大小。

[编辑]我发现可以使用.alignmentGuide(_, computeValue:)来获取和保存尺寸,尽管这绝对是个技巧。

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

5 个答案:

答案 0 :(得分:13)

已更新和通用的@arsenius代码。现在,您可以轻松绑定父视图的状态变量。

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()
    }
}

用法:

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)!")
        }
    }
}

答案 1 :(得分:0)

基本上,这时的答案是在孩子的background(...)修饰符的内部 中使用GeometryReader。不过,只有在布局好视图之后,才能进行评估。

// This won't be valid until the first layout pass is complete
@State var childRect: CGRect = .zero

var body: some View {
    Text("Hello World!")
        .background(
            GeometryReader { proxy in
                let rect = proxy.frame(in: .global)
                // This avoids an infinite layout loop
                if rect.integral != self.childRect.integral {
                    DispatchQueue.main.async(after: .now()) {
                        self.childRect = rect
                }
            }
            // Just `EmptyView()` results in the background being ignored, so the size won't be calculated.
            return AnyView(EmptyView())
        }
    )
}

答案 2 :(得分:0)

您可以使用AnchorPreferences将有关子视图几何图形的信息冒泡直至父视图。参见:https://swiftui-lab.com/communicating-with-the-view-tree-part-2/

答案 3 :(得分:0)

以下是已接受答案的可重用变体:

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)
            }
    }
}

用法:

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
    }
}

答案 4 :(得分:0)

我参考了以下所有已经回答的代码。

this answer 的自定义修饰符:

extension View {
  func size(size: Binding<CGSize>) -> some View {
    ChildSizeReader(size: size) {
      self
    }
  }
}

作为 this comment says,我认为 ZStack 没有必要,所以我也发布了一个删除了 ZStack 的版本。

所有代码:

import SwiftUI

struct ContentView: View {
  var body: some View {
    ChildSizeReaderExample()
  }
}

struct ChildSizeReaderExample: View {
  @State var textSize: CGSize = .zero

  var body: some View {
    VStack {
      Text("Hello I am some arbitrary text.").size(size: $textSize) // Usage
      Text("My size is \(textSize.debugDescription)")
    }
  }
}

struct ChildSizeReader<Content: View>: View {
  @Binding var size: CGSize

  let content: () -> Content
  var body: some View {
    // Remove ZStack from the existing answer.
    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()
  }
}

extension View {
  func size(size: Binding<CGSize>) -> some View {
    ChildSizeReader(size: size) {
      self
    }
  }
}