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

点击按钮外区域致App崩溃:UIStackView子视图越界问题

修复UIStackView子视图访问越界的崩溃问题

你的崩溃原因很明确:当点击按钮外区域时,settingsMenu的subviews数组是空的,但你直接通过索引[0]、[1]访问,导致数组越界。下面是几个可行的修复方案,按推荐程度排序:

1. 用手势识别替代touchesBegan(最推荐)

放弃在父视图的touchesBegan里全局判断触摸,直接给两个子视图添加点击手势,逻辑更清晰,也从根源避免数组访问问题:

// 先在类里定义子视图的属性,方便后续引用
private let firstMenuView = UIView()
private let secondMenuView = UIView()

// 创建菜单视图时添加手势
private func setupSettingsMenu() {
    // 给第一个子视图加点击手势
    let firstTap = UITapGestureRecognizer(target: self, action: #selector(firstMenuAction))
    firstMenuView.addGestureRecognizer(firstTap)
    firstMenuView.isUserInteractionEnabled = true // 记得开启交互,默认UIView是关闭的
    
    // 给第二个子视图加点击手势
    let secondTap = UITapGestureRecognizer(target: self, action: #selector(secondMenuAction))
    secondMenuView.addGestureRecognizer(secondTap)
    secondMenuView.isUserInteractionEnabled = true
    
    // 将子视图加入StackView
    settingsMenu.addArrangedSubview(firstMenuView)
    settingsMenu.addArrangedSubview(secondMenuView)
}

// 对应的点击事件处理
@objc private func firstMenuAction() {
    // 执行第一个视图的操作逻辑
}

@objc private func secondMenuAction() {
    // 执行第二个视图的操作逻辑
}

这种方式只有当子视图存在且可见时,手势才会响应,完全不会出现访问空数组的情况。

2. 持有子视图引用,避免依赖subviews数组

如果一定要用touchesBegan,不要通过subviews索引取子视图,而是提前把两个子视图用属性存起来:

private let firstMenuView = UIView()
private let secondMenuView = UIView()

// 初始化StackView时直接使用这些属性
private func setupSettingsMenu() {
    settingsMenu.addArrangedSubview(firstMenuView)
    settingsMenu.addArrangedSubview(secondMenuView)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    guard let touch = touches.first else { return }
    let touchPoint = touch.location(in: self.view)
    
    // 先判断StackView是否可见,再判断触摸位置
    guard settingsMenu.isHidden == false else { return }
    
    if firstMenuView.frame.contains(touchPoint) {
        // 第一个视图的操作
    } else if secondMenuView.frame.contains(touchPoint) {
        // 第二个视图的操作
    }
}

3. 给subviews访问加安全校验(临时修复)

如果不想改太多代码,至少要在访问索引前判断数组长度:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    guard let touch = touches.first else { return }
    let touchPoint = touch.location(in: self.view)
    
    // 先检查StackView是否有足够的子视图
    guard settingsMenu.subviews.count >= 2 else {
        // 子视图还未创建/已被移除,直接返回
        return
    }
    
    let firstView = settingsMenu.subviews[0]
    let secondView = settingsMenu.subviews[1]
    
    if firstView.frame.contains(touchPoint) {
        // 操作逻辑
    } else if secondView.frame.contains(touchPoint) {
        // 操作逻辑
    }
}

不过这种方式不推荐,因为subviews的顺序可能因为布局调整、子视图添加顺序变化而改变,后续容易出现逻辑错误。

内容的提问来源于stack exchange,提问作者Alex Smith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 01:38:16