You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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上滚动明显卡顿,掉帧很严重。

我的疑问

  1. 是不是我用.onGeometryChange的姿势完全错了?有没有办法避免循环触发?
  2. 用GeometryReader的话,怎么优化才能解决卡顿问题?
  3. 有没有更简单的方案能实现这种“根据滚动位置动态调整元素高度”的效果?

麻烦各位大佬指点下,谢谢!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 10:43:09