如何从UIKit中禁用SwiftUI自定义Toggle按钮
解决SwiftUI Toggle按钮在UIKit中无法禁用的问题
问题根源分析
你的代码存在几个核心问题导致无法正确禁用按钮:
- @State属性误用:
TwoStateButton中的isDisabled和isSelected使用@State修饰,这是SwiftUI用于管理视图内部私有状态的属性,外部直接修改(如UIKit中twoStateButton.isDisabled.toggle())不会触发视图刷新,因为@State的更新只能由SwiftUI框架内部处理。 - 值类型视图特性:SwiftUI视图是值类型,你在UIKit中持有
twoStateButton实例后,修改它的属性不会同步到UIHostingController已加载的视图实例,相当于修改的是一个副本。 - 代码笔误:
toggleButton初始化时引用了未定义的twoStateView,应该改为twoStateButton。
解决方案
步骤1:重构TwoStateButton,用@Binding暴露外部可控状态
将内部的@State替换为@Binding,允许外部传递并控制状态:
struct TwoStateButton: View { @Binding var isSelected: Bool @Binding var isDisabled: Bool var onTap: ((Bool) -> Void)? = nil private let imageSize: CGFloat = 60 // 补充缺失的常量定义 var body: some View { Toggle(isOn: $isSelected) { Image("buttonImage") .resizable() .frame(width: 50, height: 50) .padding(EdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)) .background( Circle() .fill(Color.clear) .shadow(color: Color.black, radius: 50) .frame(width: imageSize, height: imageSize) ) } .disabled(isDisabled) .toggleStyle(.button) .onChange(of: isSelected) { value in onTap?(value) } .onChange(of: isDisabled) { value in print("按钮禁用状态变化:\(value)") } } }
步骤2:在UIKit的MyView中管理状态,通过Binding传递给SwiftUI视图
在UIKit中持有状态变量,通过Binding将状态传递给TwoStateButton,状态变化时更新UIHostingController的rootView:
class MyView: UIView { private var isButtonSelected = false private var isButtonDisabled = false private lazy var hostingController: UIHostingController<TwoStateButton> = { let button = TwoStateButton( isSelected: Binding( get: { self.isButtonSelected }, set: { self.isButtonSelected = $0 } ), isDisabled: Binding( get: { self.isButtonDisabled }, set: { self.isButtonDisabled = $0 } ), onTap: { value in print("按钮选中状态变化:\(value)") } ) return UIHostingController(rootView: button) }() private lazy var toggleButton: UIView = hostingController.view override init(frame: CGRect) { super.init(frame: frame) addSubview(toggleButton) // 给按钮添加约束,确保正常显示 toggleButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ toggleButton.topAnchor.constraint(equalTo: topAnchor), toggleButton.leadingAnchor.constraint(equalTo: leadingAnchor), toggleButton.trailingAnchor.constraint(equalTo: trailingAnchor), toggleButton.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func updateView() { isButtonDisabled.toggle() // 重置rootView触发SwiftUI视图刷新 hostingController.rootView = TwoStateButton( isSelected: Binding(get: { self.isButtonSelected }, set: { self.isButtonSelected = $0 }), isDisabled: Binding(get: { self.isButtonDisabled }, set: { self.isButtonDisabled = $0 }), onTap: { value in print("按钮选中状态变化:\(value)") } ) print("twoStateButton.isDisabled=\(isButtonDisabled)") } }
更优雅的方式:用ObservableObject自动同步状态
如果需要复杂状态管理,可使用ObservableObject实现自动同步,无需手动重置rootView:
// 定义ViewModel管理按钮状态 class ButtonViewModel: ObservableObject { @Published var isSelected = false @Published var isDisabled = false } // 重构TwoStateButton struct TwoStateButton: View { @ObservedObject var viewModel: ButtonViewModel var onTap: ((Bool) -> Void)? = nil private let imageSize: CGFloat = 60 var body: some View { Toggle(isOn: $viewModel.isSelected) { Image("buttonImage") .resizable() .frame(width: 50, height: 50) .padding(EdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)) .background( Circle() .fill(Color.clear) .shadow(color: Color.black, radius: 50) .frame(width: imageSize, height: imageSize) ) } .disabled(viewModel.isDisabled) .toggleStyle(.button) .onChange(of: viewModel.isSelected) { value in onTap?(value) } .onChange(of: viewModel.isDisabled) { value in print("按钮禁用状态变化:\(value)") } } } // 修改MyView class MyView: UIView { private let buttonVM = ButtonViewModel() private lazy var hostingController: UIHostingController<TwoStateButton> = { let button = TwoStateButton(viewModel: buttonVM) { value in print("按钮选中状态变化:\(value)") } return UIHostingController(rootView: button) }() private lazy var toggleButton: UIView = hostingController.view override init(frame: CGRect) { super.init(frame: frame) addSubview(toggleButton) toggleButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ toggleButton.topAnchor.constraint(equalTo: topAnchor), toggleButton.leadingAnchor.constraint(equalTo: leadingAnchor), toggleButton.trailingAnchor.constraint(equalTo: trailingAnchor), toggleButton.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func updateView() { buttonVM.isDisabled.toggle() print("twoStateButton.isDisabled=\(buttonVM.isDisabled)") } }
效果说明
- 使用
@Binding或ObservableObject后,UIKit中修改状态会同步到SwiftUI视图,按钮会正确进入禁用状态,无法点击切换选中状态。 onChange回调会正常触发,打印状态变化信息。
内容的提问来源于stack exchange,提问作者Frakcool
相关产品推荐
相关产品推荐

