SwiftUI ScrollView动态调整元素高度时循环触发警告/卡顿问题求助
SwiftUI ScrollView动态调整元素高度时循环触发警告/卡顿问题求助
大家好,我现在遇到一个SwiftUI ScrollView的棘手问题,想请教下有没有解决办法:
我想实现的效果是:ScrollView里的每个元素,根据它在滚动视图中的位置动态调整高度——当元素完全在视口上方时保持完整高度,滚动到视口内时从固定的100px逐渐过渡到完整高度,完全滚出视口下方时回到100px。
最初的实现方案(出现循环警告+崩溃)
我一开始用.onGeometryChange来监听元素在ScrollView中的位置,然后动态计算高度:
首先是ScrollView的结构:
ScrollView { VStack(spacing: 3) { ForEach(testObjects) { obj in ObjItem(obj: obj) } } }
然后是ObjItem视图(补全了原代码中缺失的参数定义):
struct ObjItem: View { let obj: TestObject @State var pos: CGFloat = 0 @State var height: CGFloat = 100 var body: some View { Image(uiImage: UIImage(named: obj.picture)!) .resizable() .aspectRatio(contentMode: .fill) .frame(height: height, alignment: .top) .cornerRadius(5) .onGeometryChange(for: CGRect.self) { proxy in proxy.frame(in: .scrollView) } action: { rect in pos = rect.minY let fullHeight = rect.width / 1280 * 800 height = calcHeight(scrollPos: pos, fullHeight: fullHeight) } } func calcHeight(scrollPos: CGFloat, fullHeight: CGFloat) -> CGFloat { if scrollPos < 0 { return fullHeight } else if scrollPos < fullHeight { return 100 + (1 - scrollPos / fullHeight) * (fullHeight - 100) } else { return 100 } } }
这个逻辑在预览里是正常的,但一到模拟器或真机运行,就会弹出Geometry action is cycling between duplicate values.的警告,滚动几次后直接崩溃。我猜是因为调整元素高度会改变它在ScrollView里的位置,而位置变化又会触发高度重新计算,形成了无限循环。
改用GeometryReader后的问题(卡顿)
后来我换成用GeometryReader来获取位置和宽度,去掉了.onGeometryChange:
struct ObjItem: View { let obj: TestObject @State var pos: CGFloat = 0 @State var height: CGFloat = 100 var body: some View { Image(uiImage: UIImage(named: obj.picture)!) .resizable() .aspectRatio(contentMode: .fill) .frame(height: height, alignment: .top) .cornerRadius(5) .background { GeometryReader { proxy in Color.clear .onAppear { let rect = proxy.frame(in: .scrollView) pos = rect.minY let fullHeight = rect.width / 1280 * 800 height = calcHeight(scrollPos: pos, fullHeight: fullHeight) } .onChange(of: proxy.frame(in: .scrollView)) { newRect in pos = newRect.minY let fullHeight = newRect.width / 1280 * 800 height = calcHeight(scrollPos: pos, fullHeight: fullHeight) } } } } func calcHeight(scrollPos: CGFloat, fullHeight: CGFloat) -> CGFloat { if scrollPos < 0 { return fullHeight } else if scrollPos < fullHeight { return 100 + (1 - scrollPos / fullHeight) * (fullHeight - 100) } else { return 100 } } }
这次没有循环警告和崩溃了,但在iPhone 14上滚动明显卡顿,掉帧很严重。
我的疑问
- 是不是我用
.onGeometryChange的姿势完全错了?有没有办法避免循环触发? - 用GeometryReader的话,怎么优化才能解决卡顿问题?
- 有没有更简单的方案能实现这种“根据滚动位置动态调整元素高度”的效果?
麻烦各位大佬指点下,谢谢!
内容来源于stack exchange
相关产品推荐
相关产品推荐

