SwiftUI如何避免onChange被ViewModel修改触发重复调用?
解决方案
方案1:使用自定义Binding(推荐)
直接替换原有Toggle的绑定逻辑,移除onChange监听,将用户操作的触发逻辑放到Binding的set方法中,只有用户主动操作Toggle时才会执行逻辑,ViewModel内部修改值仅会刷新UI不会触发重复逻辑。
修改后的代码如下:
import SwiftUI struct ContentView: View { @StateObject var myViewModel = MyViewModel() var body: some View { VStack(spacing: 0) { Toggle("Use xyz ?", isOn: Binding( get: { myViewModel.turnToggleOn }, set: { userTurnedOn in print("on change called") myViewModel.shouldUseToggle(userTurnedOn: userTurnedOn) } )) // 移除原有的onChange监听 } } } class MyViewModel: ObservableObject { @Published var turnToggleOn = false func shouldUseToggle(userTurnedOn: Bool) { /// 复杂逻辑保留不变 print("shouldUseToggle called") let x = Int.random(in: 0..<10) print(x) if userTurnedOn { turnToggleOn = x > 5 } else { turnToggleOn = x > 3 } } }
原理说明
自定义Binding的set方法仅会在用户与Toggle交互主动修改状态时触发,ViewModel内部修改@Published的turnToggleOn属性时,只会触发Binding的get方法刷新Toggle的显示状态,不会进入set逻辑,从根源避免了重复触发的问题,也不会再出现单帧多次更新的警告。
方案2:增加内部修改标记位(备选)
如果需要保留onChange的监听逻辑,可以在ViewModel中新增私有标记位区分修改来源:
class MyViewModel: ObservableObject { @Published var turnToggleOn = false // 新增内部修改标记 private var isInternalModifying = false func shouldUseToggle(userTurnedOn: Bool) { print("shouldUseToggle called") let x = Int.random(in: 0..<10) print(x) // 修改值前标记为内部修改 isInternalModifying = true if userTurnedOn { turnToggleOn = x > 5 } else { turnToggleOn = x > 3 } // 异步复位标记,避免值变化的通知还没发出就提前复位 DispatchQueue.main.async { self.isInternalModifying = false } } } // View中的onChange修改为: .onChange(of: myViewModel.turnToggleOn) { userTurnedOn in guard !myViewModel.isInternalModifying else { return } print("on change called") myViewModel.shouldUseToggle(userTurnedOn: userTurnedOn) }
注意事项
该方案需要确保标记位的正常复位,否则会出现用户操作无响应的问题,适合需要多来源监听值变化的复杂场景使用。
内容的提问来源于stack exchange,提问作者hbr4u7vb2sljksdf2
相关产品推荐
相关产品推荐

