2018-06-04 10:16:45 william66688 阅读数 316
  • 零练就iOS高手实战班

    iOS课程教学从入门到实战,系统讲解Swift编程,精讲基础语法,详解iOS基础框架,知识点涵盖新潮实用的swift,AppleWatch App开发。 咨询QQ:2528047463 咨询群:462917576 付费学员答疑群:446896569

    59280 人正在学习 去看看 花川学院

简书阅读《 iOS 布局揭秘》

在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题。通常这种问题发生的原因就是对于 view 何时真正更新的错误理解。想理解 view 在何时是如何更新的,需要对 iOS RunLoop 和相关的 UIView 方法有深刻的理解。这篇文章会介绍这些关联,希望能帮你澄清如何用 UIView 的方法来获得正确的行为。

一个 iOS 应用的主 RunLoop

一个 iOS 应用的主 RunLoop 负责处理所有的用户输入事件并触发相应的响应。所有的用户交互都会被加入到一个事件队列中。下图中的 Application object 会从队列中取出事件并将它们分发到应用中的其他对象上。本质上它会解释这些来自用户的输入事件,然后调用在应用中的 Core objects 相应的处理代码,而这些代码再调用开发者写的代码。当这些方法调用返回后,控制流回到主 RunLoop 上,然后开始 update cycle(更新周期)。Update cycle 负责布局并且重新渲染视图们(接下来会讲到)。下面的图片展示了应用是如何和设备交互并且处理用户输入的。

1.png

https://developer.apple.com/library/content/documentation/General/Conceptual/Devpedia-CocoaApp/MainEventLoop.html

Update Cycle

Update cycle 是当应用完成了你的所有事件处理代码后控制流回到主 RunLoop 时的那个时间点。正是在这个时间点上系统开始更新布局、显示和设置约束。如果你在处理事件的代码中请求修改了一个 view,那么系统就会把这个 view 标记为需要重画(redraw)。在接下来的 Update cycle 中,系统就会执行这些 view 上的更改。用户交互和布局更新间的延迟几乎不会被用户察觉到。iOS 应用一般以 60 fps 的速度展示动画,就是说每个更新周期只需要 1/60 秒。这个更新的过程很快,所以用户在和应用交互时感觉不到 UI 中的更新延迟。但是由于在处理事件和对应 view 重画间存在着一个间隔,RunLoop 中的某时刻的 view 更新可能不是你想要的那样。如果你的代码中的某些计算依赖于当下的 view 内容或者是布局,那么就有在过时 view 信息上操作的风险。理解 RunLoop、update cycle 和 UIView 中具体的方法可以帮助避免或者可以调试这类问题。下面的图展示出了 update cycle 发生在 RunLoop 的尾部。

2.png

布局

一个视图的布局指的是它在屏幕上的的大小和位置。每个 view 都有一个 frame 属性,用来表示在父 view 坐标系中的位置和具体的大小。UIView 给你提供了用来通知系统某个 view 布局发生变化的方法,也提供了在 view 布局重新计算后调用的可重写的方法。

layoutSubviews()

这个 UIView 方法处理对视图(view)及其所有子视图(subview)的重新定位和大小调整。它负责给出当前 view 和每个子 view 的位置和大小。这个方法很开销很大,因为它会在每个子视图上起作用并且调用它们相应的 layoutSubviews 方法。系统会在任何它需要重新计算视图的 frame 的时候调用这个方法,所以你应该在需要更新 frame 来重新定位或更改大小时重载它。然而你不应该在代码中显式调用这个方法。相反,有许多可以在 run loop 的不同时间点触发 layoutSubviews 调用的机制,这些触发机制比直接调用 layoutSubviews 的资源消耗要小得多。

当 layoutSubviews 完成后,在 view 的所有者 view controller 上,会触发 viewDidLayoutSubviews 调用。因为 viewDidLayoutSubviews 是 view 布局更新后会被唯一可靠调用的方法,所以你应该把所有依赖于布局或者大小的代码放在 viewDidLayoutSubviews 中,而不是放在 viewDidLoad 或者 viewDidAppear 中。这是避免使用过时的布局或者位置变量的唯一方法。

自动刷新触发器

有许多事件会自动给视图打上 “update layout” 标记,因此 layoutSubviews 会在下一个周期中被调用,而不需要开发者手动操作。这些自动通知系统 view 的布局发生变化的方式有:

  • 修改 view 的大小
  • 新增 subview
  • 用户在 UIScrollView 上滚动(layoutSubviews 会在 UIScrollView 和它的父 view 上被调用)
  • 用户旋转设备
  • 更新视图的 constraints

这些方式都会告知系统 view 的位置需要被重新计算,继而会自动转化为一个最终的 layoutSubviews 调用。当然,也有直接触发 layoutSubviews 的方法。

setNeedsLayout()

触发 layoutSubviews 调用的最省资源的方法就是在你的视图上调用 setNeedsLaylout 方法。调用这个方法代表向系统表示视图的布局需要重新计算。setNeedsLayout 方法会立刻执行并返回,但在返回前不会真正更新视图。视图会在下一个 update cycle 中更新,就在系统调用视图们的 layoutSubviews 以及他们的所有子视图的 layoutSubviews 方法的时候。即使从 setNeedsLayout 返回后到视图被重新绘制并布局之间有一段任意时间的间隔,但是这个延迟不会对用户造成影响,因为永远不会长到对界面造成卡顿。

