2018-06-04 10:16:45 william66688 阅读数 326
  • iOS/Swift/OC/Objective-C/Xcode/0基础/入门

    这是一门快速入门iOS开发的课程,目的是让大家快速学会,iOS开发环境搭建,和iOS一些基础知识,最后完成一个小项目。 项目信息 提供完整的Git提交历史,和每节视频一一对应,目前有41次提交,355行注释,271行代码(不包含可视化布局文件,纯Swift和Objective-C代码)。

    3962 人正在学习 去看看 任苹蜻

简书阅读《 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 布局揭秘》


2016-03-19 22:44:13 wf96390 阅读数 4801
  • iOS/Swift/OC/Objective-C/Xcode/0基础/入门

    这是一门快速入门iOS开发的课程,目的是让大家快速学会,iOS开发环境搭建,和iOS一些基础知识,最后完成一个小项目。 项目信息 提供完整的Git提交历史,和每节视频一一对应,目前有41次提交,355行注释,271行代码(不包含可视化布局文件,纯Swift和Objective-C代码)。

    3962 人正在学习 去看看 任苹蜻

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/

2016-04-18 11:39:42 fengqiaoyebo2008 阅读数 508
  • iOS/Swift/OC/Objective-C/Xcode/0基础/入门

    这是一门快速入门iOS开发的课程,目的是让大家快速学会,iOS开发环境搭建,和iOS一些基础知识,最后完成一个小项目。 项目信息 提供完整的Git提交历史,和每节视频一一对应,目前有41次提交,355行注释,271行代码(不包含可视化布局文件,纯Swift和Objective-C代码)。

    3962 人正在学习 去看看 任苹蜻

初学iOS开发,请教做iOS开发多年的朋友使用代码布局的居多,赶紧又问了下度娘,发现使用storyboard和nib等布局有诸多不便,遂决定试一把代码布局,虽然iOS不想android那样多种多样的分辨率,但现在手机也有4个不同分辨率要兼容,好在水果公司出了autoLayout,话不多说直接上代码

for (UIView *view in _userView.subviews) {view.translatesAutoresizingMaskIntoConstraints = NO;}
    
   [_userView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(10)-[_avatar(80)]-(5)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_avatar)]];
    [_userView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[_avatar(80)]-(10)-[_userName]-(10)-|"
                                                                     options:0 metrics:nil views:NSDictionaryOfVariableBindings(_avatar, _userName)]];
    
    [_userView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(15)-[_userName]-(10)-[_userPhone]-(5)-[_inviteCode]"
                                                                     options:NSLayoutFormatAlignAllLeft
                                                                     metrics:nil views:NSDictionaryOfVariableBindings( _userName, _userPhone, _inviteCode)]];

折腾了整整半天弄出来的什么鬼,感觉好难用的说,找找有没有其他开源的,终于找到了一个不错的Masonry

