iOS开发基础11-点像素、Autoresizing、Auto Layout 与 Masonry

iOS 适配与布局全解析:点像素、Autoresizing、Auto Layout 与 Masonry

适配、布局和分辨率是 iOS 开发的基础概念。理解点与像素的关系、Autoresizing 与 Auto Layout 的演进,以及 Masonry 等高效布局工具,是构建多设备兼容应用的关键。


一、适配

1. 什么是适配

适配是让应用在不同环境下都能正常运行并保持良好体验的过程。

2. 移动开发中的适配种类

适配类型 说明 常见手段
系统适配 兼容不同 iOS 版本 版本判断(@available)、新 API 降级方案、废弃 API 替换
屏幕适配 兼容不同屏幕尺寸和比例 Auto Layout、Size Classes、Safe Area、动态字体

二、点和像素

1. 像素(Pixel)

屏幕的物理发光单元,像素越多屏幕越清晰。用户眼中的屏幕由像素组成。

2. 点(Point)

开发者使用的逻辑坐标单位,与物理像素解耦。一个点由一个或多个像素渲染。

设备类型 缩放因子 1 点 = 多少像素 典型设备
@1x 1.0 1 × 1 早期 iPhone 3GS 及以前
@2x 2.0 2 × 2 iPhone 6/7/8、iPhone X/XS/11 Pro
@3x 3.0 3 × 3 iPhone 6 Plus/7 Plus/8 Plus、iPhone XS Max/11 Pro Max/12+

3. 相关 API

Objective-C

// 屏幕逻辑尺寸(点)
CGRect bounds = [UIScreen mainScreen].bounds;

// 屏幕物理像素尺寸
CGRect nativeBounds = [UIScreen mainScreen].nativeBounds;

// 缩放因子(1.0 / 2.0 / 3.0)
CGFloat scale = [UIScreen mainScreen].scale;

Swift

let bounds = UIScreen.main.bounds          // 逻辑尺寸(点)
let nativeBounds = UIScreen.main.nativeBounds // 物理像素尺寸
let scale = UIScreen.main.scale            // 缩放因子

开发中所有 frame、center、Auto Layout 约束都使用点作为单位,系统自动转换为像素渲染。图片资源通过 @2x/@3x 后缀提供不同分辨率,系统根据设备自动选择。


三、Autoresizing

Autoresizing 是 Auto Layout 之前的布局方案,通过 autoresizingMask 位掩码控制子视图在父视图尺寸变化时的行为。

1. 核心概念

autoresizingMask 是一个位掩码,可组合以下值:

枚举值 对应 Xib 中的线 作用
UIViewAutoresizingFlexibleLeftMargin 左边外侧线 到父视图左边的距离可变
UIViewAutoresizingFlexibleWidth 中间水平线 宽度可变
UIViewAutoresizingFlexibleRightMargin 右边外侧线 到父视图右边的距离可变
UIViewAutoresizingFlexibleTopMargin 上边外侧线 到父视图上边的距离可变
UIViewAutoresizingFlexibleHeight 中间垂直线 高度可变
UIViewAutoresizingFlexibleBottomMargin 下边外侧线 到父视图下边的距离可变

Xib 中六根线的含义:四周的线勾选表示到对应边的距离固定(不勾选则距离可变,即对应 Flexible Margin);中间的线勾选表示宽/高可变(Flexible Width/Height)。

2. 示例代码

Objective-C

// 父视图
UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
greenView.backgroundColor = [UIColor greenColor];
[self.view addSubview:greenView];

// 子视图
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
redView.backgroundColor = [UIColor redColor];
[greenView addSubview:redView];

// 宽度和高度随父视图变化
redView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Swift

let greenView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
greenView.backgroundColor = .green
view.addSubview(greenView)

let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
greenView.addSubview(redView)

redView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

3. 注意事项

  • Flexible Width/Height 不是等比拉伸:当父视图尺寸变化时,子视图的宽/高变化量等于父视图的变化量(因为其他边固定,子视图必须吸收全部差值),而非按比例缩放。
  • 只能约束父子关系:Autoresizing 无法描述兄弟视图之间的关系(如 A 视图在 B 视图右侧 10pt),这是其最大局限。
  • 现代开发:Autoresizing 仍可用于简单场景,但复杂布局应使用 Auto Layout。

四、Auto Layout

Auto Layout 是 iOS 6 引入的自动布局技术,通过约束(Constraint)描述视图间的关系,系统自动计算每个视图的 frame。