layoutIfNeeded()

layoutIfNeeded 是另一个会让 UIView 触发 layoutSubviews 的方法。 当视图需要更新的时候,与 setNeedsLayout() 会让视图在下一周期调用 layoutSubviews 更新视图不同,layoutIfNeeded 会立即调用 layoutSubviews 方法。但是如果你调用了 layoutIfNeeded之后,并且没有任何操作向系统表明需要刷新视图,那么就不会调用 layoutsubview。如果你在同一个 run loop 内调用两次 layoutIfNeeded,并且两次之间没有更新视图,第二个调用同样不会触发 layoutSubviews 方法。

使用 layoutIfNeeded,则布局和重绘会立即发生并在函数返回之前完成(除非有正在运行中的动画)。这个方法在你需要依赖新布局,无法等到下一次 update cycle 的时候会比 setNeedsLayout 有用。除非是这种情况,否则你更应该使用 setNeedsLayout,这样在每次 run loop 中都只会更新一次布局。

当对希望通过修改 constraint 进行动画时,这个方法特别有用。你需要在 animation block 之前对 self.view 调用 layoutIfNeeded,以确保在动画开始之前传播所有的布局更新。在 animation block 中设置新 constrait 后,需要再次调用 layoutIfNeeded 来动画到新的状态。

显示

一个视图的显示包含了颜色、文本、图片和 Core Graphics 绘制等视图属性,不包括其本身和子视图的大小和位置。和布局的方法类似,显示也有触发更新的方法,它们由系统在检测到更新时被自动调用,或者我们可以手动调用直接刷新。

draw(_:)

UIView 的 draw 方法(本文使用 Swift,对应 Objective-C 的 drawRect)对视图内容显示的操作,类似于视图布局的 layoutSubviews ,但是不同于 layoutSubviewsdraw 方法不会触发后续对视图的子视图方法的调用。同样,和 layoutSubviews 一样,你不应该直接调用 draw 方法,而应该通过调用触发方法,让系统在 run loop 中的不同结点自动调用。

setNeedsDisplay()

这个方法类似于布局中的 setNeedsLayout 。它会给有内容更新的视图设置一个内部的标记,但在视图重绘之前就会返回。然后在下一个 update cycle 中,系统会遍历所有已标标记的视图,并调用它们的 draw 方法。如果你只想在下次更新时重绘部分视图,你可以调用 setNeedsDisplay(_:),并把需要重绘的矩形部分传进去(setNeedsDisplayInRectin OC)。大部分时候,在视图中更新任何 UI 组件都会把相应的视图标记为“dirty”,通过设置视图“内部更新标记”,在下一次 update cycle 中就会重绘,而不需要显式的 setNeedsDisplay 调用。然而如果你有一个属性没有绑定到 UI 组件,但需要在每次更新时重绘视图,你可以定义他的 didSet 属性,并且调用 setNeedsDisplay 来触发视图合适的更新。

有时候设置一个属性要求自定义绘制,这种情况下你需要重写 draw 方法。在下面的例子中,设置 numberOfPoints 会触发系统系统根据具体点数绘制视图。在这个例子中,你需要在 draw 方法中实现自定义绘制,并在 numberOfPoints 的 property observer 里调用 setNeedsDisplay

class MyView: UIView {
    var numberOfPoints = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        switch numberOfPoints {
        case 0:
            return
        case 1:
            drawPoint(rect)
        case 2:
            drawLine(rect)
        case 3:
            drawTriangle(rect)
        case 4:
            drawRectangle(rect)
        case 5:
            drawPentagon(rect)
        default:
            drawEllipse(rect)
        }
    }
}

视图的显示方法里没有类似布局中的 layoutIfNeeded 这样可以触发立即更新的方法。通常情况下等到下一个更新周期再重新绘制视图也无所谓。

约束

自动布局包含三步来布局和重绘视图。第一步是更新约束,系统会计算并给视图设置所有要求的约束。第二步是布局阶段,布局引擎计算视图和子视图的 frame 并且将它们布局。最后一步完成这一循环的是显示阶段,重绘视图的内容,如实现了 draw 方法则调用 draw

updateConstraints()

这个方法用来在自动布局中动态改变视图约束。和布局中的 layoutSubviews() 方法或者显示中的 draw 方法类似,updateConstraints() 只应该被重载,绝不要在代码中显式地调用。通常你只应该在 updateConstraints 方法中实现必须要更新的约束。静态的约束应该在 interface builder、视图的初始化方法或者 viewDidLoad() 方法中指定。

通常情况下,设置或者解除约束、更改约束的优先级或者常量值,或者从视图层级中移除一个视图时都会设置一个内部的标记 “update constarints”,这个标记会在下一个更新周期中触发调用 updateConstrains。当然,也有手动给视图打上“update constarints” 标记的方法,如下。

setNeedsUpdateConstraints()

调用 setNeedsUpdateConstraints() 会保证在下一次更新周期中更新约束。它通过标记“update constraints”来触发 updateConstraints()。这个方法和 setNeedsDisplay() 以及 setNeedsLayout() 方法的工作机制类似。

updateConstraintsIfNeeded()