使用起来也是很人性化,我们来看一下使用后的代码

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"LoginViewController viewDidLoad");
    
    //防止block中的循环引用
    __weak typeof(self) weakSelf = self;
    
    UIView *mainView = [UIView new];
    [self.view addSubview:mainView];
    
    float width = [Utils getScreenWidth] - MARGIN * 2;
    //使用mas_makeConstraints添加约束
    [mainView mas_makeConstraints:^(MASConstraintMaker *make) {
       //添加大小约束(make就是要添加约束的控件view)
        make.size.mas_equalTo(CGSizeMake(width, [Utils getScreenHeight]/3*2));
        make.center.equalTo(weakSelf.view);
    }];
    
    //设置logo
    UIImage * img_logo = [UIImage imageNamed:@"logo"];
    UIImageView *logo = [[UIImageView alloc]initWithImage:img_logo];
    [mainView addSubview:logo];
    
    [logo mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(80, 80));
        make.centerX.equalTo(mainView);
        make.top.mas_equalTo(0);
    }];
    
    //设置手机号输入
    UIImageView *img_phone = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_phone"]];
    UILabel *label_phone = [UILabel new];
    [label_phone setText:@"手机号"];
    label_phone.font = [UIFont systemFontOfSize:12];
    textFiled_phone = [UITextField new];
    textFiled_phone.clearsOnBeginEditing = YES;
    textFiled_phone.placeholder = @"请输入手机号码";
    textFiled_phone.font = [UIFont systemFontOfSize:12];
    textFiled_phone.keyboardType = UIKeyboardTypeNumberPad;
    
    
    [mainView addSubview:img_phone];
    [mainView addSubview:label_phone];
    [mainView addSubview:textFiled_phone];
    
    [img_phone mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(13, 15));  //大小是13x15
        make.top.equalTo(logo.mas_bottom).with.offset(50); //距离logo的底部是30
        make.left.mas_equalTo(0); //距离上层view的左边界是0
    }];
    
    [label_phone mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(40, 20));
        make.centerY.equalTo(img_phone);
        make.left.equalTo(img_phone.mas_right).with.offset(2);
    }];
    
    [textFiled_phone mas_makeConstraints:^(MASConstraintMaker *make) {
        make.height.mas_equalTo(20);
        make.right.mas_equalTo(0);
        make.centerY.equalTo(img_phone); //对齐
        make.left.equalTo(label_phone.mas_right).with.offset(15);
    }];
    
    //分割线
    UIView *divider = [UIView new];
    [mainView addSubview:divider];
    [divider setBackgroundColor:[UIColor color_divider]];
    [divider mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(width, 1));
        make.top.equalTo(textFiled_phone.mas_bottom).with.offset(3);
        make.left.mas_equalTo(0);
    }];
    
    //设置密码输入
    UIImageView *img_password = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_password"]];
    UILabel *label_password = [UILabel new];
    [label_password setText:@"密码"];
    label_password.font = [UIFont systemFontOfSize:12];
    textFiled_password = [UITextField new];
    textFiled_password.clearsOnBeginEditing = YES;
    textFiled_password.placeholder = @"请输入密码";
    textFiled_password.font = [UIFont systemFontOfSize:12];
    [textFiled_password setSecureTextEntry:YES];
    
    
    [mainView addSubview:img_password];
    [mainView addSubview:label_password];
    [mainView addSubview:textFiled_password];
    
    [img_password mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(13, 15));  //大小是13x15
        make.top.equalTo(img_phone.mas_bottom).with.offset(20); //距离phone的底部是20
        make.left.mas_equalTo(0); //距离上层view的左边界是0
    }];
    
    [label_password mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(40, 20));
        make.centerY.equalTo(img_password);
        make.left.equalTo(img_password.mas_right).with.offset(2);
    }];
    
    [textFiled_password mas_makeConstraints:^(MASConstraintMaker *make) {
        make.height.mas_equalTo(20);
        make.right.mas_equalTo(0);
        make.centerY.equalTo(img_password);
        make.left.equalTo(label_password.mas_right).with.offset(15);
    }];
    
    //分割线
    UIView *divider2 = [UIView new];
    [mainView addSubview:divider2];
    [divider2 setBackgroundColor:[UIColor color_divider]];
    [divider2 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(width, 1));
        make.top.equalTo(textFiled_password.mas_bottom).with.offset(3);
        make.left.mas_equalTo(0);
    }];
    
    //登录按钮布局
    UIButton *btn_login = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn_login setTitle:@"登   录" forState:UIControlStateNormal]; //设置按钮的文字
    [btn_login setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; //设置按钮文字的颜色
    //设置按钮的背景色
    [btn_login setBackgroundColor:[UIColor color_blue]];
    //设置圆角
    [btn_login.layer setMasksToBounds:YES];
    [btn_login.layer setCornerRadius:10.0];
    //设置onclick事件
    [btn_login addTarget:self action:@selector(login) forControlEvents:UIControlEventTouchUpInside];
    
    [mainView addSubview:btn_login];
    
    [btn_login mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(width-30, 30));
        make.centerX.equalTo(mainView);
        make.top.equalTo(textFiled_password.mas_bottom).with.offset(30);
    }];
    
    
    //忘记密码按钮布局
    UIButton *btn_forgetpsd = [UIButton new];
    [btn_forgetpsd setTitle:@"忘记密码?" forState:UIControlStateNormal];
    [btn_forgetpsd setTitleColor:[UIColor color_text_gray] forState:UIControlStateNormal];
    [btn_forgetpsd setBackgroundColor:[UIColor clearColor]];
    btn_forgetpsd.titleLabel.font = [UIFont systemFontOfSize:10]; //设置文字大小
    btn_forgetpsd.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;//文字靠右
    [mainView addSubview:btn_forgetpsd];
    
    [btn_forgetpsd mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(80, 15));
        make.right.equalTo(btn_login);
        make.top.equalTo(btn_login.mas_bottom).with.offset(5);
    }];
    [btn_forgetpsd addTarget:self action:@selector(forgetPassword) forControlEvents:UIControlEventTouchUpInside];
    
    //注册
    UIButton *btn_register = [UIButton new];
    [btn_register setTitle:@"还没有账号?现在去注册" forState:UIControlStateNormal];
    [btn_register setTitleColor:[UIColor color_text_gray] forState:UIControlStateNormal];
    btn_register.titleLabel.font = [UIFont systemFontOfSize:12];
    btn_register.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
    [btn_register addTarget:self action:@selector(userRegister) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn_register];
    
    [btn_register mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(150, 15));
        make.centerX.equalTo(weakSelf.view);
        make.bottom.equalTo(weakSelf.view).offset(-[Utils getScreenHeight]/8); //距离屏幕底部,取负值
    }];
    
    //分割线
    UIView *divider3 = [UIView new];
    [self.view addSubview:divider3];
    [divider3 setBackgroundColor:[UIColor color_divider]];
    [divider3 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(width-30, 1));
        make.top.equalTo(btn_register.mas_bottom).with.offset(3);
        make.centerX.equalTo(weakSelf.view);
    }];
    

