为何onPreferenceChange在特定场景下返回错误尺寸值?
问题原因与解决方案
核心原因
你遇到的问题根源有两点:
- SwiftUI偏好值的多阶段更新:视图初始化和布局过程中,偏好值会被多次传递。第一次会先使用
PreferenceKey的defaultValue(也就是你设置的0),之后才会传递GeometryReader读取到的正确尺寸。 - 错误的reduce方法逻辑:你当前的
reduce方法直接用新值覆盖旧值,这会导致如果某次更新的新值是0(比如初始默认值),且它是最后一次被合并的结果,onPreferenceChange就会收到0。不同的视图结构(默认Button、条件语句)会改变偏好值更新的顺序或次数,使得0成为最终保留的值。
解决方案
1. 修复PreferenceKey的reduce方法
修改reduce逻辑,确保有效尺寸会覆盖初始的0值。两种常见的实现方式:
方式一:取最大值(推荐)
struct HeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { // 用最大值确保有效尺寸不会被0覆盖 value = max(value, nextValue()) } }
方式二:仅保留非0的有效尺寸
struct HeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { let newValue = nextValue() // 只更新大于0的有效尺寸 if newValue > 0 { value = newValue } } }
2. 优化GeometryReader的位置(可选)
把GeometryReader放在ZStack内部,直接读取ZStack的尺寸,这样布局逻辑更直观,也能减少外部修饰符带来的不确定性:
struct ContentView: View { var body: some View { ZStack { Color.yellow // 插入你的内容 GeometryReader { proxy in Color.clear .preference(key: HeightPreferenceKey.self, value: proxy.size.height) } } .frame(width: 300, height: 300) .onPreferenceChange(HeightPreferenceKey.self) { height in print("onPreferenceChange height: \(height)") } } }
效果验证
修改后,无论插入默认Button还是条件语句,onPreferenceChange都会正确收到300.0的尺寸值。
内容的提问来源于stack exchange,提问作者Hans Terje Bakke
相关产品推荐
相关产品推荐