对于使用自动布局的视图来说,这个方法与 layoutIfNeeded 等价。它会检查 “update constraints”标记(可以被 setNeedsUpdateConstraints 或者 invalidateInstrinsicContentSize方法自动设置)。如果它认为这些约束需要被更新,它会立即触发 updateConstraints() ,而不会等到 run loop 的末尾。

invalidateIntrinsicContentSize()

自动布局中某些视图拥有 intrinsicContentSize 属性,这是视图根据它的内容得到的自然尺寸。一个视图的 intrinsicContentSize 通常由所包含的元素的约束决定,但也可以通过重载提供自定义行为。调用 invalidateIntrinsicContentSize() 会设置一个标记表示这个视图的 intrinsicContentSize 已经过期,需要在下一个布局阶段重新计算。

它们是如何连接起来的

布局、显示和约束都遵循着相似的模式,例如他们更新的方式以及如何在 run loop 的不同时间点上强制更新。任一组件都有一个实际去更新的方法(layoutSubviewsdraw, 和 updateConstraints),你可以重写来手动操作视图,但是任何情况下都不要显式调用。这个方法只在 run loop 的末端会被调用,如果视图被标记了告诉系统该视图需要被更新的标记的话。有一些操作会自动设置这个标志,但是也有一些方法允许您显式地设置它。对于布局和约束相关的更新,如果你等不到在 run loop 末端才更新(例如:其他行为依赖于新布局),有方法可以让你立即更新,并保证 “update layout” 标记被正确标记。下面的表格列出了任意组件会怎样更新及其对应方法。

3.png

下面的流程图总结了 update cycle 和 event loop 之间的交互,并指出了上文提到的方法在 run loop 运行期间的位置。你可以在 run loop 中的任意一点显式地调用 layoutIfNeeded 或者 updateConstraintsIfNeeded,需要记住,这开销会很大。在循环的末端是 update cycle,如果视图被设置了特定的 “update constraints”,“update layout” 或者 “needs display” 标记,在这节点会更新约束、布局以及展示。一旦这些更新结束,runloop 会重新启动。



4.png

简书阅读《 iOS 布局揭秘》


2019-05-09 11:50:51 qq_14920635 阅读数 145
  • 零练就iOS高手实战班

    iOS课程教学从入门到实战,系统讲解Swift编程,精讲基础语法,详解iOS基础框架,知识点涵盖新潮实用的swift,AppleWatch App开发。 咨询QQ:2528047463 咨询群:462917576 付费学员答疑群:446896569

    59280 人正在学习 去看看 花川学院

iOS自定布局 NSLayoutAnchor

官网文档:NSLayoutAnchor

iOS 自动布局(AutoLayout)是iOS6 映入的一个布局特性。自动布局的主要实现方式如下

1. 基于Frame系统自动转换为AutoLayout

2. xib或者Storybord直接添加约束

3. 通过创建NSLayoutConstraint添加

4. 通过VFL语法创建约束组添加

5. NSLayoutAnchor(iOS 9.0)

AutoLayout约束原则

1. 视图的约束明确: 代表着你需要注意约束依赖视图层级关系,不要添加冲突和不确定约束。

2. View.translatesAutoresizingMaskIntoConstraints = false  // 自动转化约束取消

 

NSLayoutAnchor

NSLayoutAnchor 是对AutoLayout的补充,核心还是NSLayoutConstraint, 且不需要过长的代码长度。因为每次创建NSLayoutConstraint都要崩溃。

NSLayoutAnchor可以理解为约束描边。视图之间的边关系

注意:

1. 约束值constant的正负

2. leadingAnchor 和 trailingAnchor一对, leftAnchor与rightAnchor一对。

使用(分别设置anchor约束,分别生效)

 private func layoutAnchorTest(){
        let blueView = UIView()
        blueView.backgroundColor = .blue
        containView.addSubview(blueView)  // containView为橙色
        blueView.translatesAutoresizingMaskIntoConstraints = false
        // 使用NSLayoutAnchor 添加约束 并生效
        blueView.topAnchor.constraint(equalTo: containView.topAnchor, constant: 8).isActive = true
        blueView.leadingAnchor.constraint(equalTo: containView.leadingAnchor, constant: 8).isActive = true
        blueView.bottomAnchor.constraint(equalTo: containView.bottomAnchor, constant: -8).isActive = true    // 注意约束与数值正负关系
        blueView.trailingAnchor.constraint(equalTo: containView.trailingAnchor, constant: -8).isActive = true
        
    }

效果图

蓝色与橙色各边间距为8

除了分别设置,还可以分别设置anchor约束,统一生效(见UILayoutGuide使用)

UILayoutGuide

添加布局时,如果需要封装模块,通常做法是添加容器实体视图。除了这种做法,还可以使用UILayoutGuide。

UILayoutGuide:我的理解是相当于一个透明的约束空间。

使用

