Creating iOS Apps without Storyboard – Part 2

NSLayoutConstraints Class

In this approach we actually create a constraints class object and the provide the details. Basically we use the NSLayoutConstraints init method to create constraints. Here is how it looks:

NSLayoutConstraint(item: list, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 10.0).isActive = true

It becomes immediately clear that we have to provide all the information to create the constraints object. This has to be done when if the information is not necessary for the creation of the constraint. Let us have a look at some code to see how it works.


I will be continuing from the same UIStackView project.

  1. Switch to the ViewController.swift file. Add an extension to the ViewController class.
  2. Add the function to apply constraints to the title elements as shown below:
func apply_constraints_to_title()
{
    icon.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal)
    icon.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical)
    appTitle.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal)

    let apptitleConstraint  : NSLayoutConstraint    = NSLayoutConstraint(item: appTitle, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: icon, attribute: NSLayoutConstraint.Attribute.width, multiplier: 4.0, constant: 0.0)
    apptitleConstraint.isActive                     = true
}

Let us examine the line where we create the constraint.

let apptitleConstraint  : NSLayoutConstraint    = NSLayoutConstraint(item: appTitle, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: icon, attribute: NSLayoutConstraint.Attribute.width, multiplier: 4.0, constant: 0.0)

Here we are creating the constraint object. We are specifying all the components of the equation

appTitle.width \geq (4.0 \times icon.width) + 0.0

3. Similarly write the following code down to complete the implementation for the remaining views.

func apply_constraints_to_fields()
{
    let nameFieldConstraint     : NSLayoutConstraint    = NSLayoutConstraint(item: nameField, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0)
    nameFieldConstraint.isActive                        = true

    let emailFieldConstraint    : NSLayoutConstraint    = NSLayoutConstraint(item: emailField, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0)
    emailFieldConstraint.isActive                       = true

    let fieldConstraint         : NSLayoutConstraint    = NSLayoutConstraint(item: nameField, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: emailField, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 0.0)
    fieldConstraint.isActive                            = true
    }
    
func apply_constraints_to_age()
{
    let ageConstraint           : NSLayoutConstraint    = NSLayoutConstraint(item: age, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: ageValue, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 0.0)
    ageConstraint.isActive                              = true
}
    
func apply_constraints_to_service()
{
    let serviceConstraint       : NSLayoutConstraint    = NSLayoutConstraint(item: serviceRating, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: serviceLbl, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.5, constant: 0.0)
    serviceConstraint.isActive                          = true
        
    let serviceLblConstraint    : NSLayoutConstraint    = NSLayoutConstraint(item: serviceLbl, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0)
    serviceLblConstraint.isActive                       = true
        
    serviceLbl.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal)
}
    
func apply_constraints_to_satisfaction()
{
    let satisfactionConstraint  : NSLayoutConstraint    = NSLayoutConstraint(item: satisfaction, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: satisfactionLbl, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.5, constant: 0.0)
    satisfactionConstraint.isActive                     = true
        
    let satisfactionLblCnstr    : NSLayoutConstraint    = NSLayoutConstraint(item: satisfactionLbl, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0)
    satisfactionLblCnstr.isActive                       = true
        
    satisfactionLbl.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal)
}
    
func apply_constraints_to_buttons()
{
    let btnConstraint       : NSLayoutConstraint    = NSLayoutConstraint(item: saveBtn, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: fetchBtn, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 0.0)
    btnConstraint.isActive                          = true
}
    
func apply_constraints_to_all_Stacks()
{
    NSLayoutConstraint(item: titleStack, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0).isActive  = true

    NSLayoutConstraint(item: ageStack, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0).isActive  = true

    NSLayoutConstraint(item: serviceStack, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0).isActive  = true
        
    NSLayoutConstraint(item: satisfactionStk, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0).isActive  = true

    NSLayoutConstraint(item: buttonStk, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 100.0).isActive  = true
        
    NSLayoutConstraint(item: buttonStk, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 30.0).isActive   = true
        
    NSLayoutConstraint(item: serviceStack, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: satisfactionStk, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 0.0).isActive                 = true
        
    NSLayoutConstraint(item: satisfactionStk, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: buttonStk, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 0.0).isActive              = true
        
    NSLayoutConstraint(item: ageStack, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: buttonStk, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 0.0).isActive                    = true
        
    NSLayoutConstraint(item: titleStack, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: ageStack, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 0.0).isActive              = true
        
    titleStack.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal)
    agePicker.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical)
    nameField.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.vertical)
    emailField.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.vertical)
}
    
func apply_constraints_to_enclosing_stack()
{
    NSLayoutConstraint(item: enclosingStack, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 10.0).isActive                           = true
    NSLayoutConstraint(item: enclosingStack, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: -10.0).isActive                         = true
    NSLayoutConstraint(item: enclosingStack, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: -30.0).isActive                           = true
    NSLayoutConstraint(item: enclosingStack, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 170.0).isActive                              = true
}

The code is the same throughout. The only change I made was to skip saving the object reference in another variable. I directly chained the property isActive to the init call.
4. Consolidate all the calls in a single function.

func apply_constraints()
{
    self.apply_constraints_to_title()
    self.apply_constraints_to_fields()
    self.apply_constraints_to_age()
    self.apply_constraints_to_service()
    self.apply_constraints_to_satisfaction()
    self.apply_constraints_to_buttons()
    self.apply_constraints_to_all_Stacks()
    self.apply_constraints_to_enclosing_stack()
}

5. Call the consolidated function in the viewDidLoad method.

override func viewDidLoad() 
{
    super.viewDidLoad()
    // Do any additional setup after loading the view.
        
    self.navigationItem.title               = "Feedback"
    self.configureUIElements()
    self.configureStacks()
    self.apply_constraints()
}

Run the project and see how the second screen comes up. Feel free to add or remove constraints to see how the UI renders.

Here is the link to the completed project using NSLayoutConstraints.

Advertisement

One thought on “Creating iOS Apps without Storyboard – Part 2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s