Deep understand iOS view(二)

接着一,继续看变换,先旋转后平移再逆旋转(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 效果图

 

 skew (shear) transform

v1.transform = CGAffineTransform(a:1, b:0, c:-0.2, d:1, tx:0, ty:0)

结果看起来是个平行四边形

 

 

The View Environment

view -> window -> screen

这里主要看看view 和周围环境的关系

 

Window Coordinates and Screen Coordinates 窗口坐标和屏幕坐标

设备是没有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的边界自动调整

 

Trait Collections and Size Classes

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

Layout

我们已经看到父view bounds原点改变时,子view改变位置,如果父view的bounds size or  frame改变会怎么样?不会改变,所以需要Layout,  

Layout 种类:

1)手动 manual

原理:superview is sent the layoutSubviews message whenever it is resized

在以下情况都会调用

  • 1.直接调用[self setNeedsLayout];(这个在上面苹果官方文档里有说明)
  • 2.addSubview的时候。
  • 3.当view的size发生改变的时候。
  • 4.滑动UIScrollView的时候。
  • 5.旋转Screen会触发父UIView上的layoutSubviews事件。

2)Autoresizing

  • autoresizingMask property value
  • oldest way of performing layout automatically
  • describes the resizing relationship between the subview and its superview.

3)Autolayout

  • Autolayout depends on the constraints of views: an instance of NSLayoutConstraint
  • describing some aspect of the size or position of a view, often in terms of some other view
  • describe a relationship between any two views
  • Autolayout is implemented behind the scenes in layoutSubviews

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

  • A view that your code creates and adds to the interface, by default, uses autoresizing, not autolayout.
  • must deliberately suppress its use of autoresizing

In a nib file

  • All new .storyboard and .xib files opt in to autolayout
  • examine the “Use Auto Layout” checkbox
  • a view in the nib editor can still use autoresizing

 

Autoresizing (frame + autoresizingMask)

  • conceptually assigning a subview “springs and struts.”A spring can stretch; a strut can’t
  • Springs and struts can be assigned internally or externally, horizontally or vertically.
  • using internal springs and struts whether and how the view can be resized
  • using external springs and struts whether and how the view can be repositioned

例子

1)考虑一个按钮在父view的右下角:需要设置: 

  1. struts internally
  2. struts externally to its right and bottom
  3. springs externally to its top and left

2)考虑一个文本框在父view 的上面,宽度和父view相等:需要设置

  1. struts externally,spring to its bottom
  2. internally it would have a vertical strut and a horizontal spring

代码实现方法:

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:

 

  1. Combine it with manual layout in layoutSubviews,Autoresizing happens before layoutSubviews is called
  2. Use autolayout.

 

Autolayout and Constraints

  • Autolayout is an opt-in technology, at the level of each individual view
  • autolayout is implemented through the superview chain, so if a view uses autolayout, then automatically so do all its superviews
  • view controller receives autolayout-related events.

how does a view opt in to using autolayout?   :使用约束Constraints,约束通知autolayout 引擎如何布局view

Constraint

  • is an NSLayoutConstraint instance
  • describes either the absolute width or height of a view or
  • else a relationship between an attribute of one view and an attribute of another view.(不必是父子view) 但要求有公共父节点

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):

  •  .equal
  •  .lessThanOrEqual
  • .greaterThanOrEqual

 

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属性 和相关方法

  • addConstraint(_:), addConstraints(_:)
  • removeConstraint(_:), removeConstraints(_:)

However, you’ll probably never call any of those methods!, 从Ios8开始,可以 activate the constraint

  • using the NSLayoutConstraint class method activate(_:), which takes an array of constraints.  
  • deactivate(_:), which removes constraints from their view.
  • isActive property; you can set it to activate or deactivate a single 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

 

Creating Constraints in Code

方式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

  1. another anchor,
  2. another anchor and a constant,
  3. another anchor and a multiplier,
  4. another anchor and both a constant and a multiplier, or a constant alone

• 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

优点:

  1. allowing you to describe multiple constraints simultaneously
  2. appropriate particularly when you’re arranging a series of views horizontally or vertically

"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).

 

Constraints as Objects

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)
}

 

 

Margins and Guides

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.

 

Safe area

  • An important set of secondary edges, is a feature of a UIView
  • but it is imposed by the UIViewController that manages this view
  • One reason the safe area is needed is that the top and bottom of the interface are often occupied by a bar (status bar, navigation bar, toolbar, tab bar)
  • To solve this problem, a UIViewController imposes the safe area on its main view
  • The top of the safe area matches the bottom of the lowest top bar, or the top of the main view if there is no top bar
  • The bottom of the safe area matches the top of the bottom bar, or the bottom of the main view if there is no bottom bar
  • The safe area changes as the situation changes — when the top or bottom bar changes its height, or vanishes entirely.

实际项目中, 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

 

Margins

  • A view also has margins of its own, you are free to set an individual view’s margins
  • Unlike the safe area, which propagates down the view hierarchy from the view controller
  • By default, a view has a margin of 8 on all four edges
  • a subview might be positioned with respect to its superview’s margins, especially through an autolayout constraint

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

 

Custom layout guides

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的布局问题,后面即将会看到。

 

Intrinsic Content Size and Alignment Rects

有一些内置的界面元素,使用自动布局时候会有一维或二维方面固定大小,比如:

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)

 

Stack Views

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

  • subviews order, determines the order in which the subviews are drawn
  • arrangedSubviews order determines how the stack view will position those 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

  1. The stack view’s spacing property determines the spacing, can set the spacing for individual views by calling setCustomSpacing(_:after:),
  2. reverting to the overall spacing property value:set the custom spacing to UIStackView.spacingUseDefault
  3. To impose the spacing that the system would normally impose, set the spacing to UIStackView.spacingUseSystem.

 

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

 

Mistakes with Constraints

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   约束少了

  • Only .required constraints (priority 1000) can contribute to a conflict,ignore lower-priority constraints that it can’t satisfy
  • Constraints with different priorities do not conflict with one another.
  • Nonrequired constraints with the same priority can contribute to ambiguity

调试技巧:

1)冲突的话会在输出窗口显示出来:

比如:Unable to simultaneously satisfy constraints. Probably at least one of the
constraints in the following list is one you don't want...
<NSLayoutConstraint:0x60008b6d0 UIView:0x7ff45e803.height == + 20 (active)>,
<NSLayoutConstraint:0x60008bae0 UIView:0x7ff45e803.height == + 10 (active)>

2)二义性的话,view基本不会按期望的输出

With the app running, choose Debug → View Debugging → Capture View Hierarchy

  1. The exclamation mark in the Debug navigator, at the left, is telling us that this view has ambiguous layout;
  2. the Issue navigator, in the Runtime pane, tells us more explicitly, in words: “Height and vertical position are ambiguous for UIView

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)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值