接着一,继续看变换,先旋转后平移再逆旋转(inverted + concatenating)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let v1 = UIView(frame:CGRect(x: 20, y: 100, width: 120, height: 200))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds)
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
self.view.addSubview(v1)
v1.addSubview(v2)
//v2.transform = CGAffineTransform(rotationAngle: 45 * .pi/180).translatedBy(x: 100, y: 0)
let r = CGAffineTransform(rotationAngle: 45 * .pi/180)
let t = CGAffineTransform(translationX:100, y:0)
v2.transform = t.concatenating(r) // not r.concatenating(t)
v2.transform = r.inverted().concatenating(v2.transform)
print(v1.frame, v1.center)
print(v2.frame, v2.center)
}
iphone XR 效果图
v1.transform = CGAffineTransform(a:1, b:0, c:-0.2, d:1, tx:0, ty:0)
结果看起来是个平行四边形
view -> window -> screen
这里主要看看view 和周围环境的关系
设备是没有frame的,但是有bounds,主窗口是没有父view的,屏幕的bounds作为窗口的frame
let w = UIWindow(frame: UIScreen.main.bounds)
frame 可以 omit, 等价于 let w = UIWindow() , 此时窗口填充整个屏幕,多数情况下 window coordinates are screen coordinates
ios7 之前 Screen Coordinates是不变的,设备旋转时,界面跟着旋转
ios8之后有一个大的改变,屏幕和窗口来改变(改变bounds),view 不会收到旋转变换。
有两套屏幕坐标(UICoordinateSpace)UICoordinateSpace is a protocol (also adopted by UIView) that provides a bounds
property
1) UIScreen’s coordinateSpace property
This coordinate space rotates. Its bounds height and width are transposed when the app rotates to compensate for a change in the orientation of the device; its origin is at the top left of the app.
2) UIScreen’s fixedCoordinateSpace property
This coordinate space is invariant. Its bounds origin stays at the top left of the physical device, remaining always in the same relationship to the device’s hardware buttons regardless of how the device itself is held.
UICoordinateSpace provides methods parallel to the coordinate-conversion methods
• convert(_:from:)
• convert(_:to:)
The first parameter is either a CGPoint or a CGRect. The second parameter is a UICoordinateSpace, which might be a UIView or the UIScreen; 是接收者
例如,希望获得view v在固定坐标的位置可以通过
let screen = UIScreen.main.fixedCoordinateSpace
let r = v.superview!.convert(v.frame, to: screen)
通常很少需要关心窗口坐标,因为app 的所有可见的东西都发生在根VC的主view里,当设备旋转时view的边界自动调整。
traitCollection is a UITraitCollection, a value class,introduced in iOS 8
属性有:
displayScale (the screen resolution)
userInterfaceIdiom (evice type, iPhone or iPad)
just two properties in particular concern us with regard to views in general:
horizontalSizeClass
verticalSizeClass
UIUserInterfaceSizeClass value: 两种:either .regular or .comp
Both the horizontal and vertical size classes are .regular : running on an iPad.
horizontal size class is .compact and the vertical size class is .regular running on an iPhone in portrait orientation.
horizontal size class is .regular and the vertical size class is .compact
running on an iPhone 6/7/8 Plus, iPhone XR, or iPhone XS Max (the “big” iPhones) with the app in landscape orientation.
Both the horizontal and vertical size classes are .compact
running on an iPhone (other than a big iPhone) with the app in landscape
orientation.
UITraitEnvironment 协议
trait collection changes while the app is running, the traitCollectionDidChange(_:) message is propagated
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection
自己可以构建trait collection
combine trait collections by calling init(traitsFrom:) with an array of trait collections. For example:
let tcdisp = UITraitCollection(displayScale: UIScreen.main.scale)
let tcphone = UITraitCollection(userInterfaceIdiom: .phone)
let tcreg = UITraitCollection(verticalSizeClass: .regular)
let tc1 = UITraitCollection(traitsFrom: [tcdisp, tcphone, tcreg])
比较 trait collections
call containsTraits(in:).
func containsTraits(in trait: UITraitCollection?) -> Bool
我们已经看到父view bounds原点改变时,子view改变位置,如果父view的bounds size or frame改变会怎么样?不会改变,所以需要Layout,
Layout 种类:
1)手动 manual
原理:superview is sent the layoutSubviews message whenever it is resized
在以下情况都会调用
2)Autoresizing
3)Autolayout
it is quite likely that all your views will opt in to autolayout, because it’s so powerful and best suited to
help your interface adapt to a great range of screen sizes.
In code
In a nib file
例子
1)考虑一个按钮在父view的右下角:需要设置:
2)考虑一个文本框在父view 的上面,宽度和父view相等:需要设置
代码实现方法:
combination of springs and struts is set through a view’s autoresizing Mask property (UIView.AutoresizingMask)
let v1 = UIView(frame:CGRect(x: 100, y: 111, width: 132, height: 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:CGRect(x: 0, y: 0, width: 132, height: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
let v1b = v1.bounds
let v3 = UIView(frame:CGRect(x: v1b.width-20, y: v1b.height-20, width: 20, height: 20))
v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
self.view.addSubview(v1)
v1.addSubview(v2)
v1.addSubview(v3)
v2.autoresizingMask = .flexibleWidth
v3.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin]
print(v1.frame, v1.center)
print(v2.frame, v2.center)
Before autoresizing
After autoresizing
If autoresizing isn’t sophisticated enough to achieve what you want, you have two choices:
how does a view opt in to using autolayout? :使用约束Constraints,约束通知autolayout 引擎如何布局view
Constraint
Here are the chief properties of an NSLayoutConstraint:
firstItem, firstAttribute, secondItem, secondAttribute :The two views and their respective attributes (NSLayoutConstraint.Attribute)
If the constraint is describing a view’s absolute height or width
the second view will be nil and the second attribute will be .notAnAttribute
possible attribute values are:
• .width, .height
• .top, .bottom
• .left, .right, .leading, .trailing
• .centerX, .centerY
• .firstBaseline, .lastBaseline
multiplier, constant
the first attribute’s value is to equal the second attribute’s value, the multiplier will be 1 and the constant will be 0.
If describing a view’s width or height absolutely, the multiplier will be 1(not 0) and the constant will be the width or height value.
relation
Possible values are (NSLayoutConstraint.Relation):
priority
range from 1000 (required) down to 1,
determining the order in which they are applied
Starting in iOS 11, a priority is not a number but a UILayoutPriority struct wrapping the numeric value as its rawValue
A constraint belongs to a view, 可以有许多约束,并且view 有个 constraints属性 和相关方法
However, you’ll probably never call any of those methods!, 从Ios8开始,可以 activate the constraint
NSLayoutConstraint properties are read-only, except for priority, constant, and isActive.
note:
Once you are using explicit constraints to position and size a view, do not set its frame (or bounds and center); use constraints alone
例子
let lab1 = UILabel(frame:CGRect(x: 270,y: 60,width: 42,height: 22))
lab1.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin]
lab1.text = "Hello"
self.view.addSubview(lab1)
let lab2 = UILabel()
lab2.translatesAutoresizingMaskIntoConstraints = false
lab2.text = "Howdy"
self.view.addSubview(lab2)
NSLayoutConstraint.activate([
lab2.topAnchor.constraint(
equalTo: lab1.bottomAnchor, constant: 20),
lab2.trailingAnchor.constraint(
equalTo: self.view.trailingAnchor, constant: -20)
])
print(lab1,lab2)
运行图
注意
conflict between your explicit constraints and the implicit constraints. ?
The solution is to set the view’s translatesAutoresizingMaskIntoConstraints property to false, so that the implicit constraints are not generated, and the view’s only constraints are your explicit constraints.
如果In a nib with “Use Auto Layout” checked,冲突的事情发生会自动解决(translatesAutoresizingMaskIntoConstraints = false)
方式1 using the NSLayoutConstraint initializer:
init(item:attribute:relatedBy:toItem:attribute:multiplier:constant:)
This initializer sets every property of the constraint, as I described them a moment ago — except the priority, which defaults to .required (1000)
使用addConstraint,实现之前autoresizing 一样的效果
let v1 = UIView(frame:CGRect(x: 100, y: 111, width: 132, height: 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView()
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
let v3 = UIView()
v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
self.view.addSubview(v1)
v1.addSubview(v2)
v1.addSubview(v3)
v2.translatesAutoresizingMaskIntoConstraints = false
v3.translatesAutoresizingMaskIntoConstraints = false
v1.addConstraint(
NSLayoutConstraint(item: v2,
attribute: .leading,
relatedBy: .equal,
toItem: v1,
attribute: .leading,
multiplier: 1, constant: 0)
)
v1.addConstraint(
NSLayoutConstraint(item: v2,
attribute: .trailing,
relatedBy: .equal,
toItem: v1,
attribute: .trailing,
multiplier: 1, constant: 0)
)
v1.addConstraint(
NSLayoutConstraint(item: v2,
attribute: .top,
relatedBy: .equal,
toItem: v1,
attribute: .top,
multiplier: 1, constant: 0)
)
v2.addConstraint(
NSLayoutConstraint(item: v2,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1, constant: 10)
)
v3.addConstraint(
NSLayoutConstraint(item: v3,
attribute: .width,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1, constant: 20)
)
v3.addConstraint(
NSLayoutConstraint(item: v3,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1, constant: 20)
)
v1.addConstraint(
NSLayoutConstraint(item: v3,
attribute: .trailing,
relatedBy: .equal,
toItem: v1,
attribute: .trailing,
multiplier: 1, constant: 0)
)
v1.addConstraint(
NSLayoutConstraint(item: v3,
attribute: .bottom,
relatedBy: .equal,
toItem: v1,
attribute: .bottom,
multiplier: 1, constant: 0)
)
方式2 Anchor notation
Focus on the attributes to which the constraint relates
These attributes are expressed as anchor properties of a UIView:
• widthAnchor, heightAnchor
• topAnchor, bottomAnchor
• leftAnchor, rightAnchor, leadingAnchor, trailingAnchor
• centerXAnchor, centerYAnchor
• firstBaselineAnchor, lastBaselineAnchor
The anchor values are instances of NSLayoutAnchor subclasses
depending on how much information you need to express, You can provide
• constraint(equalTo:)
• constraint(greaterThanOrEqualTo:)
• constraint(lessThanOrEqualTo:)
• constraint(equalTo:constant:)
• constraint(greaterThanOrEqualTo:constant:)
• constraint(lessThanOrEqualTo:constant:)
• constraint(equalTo:multiplier:)
• constraint(greaterThanOrEqualTo:multiplier:)
• constraint(lessThanOrEqualTo:multiplier:)
• constraint(equalTo:multiplier:constant:)
• constraint(greaterThanOrEqualTo:multiplier:constant:)
• constraint(lessThanOrEqualTo:multiplier:constant:)
• constraint(equalToConstant:)
• constraint(greaterThanOrEqualToConstant:)
• constraint(lessThanOrEqualToConstant:)
ios10 distance between two anchor
• anchorWithOffset(to:)
Starting in iOS 11, additional methods create a constraint based on a constant value provided by the runtime
helpful for getting the standard spacing between views, and is especially valuable when connecting text baselines vertically, because the system spacing will change according to the text size:
• constraint(equalToSystemSpacingAfter:multiplier:)
• constraint(greaterThanOrEqualToSystemSpacingAfter:multiplier:)
• constraint(lessThanOrEqualToSystemSpacingAfter:multiplier:)
• constraint(equalToSystemSpacingBelow:multiplier:)
• constraint(greaterThanOrEqualToSystemSpacingBelow:multiplier:)
• constraint(lessThanOrEqualToSystemSpacingBelow:multiplier:)
The anchor notation is particularly convenient in connection with activate(_:)
使用anchor 属性得到和上面例子一样的效果
NSLayoutConstraint.activate([
v2.leadingAnchor.constraint(equalTo:v1.leadingAnchor),
v2.trailingAnchor.constraint(equalTo:v1.trailingAnchor),
v2.topAnchor.constraint(equalTo:v1.topAnchor),
v2.heightAnchor.constraint(equalToConstant:10),
v3.widthAnchor.constraint(equalToConstant:20),
v3.heightAnchor.constraint(equalToConstant:20),
v3.trailingAnchor.constraint(equalTo:v1.trailingAnchor),
v3.bottomAnchor.constraint(equalTo:v1.bottomAnchor)
])
这种方式确实比addConstraint/s好用得多,可读性更好
方式3 Visual format notation
另外一种简洁的方法是基于文本的缩写格式叫做 visual format
优点:
"V:|[v2(15)]"
V: means that the vertical dimension, alternative is H:
A view’s name appears in square brackets
a pipe (|) signifies the superview
意思是: portraying v2’s top edge as butting up against its superview’s top edge, setting v2’s height to 15
let d = ["v2":v2,"v3":v3]
NSLayoutConstraint.activate([
NSLayoutConstraint.constraints(withVisualFormat:
"H:|[v2]|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat:
"V:|[v2(10)]", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat:
"H:[v3(20)]|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat:
"V:[v3(20)]|", metrics: nil, views: d)
].flatMap{$0})
与前例达到同样的效果, 其中flatMap 是降维的作用,将2维数组降为1维的
• To specify the distance between two successive views, use hyphens surrounding
the numeric value, like this: "[v1]-20-[v2]". The numeric value may optionally
be surrounded by parentheses.
• A numeric value in parentheses may be preceded by an equality or inequality
operator, and may be followed by an at sign with a priority. Multiple numeric val‐
ues, separated by comma, may appear in parentheses together. For example:
"[v1(>=20@400,<=30)]".
you can get many constraints generated by a single compact visual format string. However, it hasn’t been updated for recent iOS versions,so there are some important types of constraint that visual format syntax can’t express (such as pinning a view to the safe area, discussed later in this chapter).
it is frequently useful to form constraints and keep them on hand for future use, typically in a property.
例子:
create two sets of constraints, one describing the positions of v1, v2, and v3 when all three are present, the other describing the positions of v1 and v3 when v2 is absent
prepared two properties, constraintsWith and constraintsWithout, initialized as empty arrays of NSLayoutConstraint.
also need a strong reference to v2, so that it doesn’t vanish when we remove it from the interface:
var v2 : UIView!
var constraintsWith = [NSLayoutConstraint]()
var constraintsWithout = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let v1 = UIView()
v1.backgroundColor = .red
v1.translatesAutoresizingMaskIntoConstraints = false
let v2 = UIView()
v2.backgroundColor = .yellow
v2.translatesAutoresizingMaskIntoConstraints = false
let v3 = UIView()
v3.backgroundColor = .blue
v3.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(v1)
self.view.addSubview(v2)
self.view.addSubview(v3)
self.v2 = v2 // retain
// construct constraints
let c1 = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-(20)-[v(100)]", metrics: nil, views: ["v":v1])
let c2 = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-(20)-[v(100)]", metrics: nil, views: ["v":v2])
let c3 = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-(20)-[v(100)]", metrics: nil, views: ["v":v3])
let c4 = NSLayoutConstraint.constraints(withVisualFormat:
"V:|-(100)-[v(20)]", metrics: nil, views: ["v":v1])
let c5with = NSLayoutConstraint.constraints(withVisualFormat:
"V:[v1]-(20)-[v2(20)]-(20)-[v3(20)]", metrics: nil,
views: ["v1":v1, "v2":v2, "v3":v3])
let c5without = NSLayoutConstraint.constraints(withVisualFormat:
"V:[v1]-(20)-[v3(20)]", metrics: nil, views: ["v1":v1, "v3":v3])
// apply common constraints
NSLayoutConstraint.activate([c1, c3, c4].flatMap{$0})
// first set of constraints (for when v2 is present)
self.constraintsWith.append(contentsOf:c2)
self.constraintsWith.append(contentsOf:c5with)
// second set of constraints (for when v2 is absent)
self.constraintsWithout.append(contentsOf:c5without)
// apply first set
NSLayoutConstraint.activate(self.constraintsWith)
}
if self.v2.superview != nil {
self.v2.removeFromSuperview()
NSLayoutConstraint.deactivate(self.constraintsWith)
NSLayoutConstraint.activate(self.constraintsWithout)
} else {
self.view.addSubview(v2)
NSLayoutConstraint.deactivate(self.constraintsWithout)
NSLayoutConstraint.activate(self.constraintsWith)
}
secondary edges is expressed in two different ways:
Edge insets
A view vends secondary edges as a UIEdgeInsets,a struct consisting of four floats representing inset values top, left, bottom, right
Layout guides
The UILayoutGuide class represents secondary edges as a kind of pseudoview, It has a frame (its layoutFrame) with respect to the view, but its important properties are its anchors, which are the same as for a view, is useful for autolayout.
实际项目中, subviews 通常放置在主view 的safe area,从而避免重叠
To retrieve a view’s safe area as edge insets, fetch its safeAreaInsets
To retrieve a view’s safe area as a layout guide, fetch its safeAreaLayoutGuide
just allow views pinned to a safe area layout guide to move as the safe area changes
example, v is a view controller’s main view, and v1 is its subview; we construct a constraint between the top of v1 and the top of the main view’s safe area:
let c = v1.topAnchor.constraint(equalTo: v.safeAreaLayoutGuide.topAnchor)
A view controller can inset even further the safe area it imposes on its main view; set its additionalSafeAreaInsets,as the name implies, is added to the automatic safe area. It is a UIEdgeInsets.
if you set a view controller’s additionalSafeAreaInsets to a UIEdgeInsets with a top of 50,the default safe area top would be 20, so now it’s 70
view 的margins 如何获得?
through the UIView layoutMarginsGuide property which is UILayoutGuide
举个梨子:a constraint between a subview’s leading edge and its superview’s leading margin:
let c = v.leadingAnchor.constraint(equalTo:
self.view.layoutMarginsGuide.leadingAnchor)
使用visual format syntax
let arr = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-[v]", metrics: nil, views: ["v":v])
layoutMarginsGuide 与 layoutMargins,directionalLayoutMargins 属性的联系与区别?
都是UIView的属性
前者只读,后2者是可写的UIEdgeInsets,NSDirectionalEdgeInsets(prefered)
a view’s layout margins can propagate down to its subview?
To switch on this option, set the subview’s preservesSuperviewLayoutMargins to true
view controller also has a systemMinimumLayoutMargins property, it imposes these margins on its main view as a minimum, meaning that you can increase the main view’s margins beyond these limits, to reduce it, need to sett the view controller’s viewRespectsSystemMinimumLayoutMargins property to false
systemMinimumLayoutMargins default value is a top and bottom margin of 0 and side margins of 16 on a smaller device, with side margins of 20 on a larger device.
A second set of margins, a UIView’s readableContentGuide (a UILayoutGuide), constraining such a subview horizontally to its
superview’s readableContentGuide
add your own custom UILayoutGuide objects to a view. They constitute a view’s layoutGuides array, managed by calling addLayoutGuide(_:) or removeLayoutGuide(_:). Each custom layout guide object must be configured entirely using constraints.
We can constrain a view to a UILayoutGuide,by means of its anchors since a UILayoutGuide is configured by constraints
UILayoutGuide exactly as if it were a subview, but it is not a subview therefore it avoids all the overhead and complexity that a UIView would have
举个例子:
将四个子view均匀分布在父view,如何保证在父view变化情况下也成立。
思路1)4个view直接填入 view,代价有点大,2)填入UILayoutGuides 解决问题
let guides = [UILayoutGuide(),UILayoutGuide(),UILayoutGuide()]
for guide in guides {
self.view.addLayoutGuide(guide)
}
4个view初始化使用代码
let v1 = UIView()
v1.backgroundColor = .red
v1.translatesAutoresizingMaskIntoConstraints = false
let v2 = UIView()
v2.backgroundColor = .yellow
v2.translatesAutoresizingMaskIntoConstraints = false
let v3 = UIView()
v3.backgroundColor = .blue
v3.translatesAutoresizingMaskIntoConstraints = false
let v4 = UIView()
v4.backgroundColor = .cyan
v4.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(v1)
self.view.addSubview(v2)
self.view.addSubview(v3)
self.view.addSubview(v4)
let d = ["v1":v1,"v2":v2,"v3":v3,"v4":v4]
NSLayoutConstraint.activate([
NSLayoutConstraint.constraints(withVisualFormat: "H:|[v1]|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "H:|[v2]|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "H:|[v3]|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "H:|[v4]|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:|-[v1(20)]", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:[v2(20)]", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:[v3(20)]", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:[v4(20)]-|", metrics: nil, views: d),
].flatMap{$0})
view之间确定3个UILayoutGuide
let views = [v1, v2, v3, v4]
NSLayoutConstraint.activate([
// guide left is arbitrary, let's say superview margin
guides[0].leadingAnchor.constraint(equalTo:self.view.leadingAnchor),
guides[1].leadingAnchor.constraint(equalTo:self.view.leadingAnchor),
guides[2].leadingAnchor.constraint(equalTo:self.view.leadingAnchor),
// guide widths are arbitrary, let's say 10
guides[0].widthAnchor.constraint(equalToConstant:10),
guides[1].widthAnchor.constraint(equalToConstant:10),
guides[2].widthAnchor.constraint(equalToConstant:10),
// bottom of each view is top of following guide
views[0].bottomAnchor.constraint(equalTo:guides[0].topAnchor),
views[1].bottomAnchor.constraint(equalTo:guides[1].topAnchor),
views[2].bottomAnchor.constraint(equalTo:guides[2].topAnchor),
// top of each view is bottom of preceding guide
views[1].topAnchor.constraint(equalTo:guides[0].bottomAnchor),
views[2].topAnchor.constraint(equalTo:guides[1].bottomAnchor),
views[3].topAnchor.constraint(equalTo:guides[2].bottomAnchor),
// guide heights are equal!
guides[1].heightAnchor.constraint(equalTo:guides[0].heightAnchor),
guides[2].heightAnchor.constraint(equalTo:guides[0].heightAnchor),
])
这样此方案就结束了,有没有更直接的方案呢? 其实真还有就是利用stack view(UIStackView) 专门来处理一行或一列子view的布局问题,后面即将会看到。
有一些内置的界面元素,使用自动布局时候会有一维或二维方面固定大小,比如:
UIButton 按钮,缺省是个标准高度,宽度则由标题来决定。
UIImageView, 图像视图,根据图像大小自适应。
UILabel , 如果是由多行组成的,宽度约束,则高度会调整以显示所有文本
这些内在的大小即所谓的intrinsic content size. 它们用于产生隐含约束(of class NSContentSizeLayoutConstraint)
因此,这些随着具体内容变化的界面元素会影响到界面布局,故而需要考虑布局约束来适配这些潜在的变化
access these priorities (the parameter is an NSLayoutConstraint.Axis, either .horizontal or .vertical):
contentHuggingPriority(for:)
.defaultLow (250)
contentCompressionResistancePriority(for:)
.defaultHigh (750)
visual formats configuring two horizontally adjacent labels (lab1 and lab2) to be pinned to the superview
and to one another:
"V:|-20-[lab1]"
"V:|-20-[lab2]"
"H:|-20-[lab1]"
"H:[lab2]-20-|"
"H:[lab1(>=100)]-(>=20)-[lab2(>=100)]"
let p = lab2.contentCompressionResistancePriority(for: .horizontal)
lab1.setContentCompressionResistancePriority(p+1, for: .horizontal)
A stack view (UIStackView) is a view whose primary task is to generate constraints for some or all of its subviews.
叫做它的 its arranged subviews, 现实中,许多布局可能是嵌套的,简单的行或者列的子view,此时用stack view非常容易构建和维护。
Supply a stack view with arranged subviews by calling its initializer init(arrangedSubviews:).
The arranged subviews become the stack view’s arrangedSubviews read-only property
管理这些arranged subviews 的方法:
• addArrangedSubview(_:)
• insertArrangedSubview(_:at:)
• removeArrangedSubview(_:)
The arrangedSubviews array is different from, but is a subset of, the stack view’s subviews
关系:The order of the arrangedSubviews is independent of the order of the subviews
如何安排这些arranged subviews呢?
通过设置这些属性:
axis : (NSLayoutConstraint.Axis) .horizontal or .vertical
alignment: (UIStackView.Alignment) describes how the arranged subviews should be laid out with respect to the other dimension
• .fill
• .leading (or .top)
• .center
• .trailing (or .bottom)
• .firstBaseline or .lastBaseline (if the axis is .horizontal)
If the axis is .vertical, you can still involve the subviews’ baselines in their
spacing by setting the stack view’s isBaselineRelativeArrangement to true.
distribution: (UIStackView.Distribution) describe how the arranged subviews be positioned along the axis
.fill
.fillEqually
.fillProportionally
.equalSpacing
.equalCentering
isLayoutMarginsRelativeArrangement
If true, the stack view’s internal layoutMargins are involved in the positioning of its arranged subviews. If false (the default), the stack view’s literal edges are used.
注意: 不要手动添加约束来放置arranged subview, 否则会引起冲突, 然后,自己必须约束stack view 本身,否则layout engine无法知道做什么!
实现上面例子中的均匀分布效果:
// give the stack view arranged subviews
let sv = UIStackView(arrangedSubviews: views)
// configure the stack view
sv.axis = .vertical
sv.alignment = .fill
sv.distribution = .equalSpacing
// constrain the stack view
sv.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(sv)
let marg = self.view.layoutMarginsGuide
let safe = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
sv.topAnchor.constraint(equalTo:safe.topAnchor),
sv.leadingAnchor.constraint(equalTo:marg.leadingAnchor),
sv.trailingAnchor.constraint(equalTo:marg.trailingAnchor),
sv.bottomAnchor.constraint(equalTo:self.view.bottomAnchor),
])
可以看出,使用stack view 的首先好处是简单,并且它还更灵活,如果我们想让其中一个arranged subview不可见(设置isHidden)
那么剩余的子view将满足同样的布局效果,并且可以动态调整。
比如设置 views[0].isHidden = true 红条将不可见,另外3个仍然均匀分布,因此可以看出 StackView 方案优于 自定义的UILayoutGuide
Two kinds:
1) Conflict : constraints that can’t be satisfied simultaneously. This will be reported in the console (at great length) 约束多了
2) Underdetermination: haven’t supplied sufficient information to determine its size and position 约束少了
调试技巧:
1)冲突的话会在输出窗口显示出来:
比如:Unable to simultaneously satisfy constraints. Probably at least one of the
constraints in the following list is one you don't want...
2)二义性的话,view基本不会按期望的输出
With the app running, choose Debug → View Debugging → Capture View Hierarchy
Another useful trick is to pause in the debugger and give the following mystical command in the console:
(lldb) expr -l objc -O -- [[UIWindow keyWindow] _autolayoutTrace]
UIWindow:0x7fe8d0d9dbd0
| •UIView:0x7fe8d0c2bf00
| | +UIView:0x7fe8d0c2c290
| | | *UIView:0x7fe8d0c2c7e0
| | | *UIView:0x7fe8d0c2c9e0- AMBIGUOUS LAYOUT
UIView also has a hasAmbiguousLayout property. I find it useful to set up a utility method that lets me check a view and all its subviews at any depth for ambiguity
1)实现一个主view 有四个子view, 等分屏幕 中间间隔10像素,子view据主view标准间隙
可以用视觉格式来实现:
let v1 = UIView()
v1.backgroundColor = .red
v1.translatesAutoresizingMaskIntoConstraints = false
let v2 = UIView()
v2.backgroundColor = .yellow
v2.translatesAutoresizingMaskIntoConstraints = false
let v3 = UIView()
v3.backgroundColor = .blue
v3.translatesAutoresizingMaskIntoConstraints = false
let v4 = UIView()
v4.backgroundColor = .cyan
v4.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(v1)
self.view.addSubview(v2)
self.view.addSubview(v3)
self.view.addSubview(v4)
let d = ["v1":v1,"v2":v2,"v3":v3,"v4":v4]
NSLayoutConstraint.activate([
//(v1,v2)(v3,v4)等宽, (v1,v3)(v2,v4)等高, 标准间隙用破折号表示
NSLayoutConstraint.constraints(withVisualFormat: "H:|-[v1(v2)]-10-[v2]-|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "H:|-[v3(v4)]-10-[v4]-|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:|-[v1(v3)]-10-[v3]-|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:|-[v2(v4)]-10-[v4]-|", metrics: nil, views: d)
].flatMap{$0})
portait 效果, landscape 效果
目前view之间的间隙是10,若没有间隙可以使用 -0-表示 ,间隙是标准的 使用 -
portait 效果, landscape 效果 (v1,v2) 水平无间隙
NSLayoutConstraint.constraints(withVisualFormat: "H:|-[v1(v2)]-0-[v2]-|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "H:|-[v3(v4)]-[v4]-|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:|-[v1(v3)]-[v3]-|", metrics: nil, views: d),
NSLayoutConstraint.constraints(withVisualFormat: "V:|-[v2(v4)]-[v4]-|", metrics: nil, views: d)