1

If you use a Storyboard, you can add auto layout constraints to a view using Interface Builder, and you can very easily add different constant values for each size class you want. When you run the app and switch between size classes, the UI will automatically update and reposition to respect the correct constant value for the new size class.

My question is how can you obtain that same behavior programmatically?

When you create an NSLayoutConstraint you can't set different values for different size classes. I'm therefore thinking it would be a much more manual process. You'd have to create the constraints with the correct value for the current size class in viewDidLoad for example, then you'd have to use willTransitionToTraitCollection or maybe updateConstraints and detect the new size class, then change the constants for all of the constraints that's appropriate for the new size class, then call layoutIfNeeded on the views that need to be repositioned. That would be a lot of code, made worse the more size classes you optimize for. Is there not an easier and/or more efficient way to obtain that behavior programmatically?

Note that this question isn't limited to auto layout constraints but rather any object that can have its properties' values change based on size class. For example, setting a UILabel's font for different size classes.

Jordan H
  • 52,571
  • 37
  • 201
  • 351

1 Answers1

1

Swift

You need to create different sets of NSLayoutConstraint.

Edit as per discussion below.

  • @Joey: You have to handle size classes decision in both viewDidLoad (or similar) and viewWillTransitionToSize.

  • Size class detection should be done inside the animateAlongsideTransition block, not before.

Refactored code:

override func viewDidLoad() {
    super.viewDidLoad()
    let narrow = self.view.traitCollection.horizontalSizeClass 
                 == UIUserInterfaceSizeClass.Compact
    self.useNarrowConstraints(narrow)
}


override func viewWillTransitionToSize(size: CGSize,
                                       withTransitionCoordinator
                                       coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition({
        (UIViewControllerTransitionCoordinatorContext) -> Void in
            let narrow = self.view.traitCollection.horizontalSizeClass 
                         == UIUserInterfaceSizeClass.Compact
            self.useNarrowConstraints(narrow)
        })
        { (UIViewControllerTransitionCoordinatorContext) -> Void in 
        }
}

Using Activation:

func useNarrowConstraints(narrow: Bool) {
    if narrow {
        NSLayoutConstraint.deactivateConstraints([self.fullWidthConstraint])
        NSLayoutConstraint.activateConstraints([self.halfWidthConstraint])
    } else {
        NSLayoutConstraint.deactivateConstraints([self.halfWidthConstraint])
        NSLayoutConstraint.activateConstraints([self.fullWidthConstraint])
    }
}

Further details here.

Using Substitution:

func useNarrowConstraints(narrow: Bool) {
    view.removeConstraint(constraint)
    
    if narrow {
        constraint = NSLayoutConstraint.constraintsWithVisualFormat("format", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary)
    } else {
        constraint = NSLayoutConstraint.constraintsWithVisualFormat("otherFormat", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary)
    }
    view.addConstraint(constraint)
}
Community
  • 1
  • 1
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
  • Where would this code go? You would surely have to remove all the constraints and re-add them with the right constants every time the size class changes. – Jordan H Jul 12 '15 at 06:08
  • Note that this method won't be called when the view is loaded. You'd need to duplicate that code in 'viewDidLoad'. Not as clean as I'd like, also it's terribly inefficient to remove constraints and readd them, but it'd work. – Jordan H Jul 12 '15 at 06:48
  • 1
    Correct on both accounts. Efficiency is a matter of perception ; better select a good algorithm rather than optimize a poor one. As such, my recommendation is to pick Storyboard over programatic solution if at all possible. **You can't beat 0 line of code**, and let the OS do the hard work. – SwiftArchitect Jul 12 '15 at 08:42