WatchOS应用使用Picker时触发ScrollView contentOffset绑定读取错误
问题场景
在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