private func layoutGuideTest(){
        // layoutGuide相当 一个透明的约束空间
        
        let layoutGuide = UILayoutGuide()   // 1.创建一个约束空间(相当于一个透明的View)
        self.view.addLayoutGuide(layoutGuide)  // 2.添加到目标容器View
       
        // 3.为 layoutGuide添加约束
        /// 添加上左右约束和高度约束
        /// 约束要求: 左右上间隔8 高度为100
        layoutGuide.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 8).isActive = true
        layoutGuide.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 8).isActive = true
        layoutGuide.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8).isActive = true
        layoutGuide.heightAnchor.constraint(equalToConstant: 100).isActive = true
        
        
        let yellowView = UIView()
        //yellowView.frame = CGRect(x: 10, y: 10, width: 100, height: 200)
        yellowView.backgroundColor = .yellow
        self.view.addSubview(yellowView)
        yellowView.translatesAutoresizingMaskIntoConstraints = false  // 同样需要取消自动转化约束
        
        // 4.为guide空间的View添加约束(分别生生成anchor约束,统一生效)
        /// 约束要求: 左右上下为间隔8
        let topLayout = yellowView.topAnchor.constraint(equalTo: layoutGuide.topAnchor, constant: 8)
        let leftLayout = yellowView.leftAnchor.constraint(equalTo: layoutGuide.leftAnchor, constant: 8)
        let bottomLayout = yellowView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor, constant: -8)
        let rightLayout = yellowView.rightAnchor.constraint(equalTo: layoutGuide.rightAnchor, constant: -8)

        NSLayoutConstraint.activate([topLayout,leftLayout,bottomLayout,rightLayout])  //执行效率更好
    }

效果图

标题

 

参考:https://blog.csdn.net/weixin_33895516/article/details/87332196

2016-03-19 22:44:13 wf96390 阅读数 4715
  • 零练就iOS高手实战班

    iOS课程教学从入门到实战,系统讲解Swift编程,精讲基础语法,详解iOS基础框架,知识点涵盖新潮实用的swift,AppleWatch App开发。 咨询QQ:2528047463 咨询群:462917576 付费学员答疑群:446896569

    59280 人正在学习 去看看 花川学院

iOS有三种基本的界面布局的方法,分别是手写UI,xib和storyboard。手写UI是最早进行UI界面布局的方法,优点是灵活自由,缺点是需要写大段的代码进行布局。xib也是比较早出现的UI布局的方式,优点是不需要手写代码,但是每个界面对应一个xib,管理起来复杂。而storyboard则是在iOS5以后出现的,是苹果官方主推的一个代替xib的策略,不仅能将xib汇总统一管理,还可以描述各种场景之间的过渡,缺点是多人协作开发时容易产生冲突。

下面主要介绍的是手写页面布局。

一、AutoresizingMasks

可以使用 AutoresizingMasks 进行页面布局,在 UIView 中有一个autoresizingMask的属性,它对应的是一个枚举的值,属性的意思就是自动调整子控件与父控件中间的位置,宽高。默认值是UIViewAutoresizingNone,控件不会随父视图的改变而改变。

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
     UIViewAutoresizingNone = 0,
     UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 自动调整view与父视图左边距,以保证右边距不变
     UIViewAutoresizingFlexibleWidth = 1 << 1, // 自动调整view的宽度,保证左边距和右边距不变
     UIViewAutoresizingFlexibleRightMargin = 1 << 2, // 自动调整view与父视图右边距,以保证左边距不变
     UIViewAutoresizingFlexibleTopMargin = 1 << 3, // 自动调整view与父视图上边距,以保证下边距不变
     UIViewAutoresizingFlexibleHeight = 1 << 4, // 自动调整view的高度,以保证上边距和下边距不变
     UIViewAutoresizingFlexibleBottomMargin = 1 << 5 // 自动调整view与父视图的下边距,以保证上边距不变
}

AutoresizingMasks是对未来变化的一种预期,系统会生成frame的布局,当遇到需要使用到多个值的场景时,支持使用|操作符。
例如,需要设置播放器浮层随播放器大小变化:

UIView *overlay = [[UIView alloc] init];
overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:overlay];

Autoresizing需要注意的是,storyboard中设置的约束和手写代码中设置的约束是相反的。storyboard 图形页面里点的右边的线和下边的线的意思是“固定”。

二、Frame

frame指的是当前视图在其父视图中的位置和大小。
在初始化 view 的时候,可以设置 view 的frame

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)]; 

初始化一个距离父视图左边距10,上边距20,宽30,高40的视图。也可以修改声明View的位置以及大小。

view.frame = CGRectMake(20, 10, 40, 30);

设置和修改视图的 frame 可以完成对界面的布局。

2.1 bounds

提到 frame 不得不提 bounds,bounds指的是前视图在其自身坐标系统中的位置和大小。可以看到两者的区别在于坐标系不同。

2.2 layoutSubviews

需要重新布局视图可以使用 layoutSubviews

1)可以在view里重写layoutSubviews

2)可以在view controller里使用

viewWillLayoutSubviews 在autoresizingMasks前调用

viewDidLayoutSubviews 在autoresizingMasks后调用,肯定会覆盖autoresizingMasks的结果

layoutSubviews可能会在不需要调用的时候调用,如果layoutSubviews的比较复杂,可能会卡顿

三、自动布局AutoLayout

前面讲到的 frame 主要用于视图的绝对位置,但是 iOS 设备有多个尺寸,如何对不同尺寸进行适应,苹果的解决方案是使用 AutoLayout。

如果是从代码层面开始使用 Autolayout,需要对使用的 View 的translatesAutoresizingMaskIntoConstraints 的属性设置为NO。即可开始通过代码添加Constraint,否则View还是会按照以往的autoresizingMask进行计算。而在 Interface Builder 中勾选了Use Auto layout,translatesAutoresizingMaskIntoConstraints 属性都会被默认设置NO。

3.1 约束

