AutoLayout (1) — Intrinsic Content Size
I have been busy recently, sorting out the contents of AutoLayout.
Key points – Highlight
- The control of
IntrinsicContentSize
The size of the - CHCR is the key to conflict resolution
- Constrain has Priority
systemLayoutSizeFitting
Is the constraints andFrame
The link between
1. The Anchor and Constrain
AutoLayout of iOS is based on Cassowary algorithm. After the developer sets the constraint information of each view, NSISEnginer will calculate the frame of each view. The final layout uses the View’s frame for layout.
Note AutoLayout and AutoSizeMask cannot coexist, AutoLayout for use in a View of need priority. Use the View translatesAutoresizingMaskIntoConstraints = false
When we use Anchor to carry out the constraint layout of the interface, we often encounter the problem that the result phenomenon is inconsistent with the expectation, and even the constraint conflict.
For example, one of our UILabel and UITextFiled are on the same line. We expect UITextFiled to be stretched, but the result is that UILabel is stretched. The Demo code is as follows:
func setupSubViews(a) {
let label = makeLabel(withText: "Name")
let textFiled = makeTextFiled(withPlaceHolderText: "Enter name here")
view.addSubview(label)
view.addSubview(textFiled)
NSLayoutConstraint.activate([
// label anchor
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8),
label.rightAnchor.constraint(equalTo: textFiled.leftAnchor, constant: -8),
// textFiled anchor
textFiled.topAnchor.constraint(equalTo: label.topAnchor),
textFiled.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8),])}func makeLabel(withText text: String) -> UILabel {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = text
label.backgroundColor = .yellow
return label
}
func makeTextFiled(withPlaceHolderText text: String) -> UITextField {
let label = UITextField()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = text
label.backgroundColor = .lightGray
return label
}
Copy the code
why??? This article is to solve the confusion here!!
2. IntrinsicContentSize
Our understanding of the View’s IntrinsicContentSize is critical when using AutoLayout.
We need to keep in mind that every View has a natural size, that is, the CGSize of this View without external constraints!!
The UI controls of the system have their own IntrinsicContentSize. That is, most UIKit controls can size themselves. Here are some common controls IntrinsicContentSize information
UISwitch -
49, (31)UIActivityIdicator -
Small: (20, 20) Big:(37, 37)UIButton -
Internal Label size + paddingUILabel -
The size that fits its text!!! This is if the width of the label is not constrained!! Special in the back)UIImageView: -
The size of The image(if there is no image then (0, 0))UIView -
Has no intrinsic conent size (or (-1, -1))
Note that from the concept above all IntrinsicContentSize 1~5 have IntrinsicContentSize, that is, they can size themselves according to their content, whereas the native UIView cannot.
2.1 UIView
UIView by default IntrinsicContentSize is (1, 1), which means that the UIView by default is not the size of a natural, or natural size is (UIView. NoIntrinsicMetric, UIView.noIntrinsicMetric)
In addition to directly setting widthAnchor and heightAnchor to constrain the SIZE of THE UIView, another way is to override the method by custom inheritance:
class BigView: UIView {
// Have a natural size UIView
override var intrinsicContentSize: CGSize{
return CGSize(width: 200, height: 100)}}class MyView: UIView {
// The default natural size is (-1, -1) and cannot participate in AutoLayout calculations!!
override var intrinsicContentSize: CGSize{
let size = super.intrinsicContentSize
return size
}
}
Copy the code
If you want to use MyView later, you can have your own inherent size just like UIlabel!
2.2 UILabel
class MyLabel: UILabel {
override var intrinsicContentSize: CGSize {
let size1 = super.intrinsicContentSize
let size2 = sizeThatFits(UIView.layoutFittingCompressedSize)
print("UILabel intrinsicContentSize: \(size1)")
print("UILabel sizeThatFits: \(size2)")
return size1
}
}
Copy the code
- in
label.text = ""
, there is no content, and the output is as follows:
UILabel intrinsicContentSize: (0.0, 0.0) UILabel sizeThatFits: (0.0, 0.0) UILabel intrinsicContentSize: (0.0, 0.0)Copy the code
- in
label.text = "123123123131313232332"
when
UILabel intrinsicContentSize: (196.0, 20.333333333333332)
UILabel sizeThatFits: (196.0, 20.333333333333332)
Copy the code
- in
label.text = "123123123131313232332"
And,label.preferredMaxLayoutWidth = 100
.label.numberOfLines = 0
:
UILabel intrinsicContentSize: (96.0, 61.0)Copy the code
On the basis of the above:
let myLabel = MyLabel()
myLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myLabel)
myLabel.text = "123123123131313232332"
myLabel.preferredMaxLayoutWidth = 100
myLabel.numberOfLines = 0
let layoutSize = myLabel.systemLayoutSizeFitting(CGSize(width: 100, height: UIView.noIntrinsicMetric))
print("layoutSize: \(layoutSize)")
Copy the code
You can see the output:
LayoutSize: (96.0, 61.0) UILabel intrinsicContentSize: (96.0, 61.0) UILabel sizeThatFits: (196.0, 20.3333333333332)Copy the code
Note:
- PreferredMaxLayoutWidth and numberOfLines need to be used together to take effect!
- PreferredMaxLayoutWidth can simply be interpreted as the intrinsic width estimate of the intrinsicContent of the label when the text text is long
- SizeThatFits method with
systemLayoutSizeFitting
2.3 ImageView
ImageView is similar to UIlabel in that the natural size is (0, 0) when the image property is not set, and when the image property is set, the natural size is the image size
3. CHCR and constraint priority
Assume that the Intrinsic content size of a custom Label is (width: 50,height: 20).
(50, 20) represents the natural size of the Label!! But this is only the expected size of the Label itself, which is Optional!! What if there are additional constraints that make it impossible to meet this expectation? Here’s where you need to understand CHCR: CH means Content Hugging; CR stands for Content Compression Resistance.
- Content Hugging:
label.width <= 50, priority = defaultLow(250)
- Content Compression Resistance:
label.width >= 50, priority = defaultHight(750)
The Intrinsic Size specifies that width is associated with the NSISEnginer with a specified priority. (height)
The default priority for Anchor Constrain we create is Priority = Required (1000)
Therefore, as long as we manually set a horizontal direction constraint on the label, it will break its CHCH constraint, and there are two common setting width constraints on the label:
label.leftAnchor
+label.rightAnchor
label.widthAnchor
Look again at the question at the beginning of this article!!
TextFiled.CH priority = textFiled.CH priority = textFiled.
// Method 1: Horizontally: label.chp 251 > textField. CHP 250, TextFiled will be stretched horizontally.
label.setContentHuggingPriority(UILayoutPriority(rawValue: 251), for: .horizontal)
TextFiled. CHP 250 > textFiled. CHP 249
textFiled.setContentHuggingPriority(UILayoutPriority(rawValue: 249), for: .horizontal)
Copy the code
So AutoLayout summarizes:
- Key concepts:
Intrinsic Size
isOptional
, just control suggestions, can be broken!! - Problem transformation:
Constraint anbiguous
orConstraint conflict
, to be converted to constrain views in conflicting directionsCHCR
Priority issue!! - Solution: Modify CHCR Priority. Specifically:
CH
said< =
And,priority = 250
;CR
said> =
.priority = 750
; If you don’t want to be stretched, increaseCHP
If you do not want to be compressed, increaseCRP
- Relationship with Frame:
systemLayoutSizeFitting
与translatesAutoresizingMaskIntoConstraints
- Special note: with
preferredMaxLayoutWidth
Property control
CHP/CRP indicates CH Priority and CR Priority
>= indicates the minimum size. <= indicates the maximum size
Intrinsic Width, which indicates that the minimum value is Width and the maximum value is Width, but the Priority is different
4 Common CHCR configurations
func makeImageView(named: String) -> UIImageView {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleToFit
view.image = UIImage(named: named)
// Image compression/tension resistance experience configuration
// Allows images to be fitted vertically: stretched or compressed
// By making the image hug itself a little bit less and less resistant to being compressed
// we allow the image to stretch and grow as required
view.setContentHuggingPriority(UILayoutPriority(rawValue: 249), for: .vertical)
view.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 749), for: .vertical)
return view
}
Copy the code
In view of the UILabel:
func makeLabel(withText text: String) -> UILabel {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = text
label.backgroundColor = .yellow
// Text in general needs to be more compact!!
label.preferredMaxLayoutWidth = 100
// Development lesson: As with XIB, set CH to 251
label.setContentHuggingPriority(UILayoutPriority(rawValue: 251), for: .horizontal)
label.setContentHuggingPriority(UILayoutPriority(rawValue: 251), for: .vertical)
return label
}
Copy the code
In XIB or Storyboard, UILabel’s CHP is 251 and UIImageView’s CRP is 749!!
Other Official Apple advice:
-
When stretching a series of views to fill a space, if all the views have an identical content-hugging priority, the layout is ambiguous. Auto Layout doesn’t know which view should be stretched.
A common example is a label and text field pair. Typically, you want the text field to stretch to fill the extra space while the label remains at its intrinsic content size. To Ensure this, make sure that the text field’s horizontal Content-hugging priority is lower than the label’s.
In fact, this example is so common that Interface Builder automatically handles it for you, setting the content-hugging priority for all labels to 251. If you are programmatically creating the layout, you need to modify the content-hugging priority yourself.
-
Odd and unexpected layouts often occur when views with invisible backgrounds (like buttons or labels) are accidentally stretched beyond their intrinsic content size. The actual problem may not be obvious, because the text simply appears in the wrong location. To prevent unwanted stretching, increase the content-hugging priority.
-
Baseline constraints work only with views that are at their intrinsic content height. If a view is vertically stretched or compressed, the baseline constraints no longer align properly.
-
Some views, like switches, should always be displayed at their intrinsic content size. Increase their CHCR priorities as needed to prevent stretching or compressing.
-
Avoid giving views required CHCR priorities. It’s usually better for a view to be the wrong size than for it to accidentally create a conflict. If a view should always be its intrinsic content size, consider using a very high priority (999) instead. This approach generally keeps the view from being stretched or compressed but still provides an emergency pressure valve, just in case your view is displayed in an environment that is bigger or smaller than you expected.
reference
- Github.com/forkingdog/…
- Developer.apple.com/library/arc…
- Constraints.cs.washington.edu/solvers/cas…
- Github.com/SnapKit/Sna…
- Github.com/layoutBox/P…