美文网首页
iOS 界面开发 (temp)

iOS 界面开发 (temp)

作者: WesleyLien | 来源:发表于2017-02-23 00:26 被阅读0次

iPhone 设备的屏幕信息

设备 屏幕尺寸(英寸) 像素(px) pixels pre point 图片适配方案
iPhone4,4s 3.5 640*960 2 @2x
iPhone 5,5s,SE 4.0 640*1136 2 @2x
iPhone 6,6s,7 4.7 750*1334 2 @2x
iPhone 6 plus, 6s plus, 7 plus 5.5 1080*1920 3 @3x

视图的层次结构

任何一个应用都有且只有一个 UIWindow 对象,负责包含应用中的所有视图

  • 层次结构中的每个视图(包括 UIWindow 对象)分别绘制自己。视图会将自己绘制到图层( layer )中,每个 UIView 对象都有一个 layer 属性,指向一个 CALayer 对象
  • 所有视图的图层组合成一幅图像,绘制到屏幕
  • UI 界面展示流程:UIApplication — UIWindow — UIViewController — UIView — …

CGRect / CGSize / CGPoint

CGRectMake()
CGRectGetMaxX()
CGRectGetMaxY()
CGRectGetMinX()
CGRectGetMinY()
CGRectGetWidth()
CGRectGetHeight()
CGRectContainsPoint(,)
// 扩大或缩小
CGRectInset(,,)

frame / bounds / center

frame 、 bounds 和 center 是视图的三个属性,用于确定 view 在屏幕中的位置

  • frame 保存的是距离当前视图左上角的大小( size )和相对于父视图中距离原点的位置( origin ),所表示的矩形位于父视图的坐标系,用于确定与视图层次结构中其他视图的相对位置
  • bounds 表示的矩形位于自己的坐标系,origin 指当前视图距离自身坐标系左上角的位置,size 当前视图在自身坐标系中的高宽,用于确定绘制区域
  • center 是视图矩形的中心点坐标,表示当前 view 在父 view 中的位置

设有两 view ,view2 为 view1 的 subView

  • 修改 view1 的 frame.origin.x
frame bounds center
view1 x 改变 不变 x 改变
view2 不变 不变 不变
  • 修改 view2 的 frame.origin.x
frame bounds center
view1 不变 不变 不变
view2 x 改变 不变 x 改变

修改父 view 的 frame.origin 的值,移动了父 view 的位置,子 view 在父 view 中的位置没有修改,只影响当前 view 的位置

  • 修改 view1 的 bounds.origin.x
frame bounds center
view1 不变 x 改变 不变
view2 不变 不变 不变

view2显示的视图位置改变
修改父 view 的 bounds.origin 的值,父 view 的位置没有修改,但是影响了子 view 在父 view 的位置,但是相应的位置信息没有修改

  • 修改 view2 的 bounds.origin.x
frame bounds center
view1 不变 不变 不变
view2 不变 x 改变 不变

修改子 view 的 bounds.origin 的值,没有影响父 view 的位置

  • 修改 view1 的 frame.size.width
frame bounds center
view1 width 改变 width 改变 改变
view2 不变 不变 不变
  • 修改 view2 的 frame.size.width
frame bounds center
view1 不变 不变 不变
view2 width 改变 width 改变 改变

修改父 view 的 frame.size 的值,只影响当前 view 相对于左上角的高宽大小

  • 修改 view1 的 bounds.size.width
frame bounds center
view1 x,width 都改变 width 改变 不变
view2 不变 不变 不变

修改父 view 的 bounds.size 的值,父 view 的位置进行了移动,影响了子 view 在父 view 的位置,但是相应的位置信息没有修改

  • 修改 view2 的 bounds.size.width
frame bounds center
view1 不变 不变 不变
view2 x,width 都改变 width 改变 不变

修改子 view 的 bounds.size 没有影响父 view 的位置信息,但是影响了子 view 的位置信息

三种布局方式

  • frame 绝对定位布局
  • frame + autoResizing 相对布局
    .autoresizesSubviews
    .autoresizingMask
  • AutoLayout 相对布局
    通过添加约束对象,可以设置任何控件之间的关系

界面的实现

  • 纯代码
  • Interface Builder
    通过 Interface Builder 绘制的文件格式为 xib 文件,在编译过程将 xib 文件转化为二进制的 nib 文件
    一般一个 xib 文件对应一个 viewController ,同时也可以用来绘制可重用的 UIView 视图
    xib 通过 UINib 类进行读取
    加载 xib 文件有两种方式:
// xib 文件在 iOS 中做为资源文件存在,因此可以使用 NSBundle 的方式进行加载
[[NSBundle mainBundle] loadNibNamed: owner: options:];
// 在iOS4.0以后新增了[UINib class] 用于加载nib资源文件
[UINib nibWithNibName: bundle:];
  • Storyboard
    Storyboard 通过 UIStoryboard 类进行读取

AutoLayout

布局属性

Width / Height
Top / Bottom / Left / Right
CenterX / CenterY
BaseLine
Leading / Trailing

约束

NSLayoutAttributeWidth / NSLayoutAttributeHeight
NSLayoutAttributeTop / NSLayoutAttributeBottom / NSLayoutAttributeLeft / NSLayoutAttributeRight
NSLayoutAttributeTopMargin / NSLayoutAttributeBottomMargin / NSLayoutAttributeLeftMargin / NSLayoutAttributeRightMargin
NSLayoutAttributeCenterX / NSLayoutAttributeCenterY
NSLayoutAttributeBaseline
NSLayoutAttributeLeading / NSLayoutAttributeTrailing
NSLayoutAttributeLeadingMargin / NSLayoutAttributeTrailingMargin
NSLayoutAttributeNotAnAttribute

  • 通常情况下,在大部分语言体系中都是从左往右进行阅读,则 NSLayoutAttributeLeft 与 NSLayoutAttributeLeading 都表示内容左侧,NSLayoutAttributeRight 与 NSLayoutAttributeTrailing 都表示内容右侧。在部分特殊语系中从右往左进行阅读,这时 NSLayoutAttributeLeft 与 NSLayoutAttributeTrailing 都表示内容左侧,NSLayoutAttributeRight 与 NSLayoutAttributeLeading 都表示内容右侧
  • Xcode 8.0 版本,视图与ViewController根视图之间 左右间距的系统推荐的间距值为 16pt。Xcode 8.0 版本及之前版本,两个普通父子视图之间 上下左右间距的系统推荐的间距值都为 8pt
  • 在 Interface Builder 中,当勾选 Constrain to margins 时,如添加右侧边界约束时,实际约束对象建立的是 NSLayoutAttributeTrailingMargin 边界约束,当不勾选 Constrain to margins 时,实际约束对象建立的是 NSLayoutAttributeTrailing 边界约束
  • NSLayoutAttributeNotAnAttribute 一般用于自身约束如设置视图宽度用
  • 容器 view 的高度可以根据子 view 的属性进行确定

Interface Builder 下约束的设置界面


代码中新建约束

如果使用代码添加约束,则需要关闭 autoResizing mask 的布局方式以免冲突
view.translatesAutoresizingMaskIntoConstrains = NO;
我们可以将约束口语化描述为:
view1.attrubute1 = view2.attribute2 * multiplier + constant
则对应的代码中的方法,有

[NSLayoutConstraint class]
+ constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:

Virtual Format Language

功能 表达式
水平方向 H:
垂直方向 V:
视图 [view]
父视图 \
关系 >=、==、<=
间隔 -
优先级 @value
[NSLayoutConstraint class]
+ constraintsWithVisualFormat:options:metrics:views:

