点击按钮外区域致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
相关产品推荐
相关产品推荐