效果图如下:



代码虽然多了一些,但逻辑很清晰


2016-10-17 15:27:27 forwardto9 阅读数 2062
  • iOS/Swift/OC/Objective-C/Xcode/0基础/入门

    这是一门快速入门iOS开发的课程,目的是让大家快速学会,iOS开发环境搭建,和iOS一些基础知识,最后完成一个小项目。 项目信息 提供完整的Git提交历史,和每节视频一一对应,目前有41次提交,355行注释,271行代码(不包含可视化布局文件,纯Swift和Objective-C代码)。

    3962 人正在学习 去看看 任苹蜻

作为iOS UI开发人员,为了适配多种机型以及横竖屏,【布局】的概念是再熟悉不过的,iOS的布局发展到今天主要有以下几种方式:

1.Absolutely Position

2.AutoResizing

3.NSLayoutConstraint

4.UIStackView

 

为了布局,就要考虑App支持的设备方向和各控件间的位置关系。根据这两个限制条件来讨论下面的技术点:

 

Absolutely Position

 

如果控件的位置是可以根据固定的控件可计算的,那么使用绝对位置是没有什么问题的,例如代码:

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.bounds = CGRectMake(0, 0, 50, 50);
[self.view addSubview:button];

 

那么,其他一些控件可以根据这个button的frame属性来动态适配frame

 

优点:

简单而快速

 

缺点:

修改比较困难

 

AutoResizing

 

这是UIView类的属性,当父控件发生变化,这个属性可以决定子控件如何调整自己的size。它是一个整数位掩码。

它的枚举结构:

typedef enum UIViewAutoresizing : NSUInteger {
    UIViewAutoresizingNone = 0,
    UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
    UIViewAutoresizingFlexibleWidth = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin = 1 << 3,
    UIViewAutoresizingFlexibleHeight = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
} UIViewAutoresizing;

 

每一个单词中都有“Flexible”这个单词,意味着,你用了某个枚举,当父控件发生变化之后,子空间对应的属性可以resize self去适配父控件,举例如下:(OC)

 

self.testAutoSizingMaskParentView = [[UIView alloc] initWithFrame:CGRectMake(105, 205, 100, 100)];
    [self.view addSubview:_testAutoSizingMaskParentView];
    
    UIView *testAutoSizingMaskView = [[UIView alloc] initWithFrame:CGRectMake(5, 5, 80, 80)];
    testAutoSizingMaskView.backgroundColor = [UIColor grayColor];
    // 这表明,当父控件发生变化的时候,允许resize self的宽度和高度以适应父控件的变化
    testAutoSizingMaskView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;

    [self.testAutoSizingMaskParentView addSubview:testAutoSizingMaskView];

