Programmatic UI

Hero image for Programmatic UI

Introduction

  • There are 3 types of user interface design approaches as mentioned in User Interface page.

  • At Nimble, the two most common ways to layout views programmatically are:
    • NSLayoutConstraint: is a native Swift API to layout views.
    • SnapKit: is a domain-specific language (DSL) to make Auto Layout easy on both iOS and macOS.
  • In general, the idea of layout views follows these main two steps:
    1. Creating constraints.
    2. Activating and deactivating constraints.

NSLayoutConstraint

  • The constraint-based layout system must satisfy the relationship between two user interface objects.

    There are few examples through this page, but their meaning is to layout the same view and the same constraints. It is about the layout of the containerView by leading and trailing constraints.

    Example:

      // 1 - Create constraints
      let containerViewConstraints = [
          containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
          containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0)
      ]
      // 2 - Activate constraints
      NSLayoutConstraint.activate(containerViewConstraints)
    
  • As the above example, the code has enough two steps to layout containerView:
    1. Create constraints by using NSLayoutAnchor.
    2. Activate created constraints by NSLayoutConstraint.activate.
  • Besides, another way (less preferred) is creating constraints directly from NSLayoutConstraint and adding constraints to the view instead of activating it.

    Example:

      // Create constraints by initializing NSLayoutConstraint
      let leadingConstraint = NSLayoutConstraint(
          item: contentView,
          attribute: .leading,
          relatedBy: .equal,
          toItem: view,
          attribute: .leading,
          multiplier: 1.0,
          constant: 20.0
      )
      let trailingConstraint = NSLayoutConstraint(
          item: contentView,
          attribute: .trailing,
          relatedBy: .equal,
          toItem: view,
          attribute: .trailing,
          multiplier: 1.0,
          constant: -20.0
      )
    
      // Activate constraints constraints by addConstraints
      containerView.addConstraints([
          leadingConstraint,
          trailingConstraint
      ])
    

    First of all, it is necessary to set the containerView.translatesAutoresizingMaskIntoConstraints to false. This is to prevent the view’s auto-resizing mask from being translated into Auto Layout constraints and affecting the constraints.

SnapKit

  • SnapKit is exceptionally straightforward to use, and it aims to create a syntax that is much more intuitive for Auto Layout constraints.

    Example:

      containerView.snp.makeConstraints {
          $0.leading.trailing.equalToSuperView().inset(20.0)
      }
    

    SnapKit doesn’t require to set translatesAutoresizingMaskIntoConstraints = false like NSLayoutConstraint

  • The two important factors the team should get used to are: Equality Constraints and the View Attributes.

    • Equality Constraints:

      • .equalTo equivalent to NSLayoutConstraint.Relation.equal

      • .lessThanOrEqualTo equivalent to NSLayoutConstraint.Relation.lessThanOrEqual

      • .greaterThanOrEqualTo equivalent to NSLayoutConstraint.Relation.greaterThanOrEqual

    • View Attributes table

      ViewAttribute NSLayoutAttribute
      view.snp.left NSLayoutConstraint.Attribute.left
      view.snp.right NSLayoutConstraint.Attribute.right
      view.snp.top NSLayoutConstraint.Attribute.top
      view.snp.bottom NSLayoutConstraint.Attribute.bottom
      view.snp.leading NSLayoutConstraint.Attribute.leading
      view.snp.trailing NSLayoutConstraint.Attribute.trailing
      view.snp.width NSLayoutConstraint.Attribute.width
      view.snp.height NSLayoutConstraint.Attribute.height
      view.snp.centerX NSLayoutConstraint.Attribute.centerX
      view.snp.centerY NSLayoutConstraint.Attribute.centerY
      view.snp.lastBaseline NSLayoutConstraint.Attribute.lastBaseline
  • Pay attention to priority, updateConstraints, or remakeConstraints to sharp actual implementation.

Discussion

Version Support

  • NSLayoutConstraint is available on iOS 6. However, from iOS 8, Apple introduces a concept of active state to NSLayoutConstraint. Developers can activate or deactivate a constraint by changing this property and note that only active constraints affect the calculated layout.
  • In iOS 9, Apple solves the problem of creation syntax by coming up with NSLayoutAnchor.

  • While iOS 10 or later, SnapKit can be used.

Syntax

  • SnapKit is more convenient than NSLayoutConstraint since it makes the code look short and precise, while NSLayoutConstraint doesn’t require integrating any external libraries and learning new syntax. External libraries might not be familiar to current or future teammates.

  • Although SnapKit is now the preferred option for every project that could be easier and faster for both the development and review phase, it depends on the project process and resources to make a decision that could be more facilitated.

Debug

  • SnapKit provides the ability to debug view by printing out logs for every constraint. This can be pretty tricky to achieve when using NSLayoutConstraint.

    .labeled allows to specify constraint labels for debug logs

  • Labels can be tacked on to the end of a constraint chain like so:

    Example:

      containerView.snp.makeConstraints {
          $0.leading.equalToSuperView().labeled("containerViewLeadingConstraint")
      }
    

    Resulting Unable to simultaneously satisfy constraints. logs will use constraint label to clarify:

      "<SnapKit.LayoutConstraint:[email protected]#78 Project.UIView:0x15cf1c870.leading == UIView:0x15cf1b810.leading>"
    

    And showing Will attempt to recover by breaking constraint logs that constraint is recovering:

      "<SnapKit.LayoutConstraint:[email protected]#78 Project.UIView:0x15cf1c870.leading == UIView:0x15cf1b810.leading + x.x>"
    

Conclusion

  • After considering several criteria, deciding to go with SnapKit or NSLayoutConstraint depends on minimum iOS version support or current teammates’ knowledge and experience.
  • In terms of pros what’s mentioned in Discussion of SnapKit, there’s almost no preference to use NSLayoutConstraint despite being Apple’s default API. However, it’s totally perfect as an alternative if any reasons SnapKit is not an option.