Search code examples
swiftautolayoutmodelandscape-portrait

swift how to set autolayout programmatically


I have buttons inside a view which in potrait mode I want like this -

enter image description here

which is achieved by the following code -

//original potrait mode/////

 import UIKit
 class PotraitViewController: UIViewController {
 override func viewDidLoad() {
 let buttonred = UIButton()
 buttonred.backgroundColor = UIColor.red
 let buttonblue = UIButton()
 buttonblue.backgroundColor = UIColor.blue
 let landscapesmallview = UIView()
 view.addSubview(landscapesmallview)
 landscapesmallview.addSubview(buttonred)
 landscapesmallview.addSubview(buttonblue)
 buttonred.translatesAutoresizingMaskIntoConstraints = false
 buttonblue.translatesAutoresizingMaskIntoConstraints = false
 NSLayoutConstraint.activate([

    buttonred.topAnchor.constraint(equalTo: view.topAnchor,constant: 200),
    buttonred.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    buttonred.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant:-20),
    buttonred.widthAnchor.constraint(equalToConstant: 50),
    //-------
    buttonblue.topAnchor.constraint(equalTo: buttonred.bottomAnchor,constant: 40),
    buttonblue.leadingAnchor.constraint(equalTo: buttonred.leadingAnchor),
    buttonblue.trailingAnchor.constraint(equalTo:buttonred.trailingAnchor),
    buttonblue.widthAnchor.constraint(equalTo: buttonred.widthAnchor)

      ])
    }
  }

and in landscape mode I want like this -

enter image description here

which is achieved by the following code -

 // original lanscape mode/////

import UIKit
class LandscapeViewController: UIViewController {
override func viewDidLoad() {
let buttonred = UIButton()
buttonred.backgroundColor = UIColor.red
let buttonblue = UIButton()
buttonblue.backgroundColor = UIColor.blue
view.addSubview(buttonred)
view.addSubview(buttonblue)
buttonred.translatesAutoresizingMaskIntoConstraints = false
buttonblue.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([

    buttonred.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    buttonred.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant:40),
    buttonred.trailingAnchor.constraint(equalTo: view.centerXAnchor,constant:-20),
    buttonred.widthAnchor.constraint(equalToConstant: 50),

    //-------
    buttonblue.centerYAnchor.constraint(equalTo: buttonred.centerYAnchor),
    buttonblue.leadingAnchor.constraint(equalTo: view.centerXAnchor,constant:40),
    buttonblue.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant:-20),
    buttonblue.widthAnchor.constraint(equalTo:buttonred.widthAnchor)

       ])
     }
  }

So, I tried the following code to achieve by screen rotation i.e. two different layouts in potrait and landscape views programmatically with the help of the following code:-

import UIKit

class NewViewController: UIViewController {
override func viewDidLoad() {
let buttonredlandscape = UIButton()
buttonredlandscape.backgroundColor = UIColor.red
let buttonbluelandscape = UIButton()
buttonbluelandscape.backgroundColor = UIColor.blue
let buttonredportrait = UIButton()
buttonredportrait.backgroundColor = UIColor.red
let buttonblueportrait = UIButton()
buttonblueportrait.backgroundColor = UIColor.blue

let landscapesmallview = UIView()
let portraitsmallview = UIView()
landscapesmallview.backgroundColor = UIColor.gray
portraitsmallview.backgroundColor = UIColor.purple
landscapesmallview.frame = view.frame
portraitsmallview.frame = view.frame
view.addSubview(landscapesmallview)
view.addSubview(portraitsmallview)


landscapesmallview.addSubview(buttonredlandscape)
landscapesmallview.addSubview(buttonbluelandscape)
portraitsmallview.addSubview(buttonredportrait)
portraitsmallview.addSubview(buttonblueportrait)
buttonredlandscape.translatesAutoresizingMaskIntoConstraints = false
buttonbluelandscape.translatesAutoresizingMaskIntoConstraints = false
buttonredportrait.translatesAutoresizingMaskIntoConstraints = false
buttonblueportrait.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([

     buttonredlandscape.centerYAnchor.constraint(equalTo:landscapesmallview.centerYAnchor),
      buttonredlandscape.topAnchor.constraint(equalTo:landscapesmallview.topAnchor,constant:40),
    buttonredlandscape.trailingAnchor.constraint(equalTo:   landscapesmallview.centerXAnchor,constant:-20),
    buttonredlandscape.heightAnchor.constraint(equalTo:   landscapesmallview.heightAnchor,constant:50),

    buttonbluelandscape.centerYAnchor.constraint(equalTo:buttonredlandscape.centerYAnchor),
    buttonbluelandscape.leadingAnchor.constraint(equalTo: landscapesmallview.centerXAnchor,constant:40),
    buttonbluelandscape.trailingAnchor.constraint(equalTo: landscapesmallview.trailingAnchor,constant:-20),
    buttonbluelandscape.heightAnchor.constraint(equalTo: buttonredlandscape.heightAnchor),


    buttonredportrait.topAnchor.constraint(equalTo: portraitsmallview.topAnchor,constant: 200),
    buttonredportrait.centerXAnchor.constraint(equalTo: portraitsmallview.centerXAnchor),
    buttonredportrait.trailingAnchor.constraint(equalTo: portraitsmallview.trailingAnchor,constant:-20),
    buttonredportrait.widthAnchor.constraint(equalTo: buttonredportrait.widthAnchor),

    buttonblueportrait.topAnchor.constraint(equalTo: buttonredportrait.bottomAnchor,constant: 40),
    buttonblueportrait.leadingAnchor.constraint(equalTo: buttonredportrait.leadingAnchor),
    buttonblueportrait.trailingAnchor.constraint(equalTo:buttonredportrait.trailingAnchor),
    buttonblueportrait.widthAnchor.constraint(equalTo: buttonredportrait.widthAnchor)


     ])

//-------



func viewWillTransition(to size: CGSize, with coordinator:    ) {
    if UIDevice.current.orientation.isLandscape {
        landscapesmallview.translatesAutoresizingMaskIntoConstraints = false
        portraitsmallview.translatesAutoresizingMaskIntoConstraints = true
     } else if UIDevice.current.orientation.isPortrait {
        portraitsmallview.translatesAutoresizingMaskIntoConstraints = false
        landscapesmallview.translatesAutoresizingMaskIntoConstraints = true

        }
      }
    }
 }