自动布局里最重要的组成部分就是约束。分别可以设置视图相对于另一个视图的 leading、trailing、top、bottom、CenterX、CenterY 等关系。根据这些约束来确定视图的相对位置。

视图的约束之间的关系为线型关系。例如,视图Y 相对于 视图X 的位置可以表示为一个线性变换,即

Y = kX + b

即 Y 是 X 某个方向坐标或大小的 k 倍并偏移 b。k 和 b 的大小可以是0。如果 k = 1, b = 0, 则表示 Y 和 X 分别表示视图的宽,则等式表示 Y 和 X 的宽度相等。

AutoLayout 的核心是:Every view requires at least two constraints along each axis to set position and size. 即在每个坐标轴上至少需要2个约束来确定视图位置。

3.2 Ambiguous Layout

在开发过程中,你可以通过调用hasAmbiguousLayout 来测试你的view约束是否足够的。函数会返回布尔值。如果有一个不同的frame就会返回yes,如果view的约束完全指定了就会返回no。
一个设定了完全约束的view的子view也可能存在ambiguous layout,需要为每一个view单独测试layout是否存在ambiguous layout。

3.3 Intrinsic Content Size

使用autolayout时,view的content扮演着非常重要的角色。每个view的intrinsicContentSize描述了不会剪切的显示完整view content的最小空间。例如一个image view,content size根据image显示的size设置。一个大的image需要一个大的固有的content size。image的大小提供给了view。
对于button,固有的content size根据他的title而有不同。随着title增长或者缩短,button的固有的content size也会调节来做适应,可以根据你自定义的font size和title text而有变化。

3.4 Compression Resistance and Content Hugging

3.4.1 compression resistance

压缩阻力表示一个视图的抗压缩性。一个有高compression resistance的视图会防止被压缩。也不会允许content被裁剪,而会尝试保存他的最小固有content size。
autolayout经常遇到两个冲突的请求。当只有一个请求会成功时,他就会满足高优先级的那个。可以分别设置水平和垂直方向的Compression Resistance。value从1(最低)到1,000(请求的优先级)不等。默认的是750。

[button setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal]; 

3.4.2 content hugging

抗拉属性表示view防止被拉伸的属性,和压缩阻力类似。默认值为250。

[button setContentHuggingPriority:501 forAxis:UILayoutConstraintAxisHorizontal];

3.5 VFL

Visual Format Language,即“可视化格式语言”。直接手写约束很复杂,使用VFL相对简单很多,但比较难进行调试。

  [self.view addConstraints: [NSLayoutConstraint            
constraintsWithVisualFormat:@"V:[view1]-8-[view2]"             
                    options:NSLayoutFormatAlignAllLeading
                    metrics:nil
                      views:NSDictionaryOfVariableBindings(view1, view2)]];

3.6 Masonry

VFL的写法也相当复杂,可以使用第三方框架 Masonry,Masonry 是一个轻量级的布局框架,Masonry 源码:https://github.com/Masonry/Masonry

例如,设置view1相对父View的每个边距离为padding:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

需要注意的是,在结构一样的情况下用mas_updateConstraints,会更新当前的约束,但是如果要覆盖缘由约束重新添加,则需要使用方法用mas_remakeConstraints

constraint加到两个view的公共父view上,因此有一个奇怪的现象是一个view不持有自己的约束,而被其他view持有。在 Masonry 中,实际添加constraint的不一定是约束的持有者

四、更新布局方法

设置好约束以后,布局是如何更新的呢?

Constraints

- (void)updateConstraintsIfNeeded    // 立即重新计算约束,如果在这之前addConstraints,就可以更新约束
- (void)setNeedsUpdateConstraints   // 立即返回,标记说需要改变约束值,在当前update cycle结束后更新之前所有标记过要改变的约束,调用updateConstraints方法

Layout

- (void)layoutIfNeeded     // 立即更新布局,重新计算约束,如果在这之前addConstraints就会立即反应在页面上
- (void)setNeedsLayout    // 同Constraints,不过是更新布局
- (void)layoutSubviews    // 布局当前页面的子页面

Draw

- (void)setNeedsDisplay   // 同Constraints,不过是重新渲染

4.1 Constraints,Layout,Draw调用顺序

一个页面更新的顺序一般为
调用约束计算出frame(Constraints)→ 根据计算出的frame重新布局(Layout) → 根据重新布局的结果进行图像渲染(Draw)

layout的改变会导致重新计算Constraints
layoutIfNeeded会调用updateConstraintsIfNeeded

Constraints的计算顺序是低到上 (从subview到superview)
layout的更新顺序是从顶到下(从superview到subview)

4.2 frame和约束区别

1、frame是不可以累加的,只能被替换掉,但是constraint可以累加

2、frame不可以跨级添加,但是contraint可以跨层级

3、contraint可以设定priority

另外,需要注意的是,在autolayout下使用frame,会把frame转化成autolayout的约束,如果再进行约束的设置,由于多次累加可能会造成冲突

github博客:https://wf96390.github.io/blog/2016/03/16/autolayout/

2015-01-30 10:23:09 u012458905 阅读数 349
  • 零练就iOS高手实战班

    iOS课程教学从入门到实战,系统讲解Swift编程,精讲基础语法,详解iOS基础框架,知识点涵盖新潮实用的swift,AppleWatch App开发。 咨询QQ:2528047463 咨询群:462917576 付费学员答疑群:446896569

    59280 人正在学习 去看看 花川学院