1. 约束公式

Auto Layout 的核心是一个线性方程:

view1.attribute1 = (view2.attribute2 × multiplier) + constant

例如:"红色视图的宽度等于父视图宽度的一半减 20":

redView.width = (superview.width × 0.5) + (-20)

2. Storyboard 中的约束状态

指示器 含义 处理方式
红色箭头 约束错误(冲突或缺失) 必须修复,否则布局不确定
黄色箭头 警告(当前帧与约束计算结果不匹配) 点击 Update Frames 同步,或忽略

3. Auto Layout 动画

修改约束后,在动画 block 中调用 layoutIfNeeded 触发布局更新:

Objective-C

// 1. 修改约束的 constant
self.topConstraint.constant = 100;

// 2. 在动画 block 中强制布局
[UIView animateWithDuration:0.3 animations:^{
    [self.view layoutIfNeeded]; // 对被修改约束的视图的父视图调用
}];

Swift

topConstraint.constant = 100

UIView.animate(withDuration: 0.3) {
    self.view.layoutIfNeeded()
}

layoutIfNeeded 应发送给被修改约束所在视图的父视图(或更上层),因为约束的布局计算是从父视图向下传递的。

4. 代码实现 Auto Layout

方式一:NSLayoutConstraint 原始 API

Objective-C
UIView *redView = [[UIView alloc] init];
redView.translatesAutoresizingMaskIntoConstraints = NO; // 必须设置,否则 Autoresizing 会转为冲突约束
[self.view addSubview:redView];

// 宽度约束(不依赖其他视图,toItem 传 nil)
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:redView
                                                         attribute:NSLayoutAttributeWidth
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:nil
                                                         attribute:NSLayoutAttributeNotAnAttribute
                                                        multiplier:1.0
                                                          constant:100];
// 高度约束
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:redView
                                                          attribute:NSLayoutAttributeHeight
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:nil
                                                          attribute:NSLayoutAttributeNotAnAttribute
                                                         multiplier:1.0
                                                           constant:100];
// 居中约束(依赖父视图)
NSLayoutConstraint *centerX = [NSLayoutConstraint constraintWithItem:redView
                                                           attribute:NSLayoutAttributeCenterX
                                                           relatedBy:NSLayoutRelationEqual
                                                              toItem:self.view
                                                           attribute:NSLayoutAttributeCenterX
                                                          multiplier:1.0
                                                            constant:0];
NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:redView
                                                           attribute:NSLayoutAttributeCenterY
                                                           relatedBy:NSLayoutRelationEqual
                                                              toItem:self.view
                                                           attribute:NSLayoutAttributeCenterY
                                                          multiplier:1.0
                                                            constant:0];

// 激活约束(现代方式,系统自动添加到正确的视图)
[NSLayoutConstraint activateConstraints:@[width, height, centerX, centerY]];
Swift(推荐使用 Anchor API)
let redView = UIView()
redView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(redView)

NSLayoutConstraint.activate([
    redView.widthAnchor.constraint(equalToConstant: 100),
    redView.heightAnchor.constraint(equalToConstant: 100),
    redView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    redView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])

iOS 9+ 引入的 NSLayoutAnchor(widthAnchor、centerXAnchor 等)提供了类型安全、链式的约束创建方式,是 Swift 中的首选。

约束添加规则

  • 约束应添加到能看到所有涉及视图的最近共同祖先视图上。
  • 现代 API(isActive = YES / NSLayoutConstraint.activateConstraints:)会自动处理添加位置,开发者无需手动管理。
  • translatesAutoresizingMaskIntoConstraints 必须设为 NO,否则系统会将 Autoresizing 掩码自动转换为约束,与手动添加的约束冲突。

五、VFL(Visual Format Language)

VFL 是一种用字符串描述布局的可视化格式语言,旨在简化 Auto Layout 代码。

1. 语法说明

符号 含义
H: / V: 水平方向 / 垂直方向
` `
[view] 视图
[view(==50)] 视图尺寸(宽度或高度)
- 标准间距(8pt)
-12- 自定义间距(12pt)
>= / <= 大于等于 / 小于等于

2. 示例

H:|-[cancelButton(72)]-12-[acceptButton(50)]-|

表示:cancelButton 距父视图左边标准间距,宽度 72;与 acceptButton 间距 12;acceptButton 宽度 50,距父视图右边标准间距。

3. 代码实现

Objective-C

UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:cancelButton];

