HackingWithSwift Day 30/31
https://www.hackingwithswift.com/read/6/2/advanced-auto-layout
For this part, kinda hope the author can add in some visual aids instead of full text description on what to change as everyone may be using different screen size or different constraint to begin with
let label2 = UILabel()
label2.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints set to false to prevent iOS generate default auto layout constraints if we intend to do it manually
===========================
So time for Auto Layout Visual Format Language (VFL). I have try this before in ObjC, hope the syntax is not so complicated in swift
let viewsDictionary = ["label1": label1, "label2": label2, "label3": label3, "label4": label4, "label5": label5]
view.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "H:|[label1]|", options: [], metrics: nil, views: viewsDictionary))
H = horizontal constraint
| | = single pipe on both end means to go edge to edge in parent view (equivalent to leading space = 0, Â trailing space = 0, or width = parent width)
view.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "V:|[label1]-[label2]-[label3]-[label4]-[label5]", options: [], metrics: nil, views: viewsDictionary))
V = vertical constraint
| = single pipe at the start point means align from top
- = dash in between, it’s vertical spacing , default = 10 points (equivalent to top spacing or bottom spacing, depends on the relationship between 2 views)
Now try update to this
view.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "V:|[label1(==88)]-[label2(==88)]-[label3(==88)]-[label4(==88)]-[label5(==88)]-(>=10)-|", options: [], metrics: nil, views: viewsDictionary))
==88, 88 points for height (since it’s V)
>=10, minimum bottom spacing 10 points (since it’s V)
Imagine if one need to change the height very frequent due to certain circumstances, human error may occur when wrong value input into it. So one can make use of metric, meaning aside the value to a variable and use that variable whenever needed, then can reduce human error
let metrics = ["labelHeight": 88]
view.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "V:|[label1(labelHeight)]-[label2(labelHeight)]-[label3(labelHeight)]-[label4(labelHeight)]-[label5(labelHeight)]->=10-|", options: [], metrics: metrics, views: viewsDictionary))
===========================
If turn to landscape, there will be constraint issue since the total height probably can’t fulfil 5 labels with 88 points height with the addition spacing in between. So we need to tell which constraint get higher priority
Constraint priority is a value between 1 and 1000. The higher the number, the higher the priority. 1000 = must follow. Since we know 88 points for height is impossible to be fulfilled in landscape mode, so can make that optional
view.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "V:|[label1(labelHeight@999)]-[label2(label1)]-[label3(label1)]-[label4(label1)]-[label5(label1)]->=10-|", options: [], metrics: metrics, views: viewsDictionary))
labelHeight@999 = set labelHeight with priority value 999 , so if it can’t fulfil the height set, auto layout can shrink it since it’s not 1000
label2(label1) = label2 copy label1 height (equivalent to label 2 equal height to label 1)
===========================
And then it’s Anchor time
Each view comes with widthAnchor, heightAnchor, topAnchor, bottomAnchor, leftAnchor, rightAnchor, leadingAnchor, trailingAnchor, centerXAnchor, and centerYAnchor.
If device language is those type from right to left, like Arabic, using leadingAnchor and trailingAnchor will flip the view the other way around. If want remain the same as leadingAnchor and trailingAnchor with language like English as device language, one need to use leftAnchor and rightAnchor instead.
for label in [label1, label2, label3, label4, label5] {
  label.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
  label.heightAnchor.constraint(equalToConstant: 88).isActive = true
  if let previous = previous {
    // we have a previous label – create a height constraint
    label.topAnchor.constraint(equalTo: previous.bottomAnchor, constant: 10).isActive = true
  }
  // set the previous label to be the current one, for the next loop iteration
  previous = label
}
By default the view will stick to the top if we don’t assign anything, so for the first label, we don’t need the topAnchor, only the following one onwards. BUT there’s one problem. To make sure the view look nice at iPhone X series or iPhone with notch, we need to ensure the top or bottom view is within the safe area zone
if let previous = previous {
  // we have a previous label – create a height constraint
  label.topAnchor.constraint(equalTo: previous.bottomAnchor, constant: 10).isActive = true
} else {
  // this is the first label
  label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
}
So we will need this to ensure it look nice.