Search code examples
iosswiftnslayoutconstraint

How do I update constraints after changing UIButton's image


I have a mini game in my project where you have to guess a flag of a country, I don't use Interface Builder, all UI is written in code. When user taps one of the flags I load new set of countries to guess, set their flags to buttons and since flags are different in sizes from previous ones I need to adjust UIButton constraints so I call my function setupConstraints() which looks like that:

 func setupConstraints() {
    NSLayoutConstraint.activate([
        countryNameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
        countryNameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        countryNameLabel.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -20),
        countryNameLabel.heightAnchor.constraint(equalToConstant: 30),
        
        firstCountryButton.topAnchor.constraint(equalTo: countryNameLabel.bottomAnchor, constant: 35),
        firstCountryButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        firstCountryButton.heightAnchor.constraint(equalToConstant: 120),
        firstCountryButton.widthAnchor.constraint(equalTo: firstCountryButton.heightAnchor, multiplier: flagImages[0].getAspectRatio()),
        
        secondCountryButton.topAnchor.constraint(equalTo: firstCountryButton.bottomAnchor, constant: 25),
        secondCountryButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        secondCountryButton.heightAnchor.constraint(equalToConstant: 120),
        secondCountryButton.widthAnchor.constraint(equalTo: secondCountryButton.heightAnchor, multiplier: flagImages[1].getAspectRatio()),
        
        thirdCountryButton.topAnchor.constraint(equalTo: secondCountryButton.bottomAnchor, constant: 25),
        thirdCountryButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        thirdCountryButton.heightAnchor.constraint(equalToConstant: 120),
        thirdCountryButton.widthAnchor.constraint(equalTo: thirdCountryButton.heightAnchor, multiplier: flagImages[2].getAspectRatio()),
    ])
}

This function works fine when I initially setup my View Controller but when I call it to update constraints I get in log following:

    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x280d92940 UIButton:0x102315180.height == 120   (active)>",
    "<NSLayoutConstraint:0x280d92990 UIButton:0x102315180.width == 1.5*UIButton:0x102315180.height   (active)>",
    "<NSLayoutConstraint:0x280db2b70 UIButton:0x102315180.width == 2*UIButton:0x102315180.height   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x280db2b70 UIButton:0x102315180.width == 2*UIButton:0x102315180.height   (active)>

I see that it's trying to set width twice but I don't know why. I'm not even sure if my approach to call this function every time to setup constraints is the right one.


Solution

  • Here's one way to handle it:

    Create a struct ButtonSizeConstraints:

    struct ButtonSizeConstraints {
       let widthConStraint: NSLayoutConstraint
       let heightConstraint: NSLayoutConstraint
    }
    

    Give your view an array of ButtonSizeConstraints, once for each button. (You could also have separate variables firstButtonSizeConstraints, secondButtonSizeConstraints, etc, but by making it an array you can loop through it.)

    In your setupConstraints function, put those constraints into local vars, add them to your array of ButtonSizeConstraints, and activate them outside of the code you posted.

    Then, when you load a new image into one or more of your buttons, fetch that button's ButtonSizeConstraints, update that constraint's constant value, and call the parent view's layoutIfNeeded() method.