简体   繁体   English

Swift:UIButton 不可点击,NSLayoutConstraint heightAnchor 问题

[英]Swift: UIButton not clickable, NSLayoutConstraint heightAnchor issues

I am Building a simple hangman game.我正在构建一个简单的刽子手游戏。 I have built a simple keyboard out of UIButtons.我用 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.我已经尝试设置 NSLayoutConstraint 高度锚点,它会将 UIButtons 推出其相应的视图。

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

}

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.您不需要更改按钮的y position。 It can just be 0 here since each row is within its own view.它在这里只能是0 ,因为每一行都在它自己的视图中。

// 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.这在设置rowView.clipsToBounds = true时变得可见。 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.还可以在找到时间时尝试使用集合或堆栈视图来解决问题。

There are many benefits to using UIStackView s... primarily the fact that they can be used to automatically arrange and size the subviews, making it easy to adapt your layout to different devices and screen-sizes.使用UIStackView s 有很多好处……主要是因为它们可以用来自动排列和调整子视图的大小,从而使您的布局很容易适应不同的设备和屏幕尺寸。

Here is an example of your code, modified to use stack views to hold the buttons (I also added a custom CharacterButton class that will automatically set the button label's font size in a user-defined range / proportion):这是您的代码示例,修改为使用堆栈视图来保存按钮(我还添加了一个自定义CharacterButton class,它将自动在用户定义的范围/比例中设置按钮标签的字体大小):

class CharacterButton: UIButton {
    
    // this will automatically set the font size for the button
    // if the button width >= 100, font size will be maxSize
    // if it's less than 100, font size will be proportional
    // with a minimum font size of 20
    
    // these are declared as "var" so they can be changed at run-time if desired
    var maxSize: CGFloat = 36
    var minSize: CGFloat = 20
    var forWidth: CGFloat = 100
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
        
        // set title colors
        setTitleColor(.blue, for: .normal)
        setTitleColor(.lightGray, for: .highlighted)
        
        // maybe change title color when disabled?
        //setTitleColor(.darkGray, for: .disabled)
        
        // give it a border
        layer.borderWidth = 1
        layer.borderColor = UIColor.lightGray.cgColor
        layer.backgroundColor = UIColor.white.cgColor
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        let fSize = min(max(minSize, bounds.size.width / forWidth * maxSize), maxSize)
        titleLabel?.font = UIFont.systemFont(ofSize: fSize)
    }
    
}

class HangManViewController: UIViewController {
    
    // letterGuess
    // usedLetters
    // score/lives
    
    var scoreLabel: UILabel!
    var answerLabel: UILabel!
    var characterButtons = [UIButton]()
    
    var score = 0 {
        didSet {
            scoreLabel.text = "Score: \(score)"
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        
        // create and add the Score label
        scoreLabel = UILabel()
        scoreLabel.translatesAutoresizingMaskIntoConstraints = false
        scoreLabel.textAlignment = .right
        scoreLabel.font = UIFont.systemFont(ofSize: 24)
        scoreLabel.text = "Score: 0"
        view.addSubview(scoreLabel)
        
        // create and add the Answer label
        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)
        
        // create a view to hold the buttons
        let buttonsView = UIView()
        buttonsView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(buttonsView)
        
        // create a vertical "outer" stack view
        let outerStack = UIStackView()
        outerStack.axis = .vertical
        outerStack.distribution = .fillEqually

        // add it to the buttons holder view
        outerStack.translatesAutoresizingMaskIntoConstraints = false
        buttonsView.addSubview(outerStack)
        
        // create three "row" stack views
        let row1Stack = UIStackView()
        row1Stack.axis = .horizontal
        row1Stack.distribution = .fillEqually
        
        let row2Stack = UIStackView()
        row2Stack.axis = .horizontal
        row2Stack.distribution = .fillEqually
        
        let row3Stack = UIStackView()
        row3Stack.axis = .horizontal
        row3Stack.distribution = .fillEqually
        
        // add the 3 "row" stack views to the "outer" stack view
        [row1Stack, row2Stack, row3Stack].forEach {
            outerStack.addArrangedSubview($0)
        }
        
        let g = view.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            
            // constrain Score label to top-right
            scoreLabel.topAnchor.constraint(equalTo: g.topAnchor),
            scoreLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0),
            
            // constrain Answer label centered horizontally
            answerLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            // and 4-pts above the grid of buttons
            answerLabel.bottomAnchor.constraint(equalTo: buttonsView.topAnchor, constant: -4),

            // constrain buttons holder view Leading / Trailing
            buttonsView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            buttonsView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),

            // constrain buttons holder view Bottom with 20-pts "padding"
            buttonsView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20),
            
            // constrain all 4 sides of "outer" stack view to buttons holder view
            outerStack.topAnchor.constraint(equalTo: buttonsView.topAnchor),
            outerStack.leadingAnchor.constraint(equalTo: buttonsView.leadingAnchor),
            outerStack.trailingAnchor.constraint(equalTo: buttonsView.trailingAnchor),
            outerStack.bottomAnchor.constraint(equalTo: buttonsView.bottomAnchor),
            
        ])
        
        let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".map { String($0) }

        var j = 0
        
        // loop through the 3 "rows" adding 10, 9 and 7 buttons
        
        for (thisRow, numButtonsInThisRow) in zip([row1Stack, row2Stack, row3Stack], [10, 9, 7]) {

            for i in 0..<10 {
                
                if i < numButtonsInThisRow {
                    
                    // create a button
                    let characterButton = CharacterButton()
                    
                    // set its title
                    characterButton.setTitle(letters[j], for: .normal)

                    // maybe set button title to "#" when disabled?
                    //characterButton.setTitle("#", for: .disabled)

                    // give button a touchUp target
                    characterButton.addTarget(self, action: #selector(self.characterTapped(_:)), for: .touchUpInside)
                    
                    // add button to current row stack view
                    thisRow.addArrangedSubview(characterButton)
                    
                    // add button to characterButtons Array
                    characterButtons.append(characterButton)

                    // increment j
                    j += 1
                    
                } else {
                    
                    // we're past the number of character buttons that should be on this row
                    // so "fill it out" with bordered views
                    let v = UIView()
                    v.layer.borderWidth = 1
                    v.layer.borderColor = UIColor.lightGray.cgColor
                    v.layer.backgroundColor = UIColor.white.cgColor
                    thisRow.addArrangedSubview(v)
                    
                }
            }

        }
        
        // we want square buttons, so
        //  we only need to set the first button to have a 1:1 height:width ratio
        //  the stack views' fillEqually distribution will handle the rest
        if let v = characterButtons.first {
            v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
        }

        // just so we can see the frame of the answer label
        answerLabel.backgroundColor = .green
        
    }
    
    @objc func characterTapped(_ sender: UIButton) {
        // character button tapped
        
        // get its title
        let s = sender.currentTitle ?? "no title"
        
        // do we want to disable it?
        //sender.isEnabled = false

        // for now, print the Letter to the debug console
        print("Button \(s) was tapped!")

    }
    
}

Results:结果:

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM