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】:使用UIStackView
s 有很多好处...主要是因为它们可用于自动排列和调整子视图的大小,从而使您的布局轻松适应不同的设备和屏幕尺寸。
这是您的代码示例,已修改为使用堆栈视图来保存按钮(我还添加了一个自定义 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 问题的主要内容,如果未能解决你的问题,请参考以下文章