SwiftUI 布局中,background 和 overlay 不会改变主视图的尺度,布局容器 frame 直接运用主视图的 frame。因而咱们可以经过 background + GeometryReader 来完成实时获取视图尺度。

经过把 Color.clear 放置在 background 的 GeometryReader 中实时读取尺度:

struct SizeCalculator: ViewModifier {
    @Binding var size: CGSize
    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { proxy in
                    let geoSize = proxy.size
                    Color.clear
                        .onAppear {
                            size = geoSize
                        }.onChange(of: geoSize) { newValue in
                            size = geoSize
                        }
                }
            )
    }
}

特别要强调除了注册 onAppear 还需要注册 onChange。如果只调查了 onAppear 就会出现在同一个页面手机横屏布局改写,视图尺度没有更新的问题。由于咱们的 Color.clear 一向都在界面上,onAppear 不会被再次调用,因而不会更新尺度。View 每一次改写 GeometryReader 都会被从头调用,因而咱们经过在 GeometryReader 的闭包中声明一个变量用来调查尺度是否改变。

为了让获取尺度愈加方面,咱们可以给 View 声明一个扩展:

extension View {
    func readSize(in size: Binding<CGSize>) -> some View {
        modifier(SizeCalculator(size: size))
    }
}

运用的方法如下:

struct SizeReader: View {
    @State var size: CGSize = .zero
    var body: some View {
        VStack {
            Text("text width: \(size.width)")
            Text("text height: \(size.height)")
            Color.gray
                .readSize(in: $size)
        }
    }
}
SwiftUI Tips: 如何实时获取View的尺寸

除了运用 Color.clear 作为占位内容,咱们还可以运用 Path 作为内容层。由于每次 View 改写,Path 都会被从头烘托,因而在 Path 闭包中获取尺度也是可行的。完成如下:

struct SizeCalculator: ViewModifier {
    @Binding var size: CGSize
    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { proxy in
                    Path { path in
                        DispatchQueue.main.async {
                            if size != proxy.size {
                                size = proxy.size
                            }
                        }
                    }
                }
            )
    }
}

因而 Path 天然每次都会改写,因而比较 Color.clear 可以省掉注册 onAppear 和 onChange。但是 Path 有一个需要留意的地方是由于咱们是在 Path 内容烘托中设置了状态值 size 改变,因而每次设置 size 都会引发 View 的从头烘托。为了避免死循环,在设置 size 值的时分咱们包在一个异步主线程中更新,并且设置的时分判别尺度是否修改。

咱们获取尺度还有一个场景是在开发过程中为了方便调试打印尺度到控制台。在这个场景下直接运用 Path 代码就会简洁了一些了。

extension View {
    func _printSize() -> some View {
        self.background(
            GeometryReader { proxy in
                Path { path in
                    print("frame size = \(proxy.size)")
                }
            }
        )
    }
}