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
}
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.
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.
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):
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:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.