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

Swift自定义回调首次实例失效,二次实例正常问题排查

问题描述

我实现了一个自定义UIStackView组件TitleWithSwitchView,代码如下:

class TitleWithSwitchView: UIStackView {
    let title: String
    var onSwitchTapped: ((Bool) -> Void)?
    override init(frame: CGRect) {
        self.title = ""
        super.init(frame: frame)
    }
    
    required init(coder: NSCoder) {
        self.title = ""
        super.init(coder: coder)
    }
    
    init(title: String) {
        self.title = title
        super.init(frame: .zero)
        setupUI()
        setupConstraints()
    }
    
    private var label: UILabel {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textColor = .white
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.text = "-"
        return label
    }
    
    private lazy var labelTitle = {
        let label = label
        label.text = title
        return label
    }()
    
    private lazy var switchButton = {
        let switchButton = UISwitch()
        switchButton.translatesAutoresizingMaskIntoConstraints = false
        switchButton.addTarget(self, action: #selector(switchTapped), for: .valueChanged)
        return switchButton
    }()
    
    @objc private func switchTapped() {
        onSwitchTapped?(switchButton.isOn)
    }
}

创建该组件的两个实例后,仅第二个实例的开关可正常响应点击,第一个实例点击无任何反应:

private lazy var stackViewHobbyContainer = {
    let stack = TitleWithSwitchView(title: "Some Text")
    stack.onSwitchTapped = { [weak self] isOn in
        self?.onHobbyContainerSwitchChange(isOn: isOn)
    }
    return stack
}()

private lazy var stackViewChallengeSwitchView = {
    let stack = TitleWithSwitchView(title: "Another Text?")
    stack.onSwitchTapped = { [weak self] isOn in
        print("Switch tapped \(isOn)")
    }
    return stack
}()
问题分析与修复

核心原因

第一个实例的开关被标题标签挤压或遮挡,导致无法接收点击事件。你的标签设置了numberOfLines = 0(自动换行),在栈视图默认布局逻辑下,长文本的标签会优先占据可用空间,把开关挤出屏幕范围或完全遮挡;而第二个实例的标题文本较短,开关能正常显示,因此可正常响应。

修复步骤

  1. 完善setupUI()方法(你之前的代码缺失了这部分关键布局逻辑):
private func setupUI() {
    // 将子视图添加到栈视图
    addArrangedSubview(labelTitle)
    addArrangedSubview(switchButton)
    
    // 设置栈视图基础属性
    axis = .horizontal
    spacing = 16
    
    // 让标签和开关分别靠左右两侧排列,避免互相挤压
    distribution = .spaceBetween
    alignment = .center
    
    // 给开关设置更高的水平内容优先级,确保其宽度不被压缩
    switchButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
    // 允许标签在空间不足时被压缩
    labelTitle.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
  1. 简化标签创建逻辑,移除多余的label计算属性:
private lazy var labelTitle: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.textColor = .white
    label.numberOfLines = 0
    label.lineBreakMode = .byWordWrapping
    label.text = title
    return label
}()

额外检查点

  • 确认两个实例都已正确添加到父视图,且未被其他视图完全覆盖
  • 检查onHobbyContainerSwitchChange方法是否存在且逻辑正常,避免因闭包执行后无反馈,误以为开关未响应

内容的提问来源于stack exchange,提问作者Rahul Vyas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 10:02:52