// format 为 NSString 对象,即视觉化格式语言,eg:  
H:[button]-8-[Label]
H:|-20-[button]-20-|
H:|-[button(==30)]-|
H:[button1(==60)]-10-[button2(==button1)]
// options 为 NSLayoutFormatOptions 枚举值
// metrics 为 NSDictionary 对象,存放在 format 中出现的 String 和对应的值,key 必须为 NSString ,value 为 NSNumber
// views 为 NSDictionary 对象,存放在 format 中的 View 的名字和对应的 view ,key 必须为 NSString ,value 为 view 实例
可以使用
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(button1, button2);
即创建字典 { @"button1" = button1, @"button2 = button2 }

添加约束的对象

[UIView class]
- addConstraint:
- addConstraints:

约束添加规则:

  • 约束对多个父 view 相同的 views 起作用,则添加到父 view
  • 约束对 view 自身起作用,添加到 view 自身
  • 约束对多个父 view 不同的 views 起作用,添加到他们最近一级的祖先 view
  • 约束对 view 和父 view 起作用,添加到父 view

约束的查找与删除

[UIView class]
// NSArray 对象,保存了 view 的所有 NSLayoutConstraint
.constraints
// 删除约束
- removeConstraint:
- removeConstraints:
// NSLayoutConstraint 有 .firstItem / .firstAttribute / .relation / .secondItem / .secondAttribute / .multiplier / .constant 这几个属性,可以用来查找确定一个约束

for (NSLayoutConstraint *constraint in _contentScrollView.constraints) {
    if ([constraint.firstItem isKindOfClass:[UIView class]] && constraint.firstAttribute == NSLayoutAttributeBottom && constraint.firstItem == _preView) {
        [_contentScrollView removeConstraint:constraint];
    }
}

优先级

约束有属性 .priority(优先级),当约束冲突时,低优先级的约束被忽略
数值从1 - 1000,分为三档:

  • 1000(required) -- UILayoutPriorityRequired
  • 750(high) -- UILayoutPriorityDefaultHigh
  • 250(low) -- UILayoutPriorityDefaultLow

默认情况下为1000

内容优先级
content priority
  • 固有内容大小( Intrinsic Content Size ):视图实际显示的实际内容大小。如 UILabel 的固有内容大小由字数和字体决定,UIImageView 的固有内容大小由图片的尺寸决定
[UIView class]
- intrinsicContentSize
  • AutoLayout 会为固有内容大小添加约束,这类约束有两个优先级属性,分别是 内容放大优先级( content hugging priority ) 和 内容缩小优先级( content compression resistance priority )
[UIView class]
// axis -- UILayoutConstraintAxisVertical、UILayoutConstraintAxisHorizontal
- setContentHuggingPriority:forAxis:
- setContentCompressionResistancePriority:forAxis:
  • content hugging priority: 表示视图固有内容大小的放大优先级。优先级越高,表示越不允许自动布局系统基于固有内容大小放大视图尺寸,默认值为 251
  • content compression resistance priority: 表示视图固有内容大小的缩小优先级。优先级越高,表示越不允许自动布局系统基于固有内容大小缩小视图尺寸,默认值为 750
  • 当约束的优先级大于等于固有内容的布局优先级时,会忽略内容布局的设置
    当设置一个imageView(50* 50)的宽度约束为40(约束优先级为required),内容优先级为默认,显示大小为40。将约束优先级设置为DefaultHigh - 1,显示大小为50
    当父视图的宽度不足容纳两个label固有内容,两个label hugging 和 compression 相同,设置labelA的宽度约束为100(优先级为DefaultHigh - 1),宽度约束仍然会被满足

Size class

主要用来适应设备屏幕大小的变化,不需要再根据屏幕大小的尺寸进行区分,而只需要对矩形区域的选择,系统会自动适配各个屏幕

//UIInterface.h
enum UIUserInterfaceSizeClass {
   UIUserInterfaceSizeClassUnspecified //高或宽占据两个区域
   UIUserInterfaceSizeClassCompact  //高或宽占据一个区域
   UIUserInterfaceSizeClassRegular  //高或宽占据三个区域
}

苹果各设备高宽对应的枚举值

iPhone 4s/5/SE/6/6s/7 iPhone 6 plus/6s plus/7 plus iPad
竖屏 (w:Compact h:Regular) (w:Compact h:Regular) (w:Regular h:Regular)
横屏 (w:Compact h:Compact) (w:Regular h:Compact) (w:Regular h:Regular)

在 Interface Builder 中我们可以根据设备对 Size Class 进行选择


当我们点击某一视图,在 Attributes inspector 下最底部分别有两项(installed (wAny hAny)和 wC hR installed (如果没有可添加)),可根据需要进行选中,选中表示在当这个 size class 下会显示这个视图
同样对于约束,在 Attributes inspector 下最底部也可选择对应约束在哪种 size class 下生效

代码中的实现

// 这个类主要封装了水平和垂直方向的 size class 等信息
// UIKit 中大多数 UI 的基础类 UIView  UIViewController UIWindow ... 都实现了 <UITraitEnvironment> 协议,其中的 .traitCollection 属性为 UITraitCollection 对象,从而可以得到当前视图以及视图的约束布局的 size class 属性
[UITraitCollection class]

Top Layout Guide / Bottom Layout Guide

通常绘制 UIViewController 的内容会有几种状态:

  • 显示在顶部 status bar( 20pt )下方
  • 显示在navigation bar( 44pt )下方
  • 显示在 tab bar( 44pt )上方

而我们绘制 UIViewController 并不知道其他 bar 的内容或是否存在
控制 UIViewController 的内容在可见区域内进行显示,[UIViewController class] 有两个属性:.topLayoutGuide 和 .bottomLayoutGuide ,设置了 VC 顶边与 topLayoutGuide 关联,底边与 bottomLayoutGuide 关联。也就是当 navigationBar 显示时,topLayoutGuide 表示 navigationBar 的底边……
则我们在设置约束时,可设置 view 与 topLayoutGuide 或 bottomLayoutGuide 的相对布局关系

布局流程

UIView 布局流程和布局更新流程

  1. 首先初始化控件
  2. 添加到父视图
  3. “设置控件的位置信息” —— 通过约束布局时通常不会设置frame信息
  4. 计算约束布局
  5. 更新布局

则步骤和对应方法为

步骤 方法
Load Views - (instancetype)init
Update Views’ Constrains - (void)updateConstraints
Update Views’ Frame - (void)layoutSubviews
Display Views

如果更新了布局,则重复 Update Views’ Constrains、Update Views’ Frame 和 Display Views 步骤

UIViewController 布局流程

步骤 方法
Load Views - (void)awakeFromNib - (void)loadView - (void)viewDidLoad
- (void)viewWillAppear
Update Views’ Constrains - (void)updateViewConstraints
Update Views’ Frame - (void)viewWillLayoutSubviews - (void)viewDidLayoutSubviews
Display Views
- (void)viewDidAppear

UIViewController 除了维护一个视图层外,还会处理视图变化的相关通知,即 viewWillAppear、viewDidAppear、viewWillDisappear、viewDidDisappear
viewWillAppear 主要用来设置一些简单的显示动画,或者是状态栏风格的修改

布局流程方法的执行顺序

  1. 打开一个 ViewController
viewDidLoad
viewWillAppear
    // 根视图大小与当前屏幕大小相同,因此 updateViewConstraints 只调用一次
    updateViewConstraints
    viewWillLayoutSubviews
    viewDidLayoutSubviews
viewDidAppear
或
viewDidLoad
viewWillAppear
    updateViewConstraints
    viewWillLayoutSubviews
    viewDidLayoutSubviews
    viewWillLayoutSubviews
    viewDidLayoutSubviews
viewDidAppear
//当子 View 有 UIButton 类的视图时出现
  1. 打开另一个 ViewController
viewWillDisappear
    updateViewConstraints
    viewWillLayoutSubviews
    viewDidLayoutSubviews
viewDidDisappear
  1. 返回 ViewController
viewWillAppear
    updateViewConstraints
    viewWillLayoutSubviews
    viewDidLayoutSubviews
viewDidAppear
    updateViewConstraints
    viewWillLayoutSubviews
    viewDidLayoutSubviews
  1. ViewController 子视图修改布局
viewWillLayoutSubviews
viewDidLayoutSubviews
  1. 添加一个没有子 View 的 View 到 ViewController 的 view 作为子 view
ViewController viewWillLayoutSubviews
View updateConstraints
ViewController viewDidLayoutSubviews
View layoutSubviews
  1. 修改子 View 使子 View 包含一个子 View,即添加一个有子 View 的子 View 到 ViewController 的 view
ViewController viewWillLayoutSubviews
View updateConstraints
ViewController viewDidLayoutSubviews
View layoutSubviews
或
ViewController viewWillLayoutSubviews
View updateConstraints
ViewController viewDidLayoutSubviews
View layoutSubviews
ViewController viewWillLayoutSubviews
ViewController viewDidLayoutSubviews
  1. 修改子 View 的子 View 的布局
View layoutSubviews
  1. 使子 View 的布局为约束布局,同时使子 View 的位置发生变化,大小不变
ViewController viewWillLayoutSubviews
ViewController viewDidLayoutSubviews
View layoutSubviews

总结

  • UIViewController 首次加载时,在 viewWillAppear 之后调用 updateViewConstraints(后简用括号内容:updateConstraints) 和 viewWillLayoutSubviews、viewDidLayoutSubviews(layoutSubviews) 方法
  • UIViewController 消失或再次显示时,在 viewWillDisappear / viewWillAppear / viewDidAppear 之后调用 (updateConstraints) 和 (layoutSubviews) 方法
  • UIViewController 子视图布局更新时,会调用 (layoutSubviews) 方法
  • UIView 首次加载时,在 UIViewController 调用 (layoutSubviews) 时,执行 updateConstraints 和 layoutSubviews 方法
  • UIView 自身布局更新或者子视图布局更新时,UIViewController 调用 (layoutSubviews) 时,执行 layoutSubviews 更新布局
  • UIButton 和 UITableView 等在首次加载时,会使 UIViewController 多次调用 (layoutSubviews) 方法

屏幕渲染机制

View Drawing Cycle 每隔1/60s,检查视图更新
读取待处理视图列表,主要是视图的修改添加和删除,当列表不为空,在屏幕上显示更新结果

基于约束的布局更新

Traggering Constraint-Based Layout

- setNeedsUpdateConstraints —- 标记下一次绘制 更新约束
- updateConstraintsIfNeed —- 立即更新约束
- updateConstraints — 系统更新的执行方法

基于Frame修改的布局更新

- setNeedsLayout — 标记下一次绘制 更新布局
- layoutIfNeeded — 立即更新布局
- layoutSubviews — 系统调用布局更新的方法

UIViewController 和 UIView 的视图绘制周期

如果我们希望修改了子View的高度约束来修改子View的高度,并看到修改的过程

if (self.hadAddColorView) {
    [UIView animateWithDuration:1.f animations:^{
        if (self.grayViewHeight.constant == 100.f) {
            self.grayViewHeight.constant = 150.f;
        } else {
            self.grayViewHeight.constant = 100.f;
        }
    }];      
} else {
    ......
}

可以看到修改的结果是立即执行的,并没有动画的效果,说明子View在修改高度的约束时,frame的重新绘制与当前方法并不在同一绘制周期中进行

if (self.hadAddColorView) {
        //如果是直接修改 frame 则默认标记grayView needsLayout 并执行 layoutSubviews 方法
        //同时修改了 约束条件 以及 frame属性时,布局系统会重新调用 updateConstraint 将约束布局信息计算为对应 frame 值,调用layoutSubviews 应用计算好的 frame 值。而修改的frame属性值会被忽略
        //当视图在使用约束布局时,尽量不要修改该视图的frame值,而应该修改该视图的约束值,防止直接修改的frame值被重新计算的约束更新的frame值覆盖
    [UIView animateWithDuration:1.f animations:^{
        if (self.grayViewHeight.constant == 100.f) {
                    //默认标记grayView needsUpdateContraint 及 needsLayout
            self.grayViewHeight.constant = 150.f;
        } else {
            self.grayViewHeight.constant = 100.f;
        }
        //[self.grayView setNeedsLayout];
        [self.grayView layoutIfNeeded];
    }];        
} else {
    ......
}

但是只有 grayView 通过动画展示,其他与 grayView 有约束关系的 View 并不执行动画
可以对self.view标记立即更新

if (self.hadAddColorView) {
    [UIView animateWithDuration:1.f animations:^{
        if (self.grayViewHeight.constant == 100.f) {
            self.grayViewHeight.constant = 150.f;
        } else {
            self.grayViewHeight.constant = 100.f;
        }
        //[self.view setNeedsLayout];
        [self.view layoutIfNeeded];
    }];        
} else {
    ......
}

在重写视图绘制方法中修改约束

- (void)updateViewConstraints {
    [super updateViewConstraints];
    NSLog(@"ViewController updateViewConstraints");
}
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    NSLog(@"ViewController viewWillLayoutSubviews");
    NSLog(@"grayView frame:%@, constant:%f", NSStringFromCGRect(self.grayView.frame), self.grayViewHeight.constant);
}
- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    //
    self.grayViewHeight.constant += 100.f;       
    NSLog(@"ViewController viewDidLayoutSubviews");
    NSLog(@"grayView frame:%@, constant:%f", NSStringFromCGRect(self.grayView.frame), self.grayViewHeight.constant);
}
输出为:
ViewController viewDidLoad
ViewController viewWillAppear
ViewController updateViewConstraints
ViewController viewWillLayoutSubviews
grayView frame:{{16, 20}, {240, 150}}, constant:150.000000
ViewController viewDidLayoutSubviews
grayView frame:{{16, 20}, {240, 150}}, constant:250.000000
ViewController viewDidAppear

