Search code examples
iosswiftuikitnslayoutconstraint

Swift: UIButton not clickable, NSLayoutConstraint heightAnchor issues


I am Building a simple hangman game. I have built a simple keyboard out of UIButtons. The keyboard is inside a subview and each row is a seperate subview.

The Buttons are not clickable, I can get the top row working but then the other rows get pushed apart.

I have tried setting the NSLayoutConstraint height anchors and it will push the UIButtons out of their corresponding Views.

class ViewController: UIViewController {

// letterGuess
// usedLetters
// score/lives

var scoreLabel: UILabel!
var answerLabel: UILabel!
var characterButtons = [UIButton]()

var score = 0 {
    didSet {
        scoreLabel.text = "Score: \(score)"
    }
}
override func loadView() {
    view = UIView()
    view.backgroundColor = .white
    
    scoreLabel = UILabel()
    scoreLabel.translatesAutoresizingMaskIntoConstraints = false
    scoreLabel.textAlignment = .right
    scoreLabel.font = UIFont.systemFont(ofSize: 24)
    scoreLabel.text = "Score: 0"
    view.addSubview(scoreLabel)
    
    answerLabel = UILabel()
    answerLabel.translatesAutoresizingMaskIntoConstraints = false
    answerLabel.font = UIFont.systemFont(ofSize: 24)
    answerLabel.text = "ANSWER"
    answerLabel.numberOfLines = 1
    answerLabel.textAlignment = .center
    answerLabel.setContentHuggingPriority(UILayoutPriority(1), for: .vertical)
    view.addSubview(answerLabel)
    
    let buttonsView = UIView()
    buttonsView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(buttonsView)
    
    let row1View = UIView()
    row1View.translatesAutoresizingMaskIntoConstraints = false
    buttonsView.addSubview(row1View)
    
    let row2View = UIView()
    row2View.translatesAutoresizingMaskIntoConstraints = false
    row2View.setContentHuggingPriority(.defaultLow, for: .vertical)
    buttonsView.addSubview(row2View)
    
    let row3View = UIView()
    row3View.translatesAutoresizingMaskIntoConstraints = false
    buttonsView.addSubview(row3View)

    
    NSLayoutConstraint.activate([
        scoreLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
        scoreLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: 0),
        
        answerLabel.topAnchor.constraint(equalTo: scoreLabel.bottomAnchor, constant: 25),
        answerLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        
        buttonsView.widthAnchor.constraint(equalToConstant: 1000),
        buttonsView.heightAnchor.constraint(equalToConstant: 300),
        buttonsView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        buttonsView.topAnchor.constraint(equalTo: answerLabel.bottomAnchor, constant: 20),
        buttonsView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: -20),
        
        row1View.leftAnchor.constraint(equalTo: buttonsView.leftAnchor),
        row1View.topAnchor.constraint(equalTo: buttonsView.topAnchor),
        row1View.widthAnchor.constraint(equalTo: buttonsView.widthAnchor),
        //row1View.heightAnchor.constraint(equalTo: buttonsView.heightAnchor, multiplier: 0.333, constant: 0),
        //row1View.heightAnchor.constraint(equalToConstant: 100),
        
        
        row2View.leftAnchor.constraint(equalTo: buttonsView.leftAnchor),
        row2View.topAnchor.constraint(equalTo: row1View.bottomAnchor),
        row2View.widthAnchor.constraint(equalTo: buttonsView.widthAnchor),
        //row2View.heightAnchor.constraint(equalTo: buttonsView.heightAnchor, multiplier: 0.333, constant: 0),
        //row2View.heightAnchor.constraint(equalToConstant: 100),
        
        row3View.leftAnchor.constraint(equalTo: buttonsView.leftAnchor),
        row3View.topAnchor.constraint(equalTo: row2View.bottomAnchor),
        row3View.widthAnchor.constraint(equalTo: buttonsView.widthAnchor),
        //row3View.heightAnchor.constraint(equalTo: buttonsView.heightAnchor, multiplier: 0.333, constant: 0),
        //row3View.heightAnchor.constraint(equalToConstant: 100),
       
        
        
    
    ])
    
    let width = 100
    let height = 100
    var i = 10
    
    for row in 0..<3 {
        print(row)
        switch row {
        case 0:
            i = 10
        case 1:
            i = 9
        case 2:
            i = 7
        default:
            return
        }
        for col in 0..<i {
            let characterButton = UIButton(type: .system)
            characterButton.titleLabel?.font = UIFont.systemFont(ofSize: 36)
            
            characterButton.layer.borderWidth = 1
            characterButton.layer.borderColor = UIColor.lightGray.cgColor
            characterButton.layer.backgroundColor = UIColor.white.cgColor
            characterButton.setTitle("#", for: .normal)

            let frame = CGRect(x: col * width, y: row * height, width: width, height: height)
            characterButton.frame = frame
            
            switch row {
            case 0:
                print(row)
                print("row 1")
                row1View.addSubview(characterButton)
            case 1:
                print(row)
                print("row 2")
                row2View.addSubview(characterButton)
            case 2:
                print(row)
                print("row 3")
                row3View.addSubview(characterButton)
            default:
                print("defualt")
                return
            }
            
            characterButtons.append(characterButton)
            
            
            characterButton.addTarget(self, action: #selector(characterTapped), for: .touchUpInside)
        }
    }
    
   buttonsView.backgroundColor = .purple
    row1View.backgroundColor = .red
    row2View.backgroundColor = .yellow
    row3View.backgroundColor = .green

}

Solution

  • You have a bug in the place where you calculate the frame of the buttons to be placed in each row.

    // your code
    let frame = CGRect(x: col * width, y: row * height, width: width, height: height)
    

    You don't need to change the y position of the button. It can just be 0 here since each row is within its own view.

    // corrected code
    let frame = CGRect(x: col * width, y: 0, width: width, height: height)
    

    You should also set a height constraint for each row you have. All the buttons which were added were out of bounds of the parent view. This becomes visible when rowView.clipsToBounds = true is set. That's why your buttons weren't working.

    I believe there is an issue in the loop as well running more than it needs to, but I haven't checked it.

    Solution to your issue: I tried fixing your sample code and it works. Check here

    Also try using a collection or stack view to solve the problem when you find time.