作为一个新手,用了三个多月的时间用代码,以设frame的x,y,width,height写项目布局,奋斗写的又慢,又不好改动!已深有体会,现寻求一种新的方式!


要想自动布局,要确定两种关系

1.父子关系(父控件和子控件)

2.兄弟关系(兄弟控件)

引用参考自:http://www.cocoachina.com/ios/20140915/9623.html

从目前苹果提供的技术来看,有下、中、上三种实现方法:

 

下策是,代码中判断当前设备的尺寸,对UI元素进行手工的调整,其缺点是显而易见的:代码复杂、容易出错、且维护难度大、灵活性极差;

 

中策是,通过设置可视单元(UIView UIButton...)的autoresizing属性,预设当该view所在的环境(父view)发生变化时它的尺寸和位置应该如何调整,该方法可以在Xcode的interface builder中(storyboard 或者 xib)设置完成,但其只能针对父子关系进行有限的调整,比如左边距是否固定,尺寸是否可变等,而对于兄弟关系的调整则无法实现,对于UI比较固定的APP这种调节方式也算基本满足需求;

 

上策就是结合使用autolayout和sizeclass对UI可视单元的父子关系、兄弟关系进行全方位的调整,而且调节精度更高:不仅能确定一个view的位置尺寸的变化依据是什么,还能对这些依据加以不同的优先级,先满足什么条件,再满足什么条件,对于重要的位置尺寸可以优先保证,这样整个APP就具有极强的动态可调性,满足不同设备、不同应用场景下的需求。




AutoLayout是什么?

autolayout是基于约束,描述性的布局系统


AutoLayout的使用会和原先的frame布局的改变?

以前,不论是在IB里拖放,还是在代码中写,每个UIView都会有自己的frame属性,来定义其在当前视图中的位置和尺寸。


使用AutoLayout的话,就变为了使用约束条件来定义view的位置和尺寸。这样的最大好处是一举解决了不同分辨率和屏幕尺寸下view的适配问题,另外也简化了旋转时view的位置的定义,原来在底部之上10像素居中的view,不论在旋转屏幕或是更换设备(iPad或者iPhone5或者以后可能出现的mini iPad)的时候,始终还在底部之上10像素居中的位置,不会发生变化。


总结:使用约束条件来描述布局,view的frame会依据这些约束来进行计算

引用参考自:http://www.xuebuyuan.com/280852.html


AutoLayout和Autoresizing Mask的区别

我表示都没用过:下面这段话为我解释了:

Autoresizing Mask是我们的老朋友了…如果你以前一直是代码写UI的话,你肯定写过UIViewAutoresizingFlexibleWidth之类的枚举;如果你以前用IB比较多的话,一定注意到过每个view的size inspector中都有一个红色线条的Autoresizing的指示器和相应的动画缩放的示意图,这就是Autoresizing Mask。在iOS6之前,关于屏幕旋转的适配和iPhone,iPad屏幕的自动适配,基本都是由Autoresizing Mask来完成的。但是随着大家对iOS app的要求越来越高,以及已经以及今后可能出现的多种屏幕和分辨率的设备来说,Autoresizing Mask显得有些落伍和迟钝了。AutoLayout可以完成所有原来Autoresizing Mask能完成的工作,同时还能够胜任一些原来无法完成的任务,其中包括:

  • AutoLayout可以指定任意两个view的相对位置,而不需要像Autoresizing Mask那样需要两个view在直系的view hierarchy中。
  • AutoLayout不必须指定相等关系的约束,它可以指定非相等约束(大于或者小于等);而Autoresizing Mask所能做的布局只能是相等条件的。
  • AutoLayout可以指定约束的优先级,计算frame时将优先按照满足优先级高的条件进行计算。
总结:

Autoresizing Mask是AutoLayout的子集,任何可以用Autoresizing Mask完成的工作都可以用AutoLayout完成。AutoLayout还具备一些Autoresizing Mask不具备的优良特性,以帮助我们更方便地构建界面。

手动使用API添加约束

创建

iOS6中新加入了一个类:NSLayoutConstraint,一个形如这样的约束

 

 例1:

  • item1.attribute = multiplier ⨉ item2.attribute + constant

对应的代码为

 

 

[NSLayoutConstraint constraintWithItem:button
                             attribute:NSLayoutAttributeBottom
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeBottom
                            multiplier:1.0
                              constant:-padding]

 

这对应的约束是“button的底部(y) = superview的底部 -10”。


例2:

[NSLayoutConstraint constraintWithItem:btn2  

                                  attribute:NSLayoutAttributeTop  

                                         //要设定的属性  

                                  relatedBy:NSLayoutRelationGreaterThanOrEqual                                  

                                         //大于还是小于相对的View的值  

                                    toItem:btn1   

                                         //相对于某个View或者控件  

                                  attribute:NSLayoutAttributeTop  

                                      //指定要设定的关联View的属性  

                                 multiplier:1   //因子值  

                                   constant:0]];




 

 

添加

在创建约束之后,需要将其添加到作用的view上。UIView(当然NSView也一样)加入了一个新的实例方法:

 

 

  • -(void)addConstraint:(NSLayoutConstraint *)constraint;

