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

如何从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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:39:54