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

调整ScrollView偏移量时出现帧尺寸错误及卡顿问题求助

问题描述

在topCardView和BottomView中使用ScrollView后,每次滚动都会触发无效帧尺寸错误,界面偶尔卡顿,报错信息如下:

Bound preference SizePreferenceKey tried to update multiple times per frame

错误截图:
Error Image

完整代码:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:05:14