admin管理员组

文章数量:1125291

I have a UIButton with an action. If I click the button, I see a stack view with two views. And if I click on some view on stack view, I perform a new action of some kind. But for views in stack view, I don't use an action button. I'm using TouchesBegan to detect a touch and perform an action. I use next code:

class SomeC: UIViewController {
    
    lazy var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 32
        stackView.alignment = .fill
        stackView.distribution = .equalSpacing
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()

    var button: UIButton = {
        let button = UIButton()
        button.layer.shadowColor = UIColor.black.withAlphaComponent(0.5).cgColor
        button.layer.shadowOffset = CGSize.zero
        button.layer.shadowOpacity = 0.2
        button.layer.shadowRadius = 3
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    let settingsMenu = UIStackView()
    var itemStackView = UIStackView()
    let settingsBackground = UIVisualEffectView(effect: UIBlurEffect(style: .light))
        
    override func viewDidLoad() {
        super.viewDidLoad()
        
        button = UIButton()
        button.backgroundColor = .red.withAlphaComponent(0.25)
        button.widthAnchor.constraint(equalToConstant: 56).isActive = true
        button.heightAnchor.constraint(equalToConstant: 56).isActive = true
        button.addTarget(self, action: #selector(self.rightButtonAction), for: .touchUpInside)
        stackView.addArrangedSubview(button)
        stackView.widthAnchor.constraint(equalToConstant: ((72+32) * 2) - 32).isActive = true
    }
    
    @objc func rightButtonAction(sender: UIButton!) {
        
        settingsBackground.frame = UIScreen.main.bounds
        settingsBackground.clipsToBounds = true
        settingsBackground.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(settingsBackground)

        settingsMenu.axis = .vertical
        settingsMenu.spacing = 16
        settingsMenu.distribution = .fillEqually
        settingsMenu.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(settingsMenu)
        
        for index in 0...1 {
            itemStackView = UIStackView()
            itemStackView.tag = index
            itemStackView.backgroundColor = .white.withAlphaComponent(0.75)
            settingsMenu.addArrangedSubview(itemStackView)
        }
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if touch?.view == self.settingsBackground { /*off settingsBackground,settingsMenu*/ }
        if touch?.view == self.settingsMenu.subviews[0] { /*action*/ }
        if touch?.view == self.settingsMenu.subviews[1] { /*action*/ }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if touch?.view == self.settingsMenu.subviews[0] { /*action*/ }
        if touch?.view == self.settingsMenu.subviews[1] { /*action*/ }
    }
}

But when I click outside button area my app crashed:

Thread 1: "***-[_NSArrayO objectAtIndex:]: index 0 beyond bounds for empty array"

problem in this lines (I remove this all works fine):

if touch?.view == self.settingsMenu.subviews[0] { /*action*/ }
if touch?.view == self.settingsMenu.subviews[1] { /*action*/ }

Maybe I get error if I click outside the button because settingsMenu.subviews[0] and settingsMenu.subviews[1] doesn't exist. But how to fix the problem? How not call subviews if these not exist?

I have a UIButton with an action. If I click the button, I see a stack view with two views. And if I click on some view on stack view, I perform a new action of some kind. But for views in stack view, I don't use an action button. I'm using TouchesBegan to detect a touch and perform an action. I use next code:

class SomeC: UIViewController {
    
    lazy var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 32
        stackView.alignment = .fill
        stackView.distribution = .equalSpacing
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()

    var button: UIButton = {
        let button = UIButton()
        button.layer.shadowColor = UIColor.black.withAlphaComponent(0.5).cgColor
        button.layer.shadowOffset = CGSize.zero
        button.layer.shadowOpacity = 0.2
        button.layer.shadowRadius = 3
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    let settingsMenu = UIStackView()
    var itemStackView = UIStackView()
    let settingsBackground = UIVisualEffectView(effect: UIBlurEffect(style: .light))
        
    override func viewDidLoad() {
        super.viewDidLoad()
        
        button = UIButton()
        button.backgroundColor = .red.withAlphaComponent(0.25)
        button.widthAnchor.constraint(equalToConstant: 56).isActive = true
        button.heightAnchor.constraint(equalToConstant: 56).isActive = true
        button.addTarget(self, action: #selector(self.rightButtonAction), for: .touchUpInside)
        stackView.addArrangedSubview(button)
        stackView.widthAnchor.constraint(equalToConstant: ((72+32) * 2) - 32).isActive = true
    }
    
    @objc func rightButtonAction(sender: UIButton!) {
        
        settingsBackground.frame = UIScreen.main.bounds
        settingsBackground.clipsToBounds = true
        settingsBackground.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(settingsBackground)

        settingsMenu.axis = .vertical
        settingsMenu.spacing = 16
        settingsMenu.distribution = .fillEqually
        settingsMenu.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(settingsMenu)
        
        for index in 0...1 {
            itemStackView = UIStackView()
            itemStackView.tag = index
            itemStackView.backgroundColor = .white.withAlphaComponent(0.75)
            settingsMenu.addArrangedSubview(itemStackView)
        }
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if touch?.view == self.settingsBackground { /*off settingsBackground,settingsMenu*/ }
        if touch?.view == self.settingsMenu.subviews[0] { /*action*/ }
        if touch?.view == self.settingsMenu.subviews[1] { /*action*/ }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if touch?.view == self.settingsMenu.subviews[0] { /*action*/ }
        if touch?.view == self.settingsMenu.subviews[1] { /*action*/ }
    }
}

But when I click outside button area my app crashed:

Thread 1: "***-[_NSArrayO objectAtIndex:]: index 0 beyond bounds for empty array"

problem in this lines (I remove this all works fine):

if touch?.view == self.settingsMenu.subviews[0] { /*action*/ }
if touch?.view == self.settingsMenu.subviews[1] { /*action*/ }

Maybe I get error if I click outside the button because settingsMenu.subviews[0] and settingsMenu.subviews[1] doesn't exist. But how to fix the problem? How not call subviews if these not exist?

Share Improve this question asked 2 days ago Alex SmithAlex Smith 131 silver badge9 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

It's expected behavior because when you're tapping outside of the red button, touchesBegan and touchesEnded are also getting called. However, rightButtonAction function are not executed, thus settingsMenu.subViews is empty.

print(settingsMenu.subViews.isEmpty)
//true

By calling settingsMenu.subviews[0], you're trying to get the first subview of the settingsMenu directly by its index. And since settingsMenu has no subview at all, an exception is thrown.

Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty array'

To prevent the exception, you should check whenever a view's subView array contains an element or not:

extension Array {
    subscript(safe index: Int) -> Element? {
        guard count > index, index >= 0 else { return nil }
        return self[index]
    }
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    ...
    if touch?.view == self.settingsMenu.subviews[safe: 0] { } 
    if touch?.view == self.settingsMenu.subviews[safe: 1] { }

}

Notice: Within the for loop, you will create two new stackViews, but itemStackView always points to the 2nd one. You may want to update it.

本文标签: swiftApp crashed after using stack view subviewsStack Overflow