SwiftUI中如何避免Toggle视图onChange递归调用问题
SwiftUI单选Toggle按钮组避免递归调用的解决方案
问题描述
我想用SwiftUI实现一个水平排列的Toggle按钮组,当作单选选择器,同一时间只能选中一个分类。选中某分类后,调用rs.unselectOtherCategory(id: category.id)取消其他分类的选中状态。
现在用onChange监听category.isSelected的变化来处理逻辑,但调用取消其他分类的方法时,会修改之前选中分类的状态,导致再次触发onChange,出现递归调用。另外,为了防止点击已选中按钮时所有分类都被取消,我加了else块强制该分类保持选中,逻辑变得很复杂。
想知道怎么避免onChange重复调用,满足需求:点击按钮选中该按钮,其他自动取消;点击已选中的按钮,保持选中状态。
方案一:维护单一选中ID(最简洁的单选逻辑)
别给每个Category单独维护isSelected,而是在状态或ViewModel里存一个selectedCategoryId变量,直接用这个变量控制Toggle的选中状态,从根源上避免多个状态变化触发多次回调的问题。
代码示例:
// 先在你的状态/ViewModel里加这个变量,可设置初始选中项 @State private var selectedCategoryId: String? = "初始选中分类ID" HStack { ForEach(rs.newsCategoryCollection) { category in Toggle(category.id, isOn: .constant(selectedCategoryId == category.id)) .toggleStyle(.button) .onTapGesture { // 已选中的按钮点击不做任何操作 guard selectedCategoryId != category.id else { return } // 更新选中ID selectedCategoryId = category.id // 加载新闻数据 viewModel.loadNews() // 如果需要同步更新rs里的isSelected状态,在这里统一处理 rs.newsCategoryCollection.forEach { $0.isSelected = $0.id == category.id } } } }
逻辑清晰直接:点击未选中的按钮才更新状态,同步所有分类的选中标记,全程不会触发多余的回调。
方案二:优化现有逻辑,自定义Binding控制状态变化
如果必须保留每个Category的isSelected字段,可以用自定义Binding替代默认的绑定,在setter里一次性处理所有逻辑,避免onChange的递归触发:
HStack { ForEach($rs.newsCategoryCollection) { $category in // 自定义Binding,接管状态设置逻辑 let toggleBinding = Binding( get: { category.isSelected }, set: { newValue in // 只有点击未选中的按钮时才处理逻辑 guard newValue, !category.isSelected else { // 点击已选中的按钮,强制保持选中状态 category.isSelected = true return } // 取消其他分类选中状态 rs.unselectOtherCategory(id: category.id) // 确保当前分类处于选中状态 category.isSelected = true // 加载新闻数据 viewModel.loadNews() } ) Toggle(category.id, isOn: toggleBinding) .toggleStyle(.button) } }
这里把所有状态修改逻辑都放在自定义Binding的setter里,一次性完成取消其他分类、设置当前选中、加载数据的操作,不会因为修改其他分类的状态再次触发当前Toggle的回调,彻底解决递归调用问题。
内容的提问来源于stack exchange,提问作者Mahin
相关产品推荐
相关产品推荐