// iOS 8之后的设备方向发生变化的回调
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
	// 修改父控件frame
    if (size.width > size.height) {
        self.testAutoSizingMaskParentView.frame = CGRectMake(105, 205, 50, 60);
    } else {
        self.testAutoSizingMaskParentView.frame = CGRectMake(105, 205, 100, 100);
    }
}

NSLayoutConstraint

 

这个是iOS6之后的新特性,约束,定义两个用户界面对象之间必须满足基于约束的布局系统的关系

使用Layout布局的前提要关闭控件的translatesAutoresizingMaskIntoConstraints属性,以防止系统将autoresizing mask转换到Auto Layout constraints,从而让布局出现问题。

使用NSLayoutConstraint有两种方式:

1.VFL(Visual Formate Language)

+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary<NSString *,id> *)metrics views:(NSDictionary<NSString *,id> *)views;

 

功能        表达式

 

水平方向          H:

垂直方向          V:

Views         [view]

SuperView      |

关系         >=,==,<=

间隔              -

优先级        @value

//这个地方不要定义Frame,下面的Visual Format才是定义Frame相关的地方
UIView *redView = [[UIView alloc] init]; 
    redView.backgroundColor = [UIColor redColor];

//为了不让Constraint与View本身的autoresize冲突
    [redView setTranslatesAutoresizingMaskIntoConstraints:NO]; 
    [self.view addSubview:redView];
    
    UIView *blueView = [[UIView alloc] init];
    blueView.backgroundColor = [UIColor blueColor];
    [blueView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.view addSubview:blueView];
    
     //绑定两个View相关
    NSDictionary *views = NSDictionaryOfVariableBindings(redView, blueView);

    //此处的constraint是为了定义Frame水平方向相关(x, width)
    [self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=10)-[redView(200)]"  options:0  metrics:nil views:views]];

    //此处的constraint是为了定义Frame竖直方向相关(y, height)
    [self.view addConstraints:  [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=240)-[redView(100)]"  options:0  metrics:nil views:views]];
    [self.view addConstraints:  [NSLayoutConstraint constraintsWithVisualFormat:@"H:[blueView(==redView)]" options:0 metrics:nil views:views]];
    [self.view addConstraints:  [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(40)-[blueView(==redView)]" options:0 metrics:nil views:views]];

// 实现子空间居中
NSDictionary *dic = @{@"centerLabel":centerLabel, @"superView":label};
 [label addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[centerLabel(10)]-(<=1)-[superView]" options:NSLayoutFormatAlignAllCenterY metrics:nil views:dic]];
 [label addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[centerLabel(10)]-(<=1)-[superView]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:dic]];
// 快速实现关联关系的控件的大小一样
[label(button)],这表明label的大小与button一样

 

2.指定属性关系

+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

 这个API实际就是一个数学公式:

view1.attr1 <relation> multiplier × view2.attr2 + c

 

其中支持Layout的属性的枚举如下:

typedef enum NSLayoutAttribute : NSInteger {
    NSLayoutAttributeLeft = 1,
    NSLayoutAttributeRight,
    NSLayoutAttributeTop,
    NSLayoutAttributeBottom,
    NSLayoutAttributeLeading,
    NSLayoutAttributeTrailing,
    NSLayoutAttributeWidth,
    NSLayoutAttributeHeight,
    NSLayoutAttributeCenterX,
    NSLayoutAttributeCenterY,
    NSLayoutAttributeLastBaseline,
    NSLayoutAttributeBaseline = NSLayoutAttributeLastBaseline,
    NSLayoutAttributeFirstBaseline,
    NSLayoutAttributeLeftMargin,
    NSLayoutAttributeRightMargin,
    NSLayoutAttributeTopMargin,
    NSLayoutAttributeBottomMargin,
    NSLayoutAttributeLeadingMargin,
    NSLayoutAttributeTrailingMargin,
    NSLayoutAttributeCenterXWithinMargins,
    NSLayoutAttributeCenterYWithinMargins,
    NSLayoutAttributeNotAnAttribute = 0
} NSLayoutAttribute;

 

其中支持Layout的Relation的枚举如下:

typedef enum NSLayoutRelation : NSInteger {
    NSLayoutRelationLessThanOrEqual = -1,    // <=
    NSLayoutRelationEqual = 0,		     // ==
    NSLayoutRelationGreaterThanOrEqual = 1   // >=
} NSLayoutRelation;

 

UIStackView

为UI提供了线性布局,不管是以行Or列的形式,很像Android的布局类。可以将其看作一个布局类。增强了Auto Layout的能力.

通过Arranged Subviews来管理控件,通过设置 axis(坐标轴,水平或者是垂直布局), distribution(分布方式,类似填充样式), alignment, spacing,isLayoutMarginsRelativeArrangement,isBaselineRelativeArrangement属性来实现布局的控制。

 

举例如下:(swift)

class ULable: UILabel {
override func sizeThatFits(_ size: CGSize) -> CGSize {
        return CGSize.init(width: 100, height: 100)
    }
}

let label1 = ULable()
        label1.textAlignment = .center
        label1.sizeToFit()
        label1.text = "1111111"
        label1.backgroundColor = UIColor.brown
        let label2 = UILabel()
        label2.text = "22222222"
        label2.backgroundColor = UIColor.yellow
        let sv1 = UIStackView(arrangedSubviews: [label1,label2])
        sv1.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 200)
        sv1.axis = .vertical
        sv1.spacing = 10
        sv1.alignment = .center
        sv1.distribution = .fillEqually
        self.view.addSubview(sv1)

 

补充:

也可以使用如下方式实现控件的自适应,但不推荐:

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
+ (void)autoLayoutView:(UIView *)view;
@end

#import "AppDelegate.h"

#define kDeviceScreenWidth [[UIScreen mainScreen] bounds].size.width
#define kDeviceScreenHeight [[UIScreen mainScreen] bounds].size.height

@interface AppDelegate ()

@property CGFloat autoSizeScaleX;
@property CGFloat autoSizeScaleY;

@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    if (kDeviceScreenHeight > 480) {
        self.autoSizeScaleX = kDeviceScreenWidth/320;
        self.autoSizeScaleY = kDeviceScreenHeight/568;
    } else {
        self.autoSizeScaleX = 1.0f;
        self.autoSizeScaleY = 1.0f;
    }
    return YES;
}
+ (void)autoLayoutView:(UIView *)view {
    for (UIView *v in view.subviews) {
        v.frame = autoLayoutCGRectMake(v.frame.origin.x, v.frame.origin.y, v.bounds.size.width, v.bounds.size.height);
        if (v.subviews.count > 0) {
            [AppDelegate autoLayoutView:v];
        }
    }
}

CG_INLINE CGRect autoLayoutCGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) {
    AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    CGRect rect;
    rect.origin.x = x*appDelegate.autoSizeScaleX;
    rect.origin.y = y*appDelegate.autoSizeScaleY;
    
    rect.size.width = width*appDelegate.autoSizeScaleX;
    rect.size.height = height*appDelegate.autoSizeScaleY;
    
    return rect;
    
}
@end

 

以上就是iOS的布局方式的汇总,请根据情况使用。

AutoLayout Cook Book

2015-09-03 10:13:03 luoye2486 阅读数 193
  • iOS/Swift/OC/Objective-C/Xcode/0基础/入门

    这是一门快速入门iOS开发的课程,目的是让大家快速学会,iOS开发环境搭建,和iOS一些基础知识,最后完成一个小项目。 项目信息 提供完整的Git提交历史,和每节视频一一对应,目前有41次提交,355行注释,271行代码(不包含可视化布局文件,纯Swift和Objective-C代码)。

    3962 人正在学习 去看看 任苹蜻


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

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

self.navigationController.navigationBar.translucent = NO;

iOS流式布局

阅读数 95

iOS自动布局

阅读数 356

关于iOS自动布局

阅读数 1970

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