SwiftyJD
SwiftyJD

Reputation: 5441

Using NSLayoutAnchor to create constraints between two labels?

I'm creating a view programmatically and need to set constraints between two labels. I recently just discovered NSLayoutAnchor and feel it would be a good choice to use it but I'm unsure how to create constraints between two different things (ie labels, imageViews, etc). I know a general setup will look something like this:

let codedLabel:UILabel = UILabel()
codedLabel.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
codedLabel.textAlignment = .center
codedLabel.text = alertText
codedLabel.numberOfLines=1
codedLabel.textColor=UIColor.red
codedLabel.font=UIFont.systemFont(ofSize: 22)
codedLabel.backgroundColor=UIColor.lightGray

self.contentView.addSubview(codedLabel)
codedLabel.translatesAutoresizingMaskIntoConstraints = false
codedLabel.heightAnchor.constraint(equalToConstant: 200).isActive = true
codedLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
codedLabel.centerXAnchor.constraint(equalTo: codedLabel.superview!.centerXAnchor).isActive = true
codedLabel.centerYAnchor.constraint(equalTo: codedLabel.superview!.centerYAnchor).isActive = true

How would you set up constraints in between two labels?

Upvotes: 0

Views: 1761

Answers (1)

user7014451
user7014451

Reputation:

Let's say you have two UIViews you wish to lay out horizontally in one of two ways:

  • View #1 is 20 points from the leading edge of the superview's margin or safe area and View #2 is another 20 points from view #1. (Think of a left-justified row of buttons.)

  • Both views are to be centered, with equal spacing before/between/after each view. (Think of two buttons spaced equally apart and centered.)

For example #1 the code would be:

// #1
let view1 = UIView()
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
let view2 = UIView()
view2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view2)

// #2
let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)
view1.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 20).isActive = true

// #3
view2.leadingAnchor.constraint(equalTo: view1.trailingAnchor, constant: 20).isActive = true

// #4
view1.widthAnchor.constraint(equalToConstant: 100).isActive = true
view2.widthAnchor.constraint(equalToConstant: 100).isActive = true

Comment #1: Note that you do not need to give each view a frame, but you do need to set the auto-resizing mask to false. This is a mistake many new coders forget.

Comment #2: All UIViewController main views have a layoutMarginGuide that yield standard margins both vertically and horizontally (and in iOS 11, particularly for iPhone X, there is a new safeAreaLayoutGuide for vertical alignment). I've set the leading edge of view1 to be the leading edge of the margin with an additional constant of 20 points from it.

Comment #3: Just like I related view1 to the margin, I'm relating view2 to view1, but this time the leading edge ofview2is 20 points from theview1` trailing edge.

Comment #4: The last thing you need to do for horizontal placement is to give each view a width. In this case I wanted both to be 100 points.

Example #2 pretty much uses the same code as example #1, so I'll note the key differences only:

let view1 = UIView()
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
let view2 = UIView()
view2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view2)

let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)

// #1
let spacer1 = UILayoutGuide()
view.addLayoutGuide(spacer1)
let spacer2 = UILayoutGuide()
view.addLayoutGuide(spacer2)
let spacer3 = UILayoutGuide()
view.addLayoutGuide(spacer3)

// #2
spacer.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
view1.leadingAnchor.constraint(equalTo: spacer1.trailingAnchor).isActive = true
spacer2.leadingAnchor.constraint(equalTo: view1.trailingAnchor).isActive = true
view2.leadingAnchor.constraint(equalTo: spacer2.trailingAnchor).isActive = true
spacer3.leadingAnchor.constraint(equalTo: view2.trailingAnchor).isActive = true
spacer3.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true

// #3
view1.widthAnchor.constraint(equalToConstant: 100).isActive = true
view2.widthAnchor.constraint(equalToConstant: 100).isActive = true
spacer1.widthAnchor.constraint(equalTo: spacer2.widthAnchor).isActive = true
spacer2.widthAnchor.constraint(equalTo: spacer3.widthAnchor).isActive = true

Comment #1: In iOS 9 Apple introduced the concept of UILayoutGuides. Before this, to create "spacers" you had to actually create an invisible UIView and add it as a subview (with all the overhead associated with it). Layout guides "act" like views but do not have that overhead.

Comment #2: The horizontal sequence if "margin...spacer1...view1...spacer2...view2...spacer3...margin". Note that I'm not using any constants, as I wish to let the layout engine give equal spacing.

Comment #3: While I am giving width values for both views, I am not with the spacers. Instead, I am declaring their widths to be equal.

Please note that I've only worked with horizontal constraints. For auto layout to work, you also need to declare vertical constraints too. Most of the concepts are the same... instead of leading/trailing, centerX, and width anchors you have top/bottom, centerY, and height ones. BUT! Starting with iOS 11 you now have a safeAreaLayoutGuide that you should be using instead of layoutMarginsGuide. This only applies to vertical! That's why I separated things. Here's the code I use to work with vertical alignment:

let layoutGuideTop = UILayoutGuide()
let layoutGuideBottom = UILayoutGuide()
view.addLayoutGuide(layoutGuideTop)
view.addLayoutGuide(layoutGuideBottom)
if #available(iOS 11, *) {
    let guide = view.safeAreaLayoutGuide
    layoutGuideTop.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
    layoutGuideBottom.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
} else {
    layoutGuideTop.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
    layoutGuideBottom.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}

Now you have layoutGuideTop and layoutGuideBottom that should work regardless of what iOS version is running.

Upvotes: 1

Related Questions