用来将约束添加到view。在添加时唯一要注意的是添加的目标view要遵循以下规则:

 

 

  • 对于两个同层级view之间的约束关系,添加到他们的父view上

 

 

  • 对于两个不同层级view之间的约束关系,添加到他们最近的共同父view上

 

 

  • 对于有层次关系的两个view之间的约束关系,添加到层次较高的父view上



Visual Format Language 可视格式语言

UIKit团队这次相当有爱,估计他们自己也觉得新加约束的API名字太长了,因此他们发明了一种新的方式来描述约束条件,十分有趣。这种语言是对视觉描述的一种抽象,大概过程看起来是这样的:

accept按钮在cancel按钮右侧默认间距处

最后使用VFL(Visual Format Language)描述变成这样:

 

 

[NSLayoutConstraint constraintsWithVisualFormat:@"[cancelButton]-[acceptButton]" 
                                        options:0 
                                        metrics:nil 
                                          views:viewsDictionary];

 

其中viewsDictionary是绑定了view的名字和对象的字典,对于这个例子可以用以下方法得到对应的字典:

 

 

UIButton *cancelButton = ... 
UIButton *acceptButton = ... 
viewsDictionary = NSDictionaryOfVariableBindings(cancelButton,acceptButton);

 

生成的字典为

 

 

{ acceptButton = ""; cancelButton = ""; } 

当然,不嫌累的话自己手写也未尝不可。现在字典啊数组啊写法相对简化了很多了,因此也不复杂。关于Objective-C的新语法,可以参考我之前的一篇WWDC 2012笔记:WWDC 2012 Session笔记——405 Modern Objective-C

在view名字后面添加括号以及连接处的数字可以赋予表达式更多意义,以下进行一些举例:

 

 

  • [cancelButton(72)]-12-[acceptButton(50)]
    • 取消按钮宽72point,accept按钮宽50point,它们之间间距12point
  • [wideView(>=60@700)]
    • wideView宽度大于等于60point,该约束条件优先级为700(优先级最大值为1000,优先级越高的约束越先被满足)
  • V:[redBox][yellowBox(==redBox)]
    • 竖直布局,先是一个redBox,其下方紧接一个宽度等于redBox宽度的yellowBox
  • H:|-[Find]-[FindNext]-[FindField(>=20)]-|
    • 水平布局,Find距离父view左边缘默认间隔宽度,之后是FindNext距离Find间隔默认宽度;再之后是宽度不小于20的FindField,它和FindNext以及父view右边缘的间距都是默认宽度。(竖线’|‘ 表示superview的边缘)

容易出现的错误

因为涉及约束问题,因此约束模型下的所有可能出现的问题这里都会出现,具体来说包括两种:

 

 

  • Ambiguous Layout 布局不能确定
  • Unsatisfiable Constraints 无法满足约束

布局不能确定指的是给出的约束条件无法唯一确定一种布局,也即约束条件不足,无法得到唯一的布局结果。这种情况一般添加一些必要的约束或者调整优先级可以解决。无法满足约束的问题来源是有约束条件互相冲突,因此无法同时满足,需要删掉一些约束。两种错误在出现时均会导致布局的不稳定和错误,Ambiguous可以被容忍并且选择一种可行布局呈现在UI上,Unsatisfiable的话会无法得到UI布局并报错。

对于不能确定的布局,可以通过调试时暂停程序,在debugger中输入

 

 

  • po [[UIWindow keyWindow] _autolayoutTrace]

来检查是否存在Ambiguous Layout以及存在的位置,来帮助添加条件。另外还有一些检查方法,来查看view的约束和约束状态:

 

 

  • [view constraintsAffectingLayoutForOrientation/Axis: NSLayoutConstraintOrientationHorizontal/Vertical]
  • [view hasAmbiguousLayout]
    • [view exerciseAmbiguityInLayout]

 



 


 

 

布局动画

动画是UI体验的重要部分,更改布局以后的动画也非常关键。说到动画,Core Animation又立功了..自从CA出现以后,所有的动画效果都非常cheap,在auto layout中情况也和collection view里一样,很简单(可以参考WWDC 2012 Session笔记——219 Advanced Collection Views and Building Custom Layouts),只需要把layoutIfNeeded放到animation block中即可~

 

 

[UIView animateWithDuration:0.5 animations:^{
    [view layoutIfNeeded];
}];



再推荐一篇博客的地址:

http://blog.csdn.net/mozixiong/article/details/14165391

其中我觉得很不错:

1.Visual format language (应该不算语言)

Apple的工程师很有爱,发明了这种哭笑不得的象形文字。感觉它就是种解析方式。

Apple的官方文档给出了少之又少的文档和坑爹的例子。(视频我没看,不知道怎么样)网上的同学们也写了点不痛不痒的几句代码。你这是学了1+1就让人搞微积分的节奏么?要写不写清楚,不如不写。

从厚道的老外那看了几篇。自己体会了些,分享给大家。只是个基本水平吧,会了这些,代码写al应该没有问题了。深入的我也还不会,希望遇到更有爱的牛人分享。

