调整ScrollView偏移量时出现帧尺寸错误及卡顿问题求助
问题描述
在topCardView和BottomView中使用ScrollView后,每次滚动都会触发无效帧尺寸错误,界面偶尔卡顿,报错信息如下:
Bound preference SizePreferenceKey tried to update multiple times per frame
错误截图:
完整代码:
import SwiftUI struct ContentView: View { var body: some View { ScrollView(.vertical, showsIndicators: false, content: { GeometryReader { reader in topCardView .offset(y: -reader.frame(in: .global).minY) .frame(width: reader.size.width, height: reader.frame(in: .global).minY + 450) } .frame(height: 450) VStack(spacing: 40) { BottomView } .padding(.vertical, 32) .padding(.horizontal, 16) .background( Color(red: 232/255, green: 230/255, blue: 225/255) .cornerRadius(40) ) .offset(y: -25) }) } @ViewBuilder private var topCardView: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHGrid(rows: Array(repeating: GridItem(), count: 1), spacing: 20) { ForEach(0..<2) { _ in RoundedRectangle(cornerRadius: 12) .fill(Color(red: 248/255, green: 250/255, blue: 220/255)) .frame(width: 300, height: 377) } } .padding(.horizontal, 20) } } @ViewBuilder private var BottomView: some View { ForEach(0..<2) { _ in VStack(alignment: .center) { Text("Fastest Near You") .bold() .frame(width: UIScreen.main.bounds.width - 32 * 6, height: 35) Text("Order your food here") .frame(width: UIScreen.main.bounds.width - 32 * 4, height: 25) LazyVGrid(columns: Array(repeating: GridItem(), count: 2), spacing: 12) { ForEach(0..<4) { _ in RoundedRectangle(cornerRadius: 12) .fill(Color(red: 248/255, green: 250/255, blue: 220/255)) .frame(width: UIScreen.main.bounds.width / 2 - 24, height: 200) } } .padding(.top, 20) } } } }
解决方案
1. 打破布局循环:用PreferenceKey跟踪滚动偏移
原代码中GeometryReader直接在布局闭包中修改视图的frame和offset,会触发每帧多次布局更新,导致报错。改用PreferenceKey异步跟踪滚动偏移:
首先定义自定义PreferenceKey:
struct ScrollOffsetKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} }
然后修改外层ScrollView中的视差实现逻辑:
@State private var scrollOffset: CGFloat = 0 var body: some View { ScrollView(.vertical, showsIndicators: false) { topCardView .frame(height: 450) .offset(y: -scrollOffset) .background(GeometryReader { proxy in Color.clear.preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .global).minY) }) // 下方BottomView部分保持不变 } .onPreferenceChange(ScrollOffsetKey.self) { newValue in scrollOffset = max(0, newValue) // 限制偏移量,避免视图过度上移 } }
2. 替换硬编码屏幕尺寸:用GeometryReader适配布局
直接使用UIScreen.main.bounds会导致旋转、分屏场景下布局失效,还会加剧ScrollView的尺寸冲突。修改BottomView,用父视图的可用尺寸替代硬编码值:
@ViewBuilder private var BottomView: some View { GeometryReader { parentProxy in ForEach(0..<2) { _ in VStack(alignment: .center) { Text("Fastest Near You") .bold() .frame(width: parentProxy.size.width - 32 * 6, height: 35) Text("Order your food here") .frame(width: parentProxy.size.width - 32 * 4, height: 25) // 让GridItem自动适配宽度,移除硬编码的frame宽度 LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) { ForEach(0..<4) { _ in RoundedRectangle(cornerRadius: 12) .fill(Color(red: 248/255, green: 250/255, blue: 220/255)) .frame(height: 200) } } .padding(.top, 20) } } } }
完整修复后代码
import SwiftUI struct ScrollOffsetKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} } struct ContentView: View { @State private var scrollOffset: CGFloat = 0 var body: some View { ScrollView(.vertical, showsIndicators: false) { topCardView .frame(height: 450) .offset(y: -scrollOffset) .background(GeometryReader { proxy in Color.clear.preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .global).minY) }) VStack(spacing: 40) { BottomView } .padding(.vertical, 32) .padding(.horizontal, 16) .background( Color(red: 232/255, green: 230/255, blue: 225/255) .cornerRadius(40) ) .offset(y: -25) } .onPreferenceChange(ScrollOffsetKey.self) { newValue in scrollOffset = max(0, newValue) } } @ViewBuilder private var topCardView: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHGrid(rows: [GridItem()], spacing: 20) { ForEach(0..<2) { _ in RoundedRectangle(cornerRadius: 12) .fill(Color(red: 248/255, green: 250/255, blue: 220/255)) .frame(width: 300, height: 377) } } .padding(.horizontal, 20) } } @ViewBuilder private var BottomView: some View { GeometryReader { parentProxy in ForEach(0..<2) { _ in VStack(alignment: .center) { Text("Fastest Near You") .bold() .frame(width: parentProxy.size.width - 32 * 6, height: 35) Text("Order your food here") .frame(width: parentProxy.size.width - 32 * 4, height: 25) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) { ForEach(0..<4) { _ in RoundedRectangle(cornerRadius: 12) .fill(Color(red: 248/255, green: 250/255, blue: 220/255)) .frame(height: 200) } } .padding(.top, 20) } } } } }
修复核心说明
- 用
PreferenceKey异步传递滚动偏移,避免布局闭包内的实时尺寸修改,打破循环更新 - 移除硬编码的屏幕尺寸,改用父视图尺寸适配,提升布局稳定性
- 简化网格布局的尺寸设置,让系统自动分配空间,减少手动计算带来的冲突
内容的提问来源于stack exchange,提问作者IOS Tech
相关产品推荐
相关产品推荐