可以看出在 viewDidLayoutSubviews 中,约束立即更新,但frame没有,打开后可看到子视图已改变,但是子视图布局的改变并没有再次调用 (layoutSubviews) 方法,即在当前绘制周期中修改的约束其实是在下一个绘制周期中执行
如果

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
      NSLog(@"ViewController viewDidLayoutSubviews");
    self.grayViewHeight.constant += 100.f;
    // 会让约束在当前的绘制周期中立即执行
    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];    
    NSLog(@"grayView frame:%@, constant:%.0f", NSStringFromCGRect(self.grayView.frame), self.grayViewHeight.constant);
}
输出:
ViewController viewWillLayoutSubviews
grayView frame:{{16, 20}, {240, 123950}}, constant:124050
ViewController viewWillLayoutSubviews
grayView frame:{{16, 20}, {240, 124050}}, constant:124150
ViewController viewWillLayoutSubviews
grayView frame:{{16, 20}, {240, 124150}}, constant:124250
……
// 无限循环,在 viewDidLayoutSubviews 中,遇到立即更新的标记,会立即调用 viewWillLayoutSubviews 开始更新布局

相关文章

网友评论

      本文标题:iOS 界面开发 (temp)

      本文链接:https://www.haomeiwen.com/subject/tqsvwttx.html