which in potrait mode shows -

enter image description here

and which in landscape mode shows -

enter image description here

How to achieve what I want programmatically i.e. topmost 2 buttons to rearrange themselves programmatically every-time the user rotates the device. Its not just the buttons. It can be labels, images, collectionview etc. or just anything. The upshot is that I want to achieve two different layouts in landscape and portrait modes programmatically irrespective of the device.

Points to be noted :-

i) I have tried used NSLayoutAnchor with "NSLayoutConstraint.activate" because apple recommends it, but if the code can be made shorter(and faster) with some other method like visual format etc. I'm okay with that as well/

ii) If possible, I do not want to use stackview or containerview, because there can be many more types of labels, buttons etc, but if there is no other way, then I will use it.

iii) Is my code DRY principle compliant ?


Solution

  • There are various ways to do this. One approach:

    • declare two "constraint" arrays
      • one to hold the "narrow view" constraints
      • one to hold the "wide view" constraints
    • activate / deactivate the constraints as needed

    Here is a complete example:

    class ChangeLayoutViewController: UIViewController {
    
        let redButton: UIButton = {
            let v = UIButton()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .red
            v.setTitle("Red Button", for: [])
            return v
        }()
    
        let blueButton: UIButton = {
            let v = UIButton()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .blue
            v.setTitle("Blue Button", for: [])
            return v
        }()
    
        var narrowConstraints: [NSLayoutConstraint] = [NSLayoutConstraint]()
        var wideConstraints: [NSLayoutConstraint] = [NSLayoutConstraint]()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.addSubview(redButton)
            view.addSubview(blueButton)
    
            let g = view.safeAreaLayoutGuide
    
            var c: NSLayoutConstraint
    
            // MARK: - narrow orientation
    
            // constrain redButton above blueButton
    
            // constrain redButton leading and trailing to safe-area (with 8-pts on each side)
            c = redButton.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0)
            narrowConstraints.append(c)
            c = redButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0)
            narrowConstraints.append(c)
    
            // constrain blueButton leading and trailing to safe-area (with 8-pts on each side)
            c = blueButton.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0)
            narrowConstraints.append(c)
            c = blueButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0)
            narrowConstraints.append(c)
    
            // constrain redButton top 40-pts from safe-area top
            c = redButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0)
            narrowConstraints.append(c)
            // constrain blueButton top 20-pts from redButton bottom
            c = blueButton.topAnchor.constraint(equalTo: redButton.bottomAnchor, constant: 20.0)
            narrowConstraints.append(c)
    
    
            // MARK: - wide orientation
    
            // constrain redButton & blueButton side-by-side
            //  with equal widths and 8-pts between them
    
            // constrain redButton leading 8-pts from safe-area leading
            c = redButton.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0)
            wideConstraints.append(c)
            // constrain blueButton trailing 8-pts from safe-area trailing
            c = blueButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0)
            wideConstraints.append(c)
    
            // constrain blueButton leading 8-pts from redButton trailing
            c = blueButton.leadingAnchor.constraint(equalTo: redButton.trailingAnchor, constant: 8.0)
            wideConstraints.append(c)
    
            // constrain buttons to equal widths
            c = blueButton.widthAnchor.constraint(equalTo: redButton.widthAnchor)
            wideConstraints.append(c)
    
            // constrain both buttons centerY to safe-area centerY
            c = redButton.centerYAnchor.constraint(equalTo: g.centerYAnchor)
            wideConstraints.append(c)
            c = blueButton.centerYAnchor.constraint(equalTo: g.centerYAnchor)
            wideConstraints.append(c)
    
            // activate initial constraints based on view width:height ratio
            changeConstraints(view.frame.width > view.frame.height)
    
        }
    
        override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
            super.viewWillTransition(to: size, with: coordinator)
            // change active set of constraints based on view width:height ratio
            self.changeConstraints(size.width > size.height)
        }
    
        func changeConstraints(_ useWide: Bool) -> Void {
            if useWide {
                NSLayoutConstraint.deactivate(narrowConstraints)
                NSLayoutConstraint.activate(wideConstraints)
            } else {
                NSLayoutConstraint.deactivate(wideConstraints)
                NSLayoutConstraint.activate(narrowConstraints)
            }
        }
    
    }
    

    Results:

    enter image description here

    enter image description here