Search code examples
iosautolayoutnslayoutconstraint

Constraint doesn't get deactivated


I am practicing auto-layout programmatically. I want to put a UIView centered in the controller whose width will be 4/5 in portrait mode but when it will go to the landscape mode, I need the height to be of 4/5 of the super view's height, rather than the width.

Something like -

enter image description here

So, I am deactivating and then activating the constrains required depending on the orientation but when I change rotation, it gives me conflict as if it didn't deactivated the ones, I specified to be deactivated. Here is my full code. As It is storyboard independent, one can just assign the view controller class to a view controlller and see the effect.


class MyViewController: UIViewController {
    
    var widthSizeClass = UIUserInterfaceSizeClass.unspecified
    
    var centeredView : UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.systemGreen
        return view
    }()
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.view.addSubview(centeredView)
        centeredView.translatesAutoresizingMaskIntoConstraints = false
    }
    
    override func viewWillLayoutSubviews(){
        super.viewWillLayoutSubviews()
        widthSizeClass = self.traitCollection.horizontalSizeClass
        addConstrainsToCenterView()
    }
    
    func addConstrainsToCenterView() {
        
        centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
        centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
       
        let compactWidthAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)
        let compactHeightAnchor = centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor)
       
        
        let regularHeightAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
        let regularWidthAnchor = centeredView.widthAnchor.constraint(equalTo: centeredView.heightAnchor)
        
        if widthSizeClass == .compact{
            NSLayoutConstraint.deactivate([regularWidthAnchor, regularHeightAnchor])
            NSLayoutConstraint.activate([compactWidthAnchor, compactHeightAnchor])
        }
        else{
            NSLayoutConstraint.deactivate([compactWidthAnchor, compactHeightAnchor])
            NSLayoutConstraint.activate([regularWidthAnchor, regularHeightAnchor])
        }
    }
}

Can anyone please help me detect my flaw.


Solution

  • Couple issues...

    1 - many iPhone models only have wC hR (portrait) and wC hC (landscape) size classes. So, if you're checking for the .horizontalSizeClass on those devices it will always be .compact. You likely want to be checking the .verticalSizeClass

    2 - the way you have your code, you are creating NEW constraints every time you call addConstrainsToCenterView(). You're not activating / deactivating existing constraints.

    Take a look at this:

    class MyViewController: UIViewController {
        
        var heightSizeClass = UIUserInterfaceSizeClass.unspecified
    
        var centeredView : UIView = {
            let view = UIView()
            view.backgroundColor = UIColor.systemGreen
            return view
        }()
    
        // constraints to activate/deactivate
        var compactAnchor: NSLayoutConstraint!
        var regularAnchor: NSLayoutConstraint!
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.view.addSubview(centeredView)
            centeredView.translatesAutoresizingMaskIntoConstraints = false
    
            // centeredView is Always centerX and centerY
            centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
            centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
            
            // for a square (1:1 ratio) view, it doesn't matter whether we set
            //  height == width
            // or
            //  width == height
            // so we can set this Active all the time
            centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor).isActive = true
            
            // create constraints to activate / deactivate
            
            // for regular height, set the width to 4/5ths the width of the view
            regularAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)
    
            // for compact height, set the height to 4/5ths the height of the view
            compactAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
            
        }
    
        override func viewWillLayoutSubviews() {
            super.viewWillLayoutSubviews()
            
            // use .verticalSizeClass
            heightSizeClass = self.traitCollection.verticalSizeClass
    
            updateCenterViewConstraints()
        }
        
        func updateCenterViewConstraints() {
            
            if heightSizeClass == .compact {
                // if height is compact
                regularAnchor.isActive = false
                compactAnchor.isActive = true
            }
            else{
                // height is regular
                compactAnchor.isActive = false
                regularAnchor.isActive = true
            }
        }
    }
    

    With that approach, we create two vars for the constraints we want to activate/deactivate:

        // constraints to activate/deactivate
        var compactAnchor: NSLayoutConstraint!
        var regularAnchor: NSLayoutConstraint!
    

    Then, in viewDidLoad(), we add centeredView to the view, set its "non-changing" constraints - centerX, centerY, aspect-ratio - and create the two activate/deactivate constraints.

    When we change the size class, we only have to deal with the two var constraints.