VFL在程序中由支持:

  1. + (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;  

- 它返回一组constraint. 

- format是你的VFL字串,等下讲。

- opts自己点去头文件看。有些情况会用,等下有例子。

- metrics是一个奇妙的字典,是你自己定义的。这个字典里面的key可以写在format字串中。编译器解析时,自动替换为metrics字典中的value。等下有例子。

- views是需要constraint关系的所有view.(也可以是一个)


2.VFL例子

写vfl字串的时候,脑海里要想象出画面的合理性。不合理的constraint会导致程序运行时报错或者直接崩溃。

随便写几个

  1. NSDictionary *dict1 = NSDictionaryOfVariableBindings(_boxV,_headerL,_imageV,_backBtn,_doneBtn);  
  2. NSDictionary *metrics = @{@"hPadding":@5,@"vPadding":@5,@"imageEdge":@150.0};  
  3. NSString *vfl = @"|-hPadding-[_boxV]-hPadding-|";  
  4. NSString *vfl0 = @"V:|-25-[_boxV]";  
  5. NSString *vfl3 = @"V:|-vPadding-[_headerL]-vPadding-[_imageV(imageEdge)]-vPadding-[_backBtn]-vPadding-|";  

dict1就是api 中需要的最后一个参数views。由上述宏来完成。

metrics定义了一些vfl中要用的参数。

下面有些vfl字串,一看便知如何使用metrics。

看到:

1)"|"表示superview. 

    |-间距-[view1对象名]-(>=20)-[view2对象名]

    不写H/V就表示横向,间距可以写固定值也可写>/<。

    形象化的理解,"|"是用来确定view上、下、左、右关系的。

    想要确定从上到下的关系,就加V:|。那么这个vfl字串就可以描述从上到下的view们的关系。

2)看到vfl3里面,方括号表示view,圆括号表示尺寸数值。支持大小等于。或者另一个view |-[view1(view2)],v1的宽度等于v2。

3)优先级用@表示。如V:|-50@750-[view(55)],或者写到metrics里面更好。

    具体定义查看UILayoutPriority。有几个固定的数值。1000表示必须支持。

4)options,这个要看具体需要。如果是竖排V布局,可以添加NSLayoutFormatAlignAllLeft,让他们对齐。

    根据需要也可以添加按位或NSLayoutFormatAlignAllLeft | NSLayoutFormatAlignAllRight。(鬼知道什么需要,自己看经验吧)

5)写好以后一般把constraint添加给superview:

  1. NSString *vfl1 = @"|-hPadding-[_headerL]-hPadding-|";  
  2. [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl1 options:0 metrics:metrics views:dict1]];  

6)还有一个api用于生成单个constaint
  1. +(id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;  
对于参数,记得,view1.attr1 = view2.attr2 * multiplier + constant就好。

这个是不用VFL的,好理解,但是不方便。如果用这个写。工作两不会比传统布局少多少。


实际操作中的问题

上面都是理论,世界操作会有些奇怪的问题要注意。这节才是重点。

xib模式,没啥要注意的,xib里报warning就报吧,我也不知道怎么弄,一切正常就好。

编码模式中,

1.addConstraint(s)前,view应该去部被addSubView上去了。

2.不必给views写frame

3.给必要的view关掉AutoresizeingMask。[_aView setTranslatesAutoresizingMaskIntoConstraints:NO];

4.UILabel换行要写linebreakMode,要写numberOfLines(iOS7.0默认好像是1,坑爹了)

5.UILabel要想换行,一定要添加preferredMaxLayoutWidth。否则没法初始化宽度。


编码模式感受到的最大方便。label换行不用写行高计算了。完全自动适应。label所在的superview也会自动计算rect。这才是al的精华。

所以,可以不用写这些了:

  1. /* 
  2.     if([[UIDevice currentDevice].systemVersion floatValue]<7.0){ 
  3.         CGSize titleS = [title sizeWithFont:[_headerL font] 
  4.                            constrainedToSize:CGSizeMake(270.0, CGFLOAT_MAX) 
  5.                                lineBreakMode:NSLineBreakByWordWrapping]; 
  6.          
  7.         _headerL.frame = CGRectMake(_headerL.frame.origin.x, _headerL.frame.origin.y, 
  8.                                     _headerL.frame.size.width, titleS.height); 
  9.     }else{ 
  10.         CGRect titleR = [title boundingRectWithSize:CGSizeMake(270.0, CGFLOAT_MAX) 
  11.                                             options:NSStringDrawingUsesLineFragmentOrigin 
  12.                                          attributes:nil 
  13.                                             context:nil]; 
  14.         headerL.frame = CGRectMake(_headerL.frame.origin.x, _headerL.frame.origin.y, 
  15.                                    _headerL.frame.size.width, titleR.size.height); 
  16.     } 
  17.     */ 

AutoLayout与安卓布局的相似性与不同?



2015-09-03 10:13:03 luoye2486 阅读数 184
  • 零练就iOS高手实战班

    iOS课程教学从入门到实战,系统讲解Swift编程,精讲基础语法,详解iOS基础框架,知识点涵盖新潮实用的swift,AppleWatch App开发。 咨询QQ:2528047463 咨询群:462917576 付费学员答疑群:446896569

    59280 人正在学习 去看看 花川学院


    ios 7 使用全屏的布局,其实位置从(0,0)算起,ios 6 从导航栏下面开始算起(0,64),原因是ios7 导航栏、状态栏不占用实际空间,而且都是透明的。

    如果我们设置导航栏不透明,ios7就会从导航栏下面开始计算

self.navigationController.navigationBar.translucent = NO;

关于iOS自动布局

阅读数 1954

iOS 关于自动布局 自适应宽高

博文 来自: dashenid

iOS代码布局

阅读数 480

没有更多推荐了,返回首页