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

WatchOS应用使用Picker时触发ScrollView contentOffset绑定读取错误

WatchOS Picker 组件「ScrollView contentOffset binding has been read」错误修复

问题场景

在WatchOS应用中使用Picker组件时,Picker功能正常,但每次加载视图都会两次弹出以下错误提示:

ScrollView contentOffset binding has been read; this will cause grossly inefficient view performance as the ScrollView's content will be updated whenever its contentOffset changes. Read the contentOffset binding in a view that is not parented between the creator of the binding and the ScrollView to avoid this.
ScrollView contentOffset binding has been read; this will cause grossly inefficient view performance as the ScrollView's content will be updated whenever its contentOffset changes. Read the contentOffset binding in a view that is not parented between the creator of the binding and the ScrollView to avoid this.

相关代码如下:

struct FetchWebsiteView: View {
/*@Binding var usernameParent : String
@Binding var passwordParent : String*/
@Binding var developerModeParent : Bool
@Binding var rToken : String
@State var isRunning : Bool = false
@State var projects = [""]
@State private var selectedProject = ""

var body: some View {
    VStack{
        Picker("Select a Project", selection: $selectedProject){
            ForEach(projects, id: \.self) {
                Text($0)
            }
        }.frame(height: 80)
        Spacer()
        if (selectedProject != "" && isRunning == false) {
            Button("Start Time Stamp"){
                print("Time started")
                isRunning = true
            }
        } else if (isRunning == true) {
            Button("Stop Time Stamp") {
                print("Time stopped")
                isRunning = false
                selectedProject = ""
            }
        }
        
        Spacer()
        //Text(selectedProject + "")
        
    }.onAppear{
        Task {
            projects = await doHTTPProjectsCall(token: rToken)
            }
    }
}
}

问题原因

WatchOS的Picker组件内部基于ScrollView实现,当你初始将projects设为[""],后续通过网络请求替换整个数组时,会触发ScrollView内部的contentOffset绑定被重复读取,从而引发性能警告。

解决方法

1. 延迟加载Picker,避免初始空内容渲染

在网络请求完成前显示加载状态,直到获取到有效项目数据再渲染Picker:

var body: some View {
    VStack{
        // 仅当项目数据有效时显示Picker
        if !projects.isEmpty && projects != [""] {
            Picker("Select a Project", selection: $selectedProject){
                ForEach(projects, id: \.self) {
                    Text($0)
                }
            }.frame(height: 80)
        } else {
            ProgressView("加载项目中...")
        }
        
        Spacer()
        if (selectedProject != "" && isRunning == false) {
            Button("Start Time Stamp"){
                print("Time started")
                isRunning = true
            }
        } else if (isRunning == true) {
            Button("Stop Time Stamp") {
                print("Time stopped")
                isRunning = false
                selectedProject = ""
            }
        }
        
        Spacer()
    }.onAppear{
        Task {
            projects = await doHTTPProjectsCall(token: rToken)
            // 加载完成后默认选中第一个项目(可选)
            if let firstProject = projects.first {
                selectedProject = firstProject
            }
        }
    }
}

2. 使用ObservableObject管理项目数据

将项目数据的管理逻辑抽离到单独的ObservableObject中,减少视图层级的状态刷新干扰:

// 新建数据管理类
class ProjectLoader: ObservableObject {
    @Published var projects: [String] = []
    
    func fetch(token: String) async {
        projects = await doHTTPProjectsCall(token: token)
    }
}

struct FetchWebsiteView: View {
    /*@Binding var usernameParent : String
    @Binding var passwordParent : String*/
    @Binding var developerModeParent : Bool
    @Binding var rToken : String
    @State var isRunning : Bool = false
    @StateObject private var loader = ProjectLoader() // 使用StateObject管理
    @State private var selectedProject = ""

    var body: some View {
        VStack{
            Picker("Select a Project", selection: $selectedProject){
                ForEach(loader.projects, id: \.self) {
                    Text($0)
                }
            }.frame(height: 80)
            
            Spacer()
            if (selectedProject != "" && isRunning == false) {
                Button("Start Time Stamp"){
                    print("Time started")
                    isRunning = true
                }
            } else if (isRunning == true) {
                Button("Stop Time Stamp") {
                    print("Time stopped")
                    isRunning = false
                    selectedProject = ""
                }
            }
            
            Spacer()
        }.onAppear{
            Task {
                await loader.fetch(token: rToken)
                if let firstProject = loader.projects.first {
                    selectedProject = firstProject
                }
            }
        }
    }
}

3. 优化初始状态与数据更新逻辑

将projects初始化为空数组,避免无效的空选项,同时在数据加载后处理空数据场景:

struct FetchWebsiteView: View {
    // 其他属性不变
    @State var projects: [String] = [] // 初始化为空数组

    var body: some View {
        VStack{
            Picker("Select a Project", selection: $selectedProject){
                ForEach(projects, id: \.self) {
                    Text($0)
                }
            }.frame(height: 80)
            
            // 剩余代码不变
        }.onAppear{
            Task {
                let fetchedProjects = await doHTTPProjectsCall(token: rToken)
                // 处理空数据,避免Picker无内容
                projects = fetchedProjects.isEmpty ? ["无可用项目"] : fetchedProjects
                if let firstProject = projects.first {
                    selectedProject = firstProject
                }
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Pjaks

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 09:24:20