如何为添加了手势的UIView实现类似Switch的开关功能?
用带手势的UIView实现类似UISwitch的开关功能
你的现有代码存在关键问题:直接访问sender的isOn属性,但普通UIView并没有这个属性,运行时会触发错误——因为手势触发时的sender是UIView实例,不是UISwitch。下面是修正后的完整实现方案:
方案一:自定义UIView(推荐)
通过自定义View封装开关状态、手势和UI逻辑,代码更清晰可复用:
import UIKit class ToggleView: UIView { // 存储开关状态,状态变更时自动更新UI var isOn = false { didSet { updateToggleUI() applyTheme() } } override init(frame: CGRect) { super.init(frame: frame) setupBaseStyle() addTapGesture() } required init?(coder: NSCoder) { super.init(coder: coder) setupBaseStyle() addTapGesture() } // 设置开关基础样式 private func setupBaseStyle() { layer.cornerRadius = bounds.height / 2 backgroundColor = .systemGray5 clipsToBounds = true // 添加滑块子视图,贴近UISwitch视觉效果 let slider = UIView(frame: CGRect(x: 2, y: 2, width: bounds.height - 4, height: bounds.height - 4)) slider.layer.cornerRadius = slider.bounds.height / 2 slider.backgroundColor = .white slider.tag = 100 addSubview(slider) } // 添加点击手势 private func addTapGesture() { let tap = UITapGestureRecognizer(target: self, action: #selector(toggleState)) addGestureRecognizer(tap) } // 切换开关状态 @objc private func toggleState() { isOn.toggle() } // 更新开关UI(含动画) private func updateToggleUI() { backgroundColor = isOn ? .systemGreen : .systemGray5 guard let slider = viewWithTag(100) else { return } UIView.animate(withDuration: 0.2) { slider.frame.origin.x = self.isOn ? self.bounds.width - slider.bounds.width - 2 : 2 } } // 应用主题切换逻辑 private func applyTheme() { if isOn { print("It's on") AppearanceService.shared.theme = Themes.light } else { print("It's off") AppearanceService.shared.theme = Themes.dark } view.window?.overrideUserInterfaceStyle = AppearanceService.shared.theme.getUserInterfaceStyle() } }
使用时直接在控制器中添加这个自定义View即可:
let toggleView = ToggleView(frame: CGRect(x: 80, y: 100, width: 60, height: 30)) view.addSubview(toggleView)
方案二:给普通UIView扩展状态属性
如果不想自定义View,可以通过关联对象给UIView添加isOn属性,快速实现功能:
import UIKit import ObjectiveC // 用关联对象存储开关状态 private var ToggleIsOnKey: UInt8 = 0 extension UIView { var isOn: Bool { get { objc_getAssociatedObject(self, &ToggleIsOnKey) as? Bool ?? false } set { objc_setAssociatedObject(self, &ToggleIsOnKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } // 控制器中使用 class YourViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupToggleView() } private func setupToggleView() { let toggleView = UIView(frame: CGRect(x: 80, y: 100, width: 60, height: 30)) toggleView.layer.cornerRadius = 15 toggleView.backgroundColor = .systemGray5 // 添加点击手势 let tap = UITapGestureRecognizer(target: self, action: #selector(handleToggle(_:))) toggleView.addGestureRecognizer(tap) view.addSubview(toggleView) } @objc func handleToggle(_ sender: UIView) { // 切换状态 sender.isOn.toggle() // 更新UI sender.backgroundColor = sender.isOn ? .systemGreen : .systemGray5 // 主题切换逻辑 if sender.isOn { print("It's on") AppearanceService.shared.theme = Themes.light } else { print("It's off") AppearanceService.shared.theme = Themes.dark } view.window?.overrideUserInterfaceStyle = AppearanceService.shared.theme.getUserInterfaceStyle() } }
核心修正点:
- 必须给UIView添加自己的
isOn状态存储(自定义类属性或关联对象) - 状态切换时要同步更新UI,给用户视觉反馈
- 避免直接强制转换
sender为未知类型访问不存在的属性
内容的提问来源于stack exchange,提问作者lin lin
相关产品推荐
相关产品推荐

