SwiftUI Tips: 如何实时获取View的尺寸

SwiftUI 布局中,background 和 overlay 不会改变主视图的尺寸,布局容器 frame 直接使用主视图的 frame。因此我们可以通过 background + GeometryReader 来实现实时获取视图尺寸。

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

swift 复制代码
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 声明一个扩展:

swift 复制代码
extension View {
    func readSize(in size: Binding<CGSize>) -> some View {
        modifier(SizeCalculator(size: size))
    }
}

使用的方式如下:

swift 复制代码
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)
        }
    }
}

除了使用 Color.clear 作为占位内容,我们还可以使用 Path 作为内容层。因为每次 View 刷新,Path 都会被重新渲染,因此在 Path 闭包中获取尺寸也是可行的。实现如下:

swift 复制代码
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 代码就会简洁了一些了。

swift 复制代码
extension View {
    func _printSize() -> some View {
        self.background(
            GeometryReader { proxy in
                Path { path in
                    print("frame size = \(proxy.size)")
                }
            }
        )
    }
}
相关推荐
茶底世界之下16 小时前
iPhone Duo 适配实战,Awaking 踩过的 8 个坑
ios·swiftui·swift
东坡肘子21 小时前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
人工智能·swiftui·swift
瓦瓦gogo5 天前
第一次了解 iOS 开发:从 Swift、SwiftUI 到公共组件
ios·swiftui·swift
传奇开心果编程7 天前
组合优先开发范式:从汉字造字原理到声明式UI组件组合思想
android·flutter·ios·swiftui·移动开发·harmonyos·android jetpack
东坡肘子8 天前
当应用不再属于特定操作系统 -- 肘子的 Swift 周报 #159
人工智能·swiftui·swift
东坡肘子15 天前
谁是 AI-relay App 的真正威胁 -- 肘子的 Swift 周报 #158
人工智能·swiftui·swift
东坡肘子22 天前
当 Build in Public 重新转入地下 -- 肘子的 Swift 周报 #157
人工智能·swiftui·swift
OliverQiu22 天前
开源一个 macOS 小工具:集中管理 ChatGPT 的网络启动配置
macos·chatgpt·swiftui·mac·proxy·codex·dmg
传奇开心果编程1 个月前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
传奇开心果编程1 个月前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer