I'm working on an iOS app which is a basic Pokedex, which will hopefully teach me the basics of working with UIKit (not using Storyboards for this project). When I run my app everything lays out fine. However when I scroll down there are some constraints that get messed up causing buttons to appear off screen. I can't figure out what would be causing this to happen. I think that some constraints are being applied or are not being applied when a cell is reused but I can't find an obvious error in my constraint code.
func set(pokemon: Pokemon) {
name.text = pokemon.name
var spriteName: String?
if let pokemonForm = pokemon.form {
addSubview(form)
form.text = pokemonForm
setNameLabelConstraints(hasForm: true)
setFormLabelConstraints()
switch pokemonForm {
case "Mega":
spriteName = String(pokemon.speciesID) + "-mega-sprite"
case "Mega X":
spriteName = String(pokemon.speciesID) + "-mega-x-sprite"
case "Mega Y":
spriteName = String(pokemon.speciesID) + "-mega-y-sprite"
default:
spriteName = String(pokemon.speciesID) + "-sprite"
}
} else {
spriteName = String(pokemon.id) + "-sprite"
form.removeFromSuperview()
setNameLabelConstraints(hasForm: false)
}
sprite.image = UIImage(named: spriteName!)
type1.setTitle(pokemon.type1, for: .normal)
styleTypeFilterButton(button: type1)
type1.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControl.Event.touchUpInside)
if let secondType = pokemon.type2 {
addSubview(type2)
type2.setTitle(secondType, for: .normal)
styleTypeFilterButton(button: type2)
setType2ButtonConstraints()
setType1ButtonConstraints(twoButtons: true)
type2.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControl.Event.touchUpInside)
} else {
type2.removeFromSuperview()
setType1ButtonConstraints(twoButtons: false)
}
}
func setNameLabelConstraints(hasForm: Bool) {
name.translatesAutoresizingMaskIntoConstraints = false
name.leadingAnchor.constraint(equalTo: sprite.trailingAnchor, constant: 20).isActive = true
if hasForm == false {
name.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
} else {
name.heightAnchor.constraint(equalToConstant: 40).isActive = true
name.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = false
}
}
func setType1ButtonConstraints(twoButtons: Bool) {
//type1.translatesAutoresizingMaskIntoConstraints = false
if twoButtons == true {
type1.trailingAnchor.constraint(equalTo: type2.leadingAnchor, constant: -15).isActive = true
} else {
type1.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25).isActive = true
}
}
func setType2ButtonConstraints() {
//type2.translatesAutoresizingMaskIntoConstraints = false
type2.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25).isActive = true
}
func setFormLabelConstraints() {
form.translatesAutoresizingMaskIntoConstraints = false
form.leadingAnchor.constraint(equalTo: sprite.trailingAnchor, constant: 20).isActive = true
form.heightAnchor.constraint(equalToConstant: 40).isActive = true
form.topAnchor.constraint(equalTo: name.bottomAnchor, constant: -20).isActive = true
form.font = form.font.withSize(14)
}
func styleTypeFilterButton(button: UIButton) {
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: 25).isActive = true
button.widthAnchor.constraint(equalToConstant: 65).isActive = true
button.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
button.layer.cornerRadius = 12
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = button.titleLabel?.font.withSize(14)
switch button.titleLabel?.text {
case "Normal":
button.backgroundColor = UIColor(hexValue: "a8a878")
case "Fighting":
button.backgroundColor = UIColor(hexValue: "c03028")
case "Flying":
button.backgroundColor = UIColor(hexValue: "a890f0")
case "Poison":
button.backgroundColor = UIColor(hexValue: "a040a0")
case "Ground":
button.backgroundColor = UIColor(hexValue: "e0c068")
case "Rock":
button.backgroundColor = UIColor(hexValue: "b8a038")
case "Bug":
button.backgroundColor = UIColor(hexValue: "a8b820")
case "Ghost":
button.backgroundColor = UIColor(hexValue: "705898")
case "Steel":
button.backgroundColor = UIColor(hexValue: "b8b8d0")
case "Fire":
button.backgroundColor = UIColor(hexValue: "f08030")
case "Water":
button.backgroundColor = UIColor(hexValue: "6890f0")
case "Grass":
button.backgroundColor = UIColor(hexValue: "78C850")
case "Electric":
button.backgroundColor = UIColor(hexValue: "f8d030")
case "Psychic":
button.backgroundColor = UIColor(hexValue: "f85888")
case "Ice":
button.backgroundColor = UIColor(hexValue: "98d8d8")
case "Dragon":
button.backgroundColor = UIColor(hexValue: "7038f8")
case "Dark":
button.backgroundColor = UIColor(hexValue: "705848")
case "Fairy":
button.backgroundColor = UIColor(hexValue: "ee99ac")
case "???":
button.backgroundColor = UIColor(hexValue: "68a090")
case "Shadow":
button.backgroundColor = UIColor(hexValue: "705848")
default:
button.backgroundColor = UIColor(hexValue: "a8a878")
}
}
If someone could explain why it lays out correctly when the app first runs, but gets messed up after the cells get reused I would greatly appreciate it! I've never worked with autolayout before, so I might also have in error in my autolayout code but I'm not sure if anything is wrong. Thank you in advance!!


I'm making some guesses about how your code works, but in short the problem is that you're not removing old layout constraints when you recycle your cells.
Let's say a cell gets created and for the first entry it has to display, you call
setType1ButtonConstraints(twoButtons: false).type1is now pinned to the trailing edge of the cell. Then that cell is recycled and for the new entry it has to display you callsetType2ButtonConstraints()and thensetType1ButtonConstraints(twoButtons: true). Your code will pintype2to the trailing edge of the cell, and then pintype1to be beforetype2, but you never got rid of the constraint that was made from the first entry. You now have a set of unsatisfiable constraints (there will be a warning about this in the console) and the system has to break one.To solve this you should keep references to these constraints and remove them in
prepareForReuse(). You could also use a horizontalUIStackView.