Padding and Spacing UIViews


Easily add padding to the UILabels, UIImageViews, and custom UIViews in your iOS app

By Ariel Elkin

Suppose your UI contains a component that:

  1. needs to expand or entirely collapse (i.e. disappear) based on its content;
  2. is positioned at several points further in relation to another view, but this spacing needs to go if it collapses.

The red UILabel in the two screenshots below offers an example of such a component:

If the label’s text is not nil, then the label’s height is sized accordingly, and the blue view is positioned a fixed 20 points below the red label. However, if the label’s text is nil, then the label’s height is zero and the 20 points that separated the two views need to be brought to zero.

I’d like to offer two possible ways to achieve this and discuss their advantages and disadvantages.

NSLayoutConstraint Constants

You can represent this fixed spacing between the two views in terms of the constant in the linear equation that produces an NSLayoutConstraint:

A sample equation (source: Apple’s Anatomy of a Constraint)

In our example above with the red label, we would define a constraint by saying that the blue view’s top should equal the red label’s bottom plus 20 points:

blueView.topAnchor.constraintEqualToAnchor(redLabel.bottomAnchor, constant: 20)

We’ll need to keep a reference to this constraint so that we can eventually update its constant.

func clearContentsOfLabel() { 
  label.text = nil blueViewTopConstraint.constant = 0 
} 

func addTextToLabel(string: String) { 
  label.text = string blueViewTopConstraint.constant = 20 
}

This approach requires the view’s superview to declare a variable referring to the constraint in question, and to remember to update its constant whenever its content changes too.

Container View + Intrinsic Content Size

Another approach is to put the view inside an invisible container view, and lay out the adjacent views against this container view.

let redLabelPadded = PaddedView(content: redLabel, bottomPadding: 10) 

... 

let verticalConstraints = "V:|[redLabelPadded][blueView(20)]" 

...

func tapped() { 
  redLabel.text = redLabel.text == nil ? "Tap me!" : nil 
}

The idea is to let PaddedView automatically handle the required padding updates if the content inside redLabel changes.

The ‘automatically’ is achieved by overriding layoutSubviews in the following manner:

public override func layoutSubviews() { 
  super.layoutSubviews() 

  if contentView.bounds.size.width == 0 || contentView.bounds.size.height == 0 { 
    self.layoutMargins = UIEdgeInsetsZero 
  } 
  else { 
    self.layoutMargins = padding 
  } 
}

The idea is to let the view’s layoutMargins do the padding. The padding will be a UIEdgeInsets you pass into the PaddedView during its initialisation, representing the padding that the PaddedView should apply to its internal view (i.e. the contentView). Whenever contentView’s content changes, both its own and its superview’s layoutSubviews method will be invoked, thus triggering an update in the layoutMargins.

To see this in action, run the following command:

$ pod try PaddedView

And check out the PaddedView repository on GitHub.

Happy padding.