UIButton *acceptButton = [UIButton buttonWithType:UIButtonTypeSystem];
acceptButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:acceptButton];

NSDictionary *views = NSDictionaryOfVariableBindings(cancelButton, acceptButton);

NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[cancelButton(72)]-12-[acceptButton(50)]-|"
                                                                options:0
                                                                metrics:nil
                                                                  views:views];
NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[cancelButton(44)]"
                                                                options:0
                                                                metrics:nil
                                                                  views:views];

[NSLayoutConstraint activateConstraints:hConstraints];
[NSLayoutConstraint activateConstraints:vConstraints];

Swift

let cancelButton = UIButton(type: .system)
cancelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cancelButton)

let acceptButton = UIButton(type: .system)
acceptButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(acceptButton)

let views = ["cancelButton": cancelButton, "acceptButton": acceptButton]

let hConstraints = NSLayoutConstraint.constraints(
    withVisualFormat: "H:|-[cancelButton(72)]-12-[acceptButton(50)]-|",
    options: [], metrics: nil, views: views)
let vConstraints = NSLayoutConstraint.constraints(
    withVisualFormat: "V:|-100-[cancelButton(44)]",
    options: [], metrics: nil, views: views)

NSLayoutConstraint.activate(hConstraints)
NSLayoutConstraint.activate(vConstraints)

VFL 语法简洁但错误信息不友好、无法表达某些复杂约束(如宽高比、对齐全等),现代开发中已基本被 Masonry/SnapKit 等库取代。


六、Masonry

Masonry 是 OC 中最流行的 Auto Layout 第三方库,通过链式语法大幅简化约束代码。Swift 对应的库是 SnapKit。

1. 基础用法

Objective-C(Masonry)

#import "Masonry.h"

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[self.view addSubview:button];

[button mas_makeConstraints:^(MASConstraintMaker *make) {
    make.width.mas_equalTo(50);
    make.height.mas_equalTo(50);
    make.center.equalTo(self.view);
}];

Swift(SnapKit)

import SnapKit

let button = UIButton(type: .system)
view.addSubview(button)

button.snp.makeConstraints { make in
    make.width.height.equalTo(50)
    make.center.equalToSuperview()
}

2. 常用方法

方法 作用
mas_makeConstraints: 添加新约束
mas_updateConstraints: 更新已有约束(按属性匹配)
mas_remakeConstraints: 移除所有旧约束,重新添加

3. mas_equalTo 与 equalTo

方法 参数类型 说明
mas_equalTo 基本数据类型 / 结构体 自动包装为 NSValue,支持 CGFloat、CGSize、UIEdgeInsets 等
equalTo id 对象 需传入对象类型,如 UIView、NSNumber

添加宏 #define MAS_SHORTHAND_GLOBALS 后,equalTo 也支持基本类型,两者无差别。

4. 语义化方法:with 和 and

make.top.left.and.right.equalTo(self.view);
make.height.mas_equalTo(44);

with 和 and 仅返回 self,不改变逻辑,用于提高代码可读性。

5. Safe Area 适配(iOS 11+)

iOS 11 引入 Safe Area,避免内容被刘海、底部横条遮挡:

Objective-C

[button mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
    make.left.right.equalTo(self.view);
    make.height.mas_equalTo(44);
}];

Swift(SnapKit)

button.snp.makeConstraints { make in
    make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
    make.left.right.equalToSuperview()
    make.height.equalTo(44)
}

七、总结

布局方案 引入版本 能力 适用场景
Autoresizing iOS 1 仅父子关系,简单伸缩 简单布局、临时视图
Auto Layout(原生) iOS 6 任意视图间关系,功能强大 所有布局场景
VFL iOS 6 字符串描述水平/垂直布局 已基本淘汰
Masonry / SnapKit 第三方 链式语法,封装 Auto Layout 代码布局首选
SwiftUI iOS 13 声明式布局 新项目、跨平台
  • 点与像素的解耦让开发者无需关心具体设备分辨率,通过 @2x/@3x 资源提供清晰画面。
  • Auto Layout 是现代 iOS 布局的基础,理解约束公式和 layoutIfNeeded 动画机制是排查布局问题的关键。
  • 代码布局优先使用 Masonry(OC)或 SnapKit(Swift),避免冗长的原生 API。
  • iOS 11+ 必须考虑 Safe Area,避免内容被刘海和底部横条遮挡。
posted @ 2015-07-19 00:28  Mr.陳  阅读(517)  评论(0)    收藏  举报