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

Posted

技术标签:

【中文标题】Swift:UIButton 不可点击,NSLayoutConstraint heightAnchor 问题【英文标题】:Swift: UIButton not clickable, NSLayoutConstraint heightAnchor issues 【发布时间】:2020-07-11 14:38:17 【问题描述】:

我正在构建一个简单的刽子手游戏。 我用 UIButtons 构建了一个简单的键盘。键盘在一个子视图中,每一行都是一个单独的子视图。

按钮不可点击,我可以让顶行工作,但其他行被推开。

我已尝试设置 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


【问题讨论】:

为什么你不使用StackView 【参考方案1】:

您在计算要放置在每一行中的按钮的框架的地方有一个错误。

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

您无需更改按钮的y 位置。因为每一行都在它自己的视图中,所以这里只能是 0

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

您还应该为您拥有的每一行设置一个高度限制。添加的所有按钮都超出了父视图的范围。这在设置rowView.clipsToBounds = true 时变得可见。这就是你的按钮不起作用的原因。

我认为循环中存在问题,并且运行超出了它的需要,但我没有检查它。

您的问题的解决方案:我尝试修复您的示例代码并且它可以工作。检查here

如果有时间,也可以尝试使用集合或堆栈视图来解决问题。

【讨论】:

感谢您的帮助,我对 swift 和 UIKit 很陌生,为什么集合或堆栈视图是更好的选择?【参考方案2】:

使用UIStackViews 有很多好处...主要是因为它们可用于自动排列和调整子视图的大小,从而使您的布局轻松适应不同的设备和屏幕尺寸。

这是您的代码示例,已修改为使用堆栈视图来保存按钮(我还添加了一个自定义 CharacterButton 类,它将自动在用户定义的范围/比例中设置按钮标签的字体大小):

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!")

    
    

结果:

【讨论】:

感谢 UIStavkViews 上的示例!我对这一行感到困惑:for (thisRow, numButtonsInThisRow) in zip([row1Stack, row2Stack, row3Stack], [10, 9, 7]) 什么是 zip 函数? @Ryyy23 - developer.apple.com/documentation/swift/1541125-zip

以上是关于Swift:UIButton 不可点击,NSLayoutConstraint heightAnchor 问题的主要内容,如果未能解决你的问题,请参考以下文章

UIButton 在第一次点击后不可点击

UIView中的UIButton不可点击

添加UITabbar时UIButton不可点击

UIButton 在 ScrollView 上不可点击

UITableViewCell 中的 UIButton 不可点击

UITextField 和 UIButton 不可点击