普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月5日掘金 iOS

iOS 知识点 - 一篇文章弄清「输入事件系统」(【事件传递机制、响应链机制】以及相关知识点)

作者 齐生1
2025年12月5日 17:34

iOS 事件系统全景图(硬件 → UIKit → 控件)

一个用户手指触摸屏幕的事件,从硬件到应用层,大致的经历是:

[ 触摸屏幕 ][ IOKit -> IOHIDEvent ] (硬件事件)
    ↓
[ SpringBoard / BackBoard / SystemServer ] (系统事件中转)
    ↓
[ UIApplication → RunLoop Source → _UIApplicationHandleEventQueue ] (App 事件入口)
    ↓
[ UIKit 生成触摸序列 ] (UITouch / UIEvent)
    ↓
[ UIWindow → UIView ] (事件传递机制: hitTest / pointInside)
    ↓
[ UIGestureRecognizer ] (手势识别 / 状态机 / 冲突处理)
    ↓
[ UIResponder ] (响应链: touchesBegan / nextResponder)
    ↓
[ UIcontrol → Target-Action ] (控件事件)
模块 关键词 代表类
硬件输入系统 IOKit / HID / RunLoop Source
触摸事件系统 Touch / Phase / Event UITouch / UIEvent
事件传递机制 hitTest / pointInside UIView / UIWindow
手势识别机制 state / requireToFail / delegate UIGestureRecognizer 系列
响应链机制 nextResponder / touches UIResponder / UIViewController
控件事件系统 target-action / sendActions UIControl / UIButton
RunLoop驱动层(补充) CFRunLoopSource, Observer CFRunLoop, UIApplication

一、硬件输入系统

  • IOKit / HID 驱动 负责把物理触摸信号转成 IOHIDEvent;
  • 这些 IOHIDEventbackboardd 转发给前台进程(Your App);
  • 主线程 RunLoop 注册了 _UIApplicationHandleEventQueue() 作为输入源,接收事件。

二、触摸事件系统

iOS 的输入事件分为几种类型:

类型 描述 相关类
Touch 单指/多指触摸 UITouch
Press 按压 UIPress
Motion 摇一摇、重力加速度 UIEventSubtypeMotionShake
Remote Control 耳机线控 / 外设 UIEventSubtypeRemoteControl
  1. UITouch

    • 每根手指独立对应一个 UITouch 对象
    • 保存触摸状态、位置、timestamp、phase、唯一 identifier
    • phase 会随手指动作变化(Began → Moved → Ended/Cancelled)
  2. 触摸序列 (Touch Sequence):一个概念(用来描述 “一次连续的触摸过程”)

    • 单指连续触摸,从手指接触到抬起或取消
    • 对应一个 UITouch 对象的完整生命周期
  3. 多指触摸

    • 每根手指都有自己的 UITouch → 多个触摸序列并行
    • UIEvent 封装同一时间点的所有触摸
  4. UIEvent

    • 一个 UIEvent 对象封装一批同时发生的 UITouch(或 presses/motion/remote 控件事件)
    • event.timestamp = 事件发生的时间点
    • event.type = touches / presses / motion / remoteControl

三、UIKit 分发层(事件传递机制)

UIKit 在接收到事件后开始做「命中检测」🎯

核心调用链 是:

UIApplication sendEvent: 
   ↓
UIWindow sendEvent: // 从 window 开始
   ↓
hitTest:withEvent:   // 做递归「命中检测」🎯
   ↓
pointInside:withEvent:
  • hitTest: 规则(可交互条件): 1. view.userInteractionEnabled == YES 2. view.hidden == NO 3. view.alpha > 0.01 4. pointInside == YES
    • 倒序遍历 subviews,返回最上层命中的 view。
    • 将得到的 view 作为 First Responder 候选人。

四、手势识别层(UIGestureRecognizer 系列)

  • 核心思想:手势识别发生在 时间传递后、响应链前;手势识别器监听 触摸序列,根据预设规则判断是否满足手势条件。

每个手势识别器都有一套状态机和冲突调度逻辑(手势冲突)

状态机(UIGestureRecognizerState

状态 含义 触发时机
.Possible 初始状态 等待识别开始
.Began 识别开始 手势识别成功,手势开始响应
.Changed 手势进行中 位置/角度变化中
.Ended 识别完成 手势完成(抬手、离开)
.Cancelled 被系统或上层取消 如中断或手势冲突
.Failed 未识别成功 条件不满足(时间太短、移动太远)
  • 状态迁移 大致是:
Possible → Began → Changed → Ended
         → Failed
         → Cancelled

手势冲突与协调机制

多个手势可能同时附着在 同一视图/同一层级 上,系统需要协调 “谁可以先响应”。

  • 手势关系:每个 UIGestureRecognizer 都有一个「关系图」,由以下规则控制:
规则 方法 含义
失败依赖 requireGestureRecognizerToFail: 让某个手势必须等待另一个手势失败后再识别
同时识别 gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: 允许多个手势同时识别
禁止识别 gestureRecognizer:shouldReceiveTouch: 完全忽略某次触摸
优先识别 gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer: 指定优先级关系
  • 优先级调度

    • 根据依赖关系构建「手势图」;
    • 同步触摸输入,驱动每个手势的状态机;
    • 当有手势识别成功后,让互斥手势进入 .Failed。
  • 举例:在 scrollview 上增加 tap 手势。 [tap requireGestureRecognizerToFail:scrollView.panGestureRecognizer];

    • 表示「滚动优先于点击」;只有 pan 失败后,tap 才能触发。

手势与 Touch 的竞争关系

场景 结果
✅手势识别 success 手势回调触发,touches 系列不会再调用
❌手势识别 failure 事件进入响应链,触发 touches 系列方法(touchesBegan / Moved / Ended)
❌手势识别 cancel 调用touchesCancelled,touches 系列不会再调用

手势识别器接管 触摸序列 之后,UIKit 不会再把 touches 事件下发给视图层。


五、响应链机制(Responder Chain)

当手势识别失败后,触摸事件才能进入 UIResponder

1️⃣ 事件流向(子 -> 父)

image.png

1 - UIView2 - UIViewController (若有)
    → 3 - UIWindow4 - UIApplication5 - AppDelegate
  • 如果当前 responder 不处理事件,会传递给 nextResponder

六、控件事件系统(UIControl

UIControlUIViewUIResponderNSObject

UIKit 在响应链之上又封装了一层抽象机制:Target-Action

  • UIButton/UISwitch/UISlider 等继承自 UIControl
  • UIControl 通过 touches 系列方法 监控触摸,然后触发事件。

流程:

[ 触摸序列 → UIView (touchesBegan/Moved/Ended) ][ UIControl (拦截触摸) ]
                      ↓
                 判断事件类型
                      ↓
        [sendActionsForControlEvents:]
                      ↓
          执行注册的 Target-Action 回调

控件事件类型 (常用):

类型 时机
TouchDown 手指按下
TouchUpInside 在控件内抬起(最常用)
ValueChanged 值改变(Slider/Switch)

思考🤔:为什么在 UIScrollView 上的 UIButton 事件响应有延迟?

现象:

  • 点击按钮 → 高亮/触发 action 延迟约 100~200ms
  • 滑动触发滚动时,按钮点击可能被“吃掉”

原因分析

控件 事件处理机制
UIScrollView 内部有 UIPanGestureRecognizer 判断拖动;默认 delaysContentTouches = YES,会延迟将 touchesBegan 传给子控件
UIButton 依赖 touchesBegan/Moved/Ended 来管理高亮和触发 action;无法立即处理 touches,如果手势被占用,可能收到 touchesCancelled

✅ 核心点:

  • UIScrollView 先抢占触摸 → 拖动手势触发 → UIButton 延迟或取消事件。
  • UIButton 事件依赖 触摸序列未被取消 才能触发 target-action。

为什么 UIScrollView 先抢占触摸 ?

  • hitTest 结果
    • 手指点击在 UIButton 上 → 通过事件传递机制 → 设置 UIButtonFirst Responder 候选人
    • 但是 UIScrollView 内部的 panGestureRecognizer 也会监听同一触摸序列:
      • 手势识别器在 touchesBegan 延迟期间观察手势意图;
        • 如果 panGesture 成功,UIKit 会将触摸序列会被标记 “被 UIScrollView 占用” → UIButton 收到 touchesCancelled
        • 如果 panGesture 失败,触摸序列被 UIButton 占有。

这个延迟可以通过 UIScrollViewdelaysContentTouches 字段取消掉。

Swift中Package Manager的使用

2025年12月5日 14:53

Swift中Package Manager的使用

一、Package文件构成

Swift Package Manager简称SPM是苹果官方为swift语言提供的强大的依赖管理工具。能够自动化地处理包的依赖下载、编译、链接和管理。

Products:在包中的每个target最终都可能构建成一个Library或者一个execute作为product,这是package编译后的产物,

Target:构建单元,包含一组源代码文件,可以是一个库,可执行文件等。可依赖其他目标,如library、executable。一个package可以包含多个target

Dependencies:package所依赖的其他package,SPM会自动下载并解析这些依赖,确保项目的所有库都能正确构建。

Tool Version:最低支持的Swift工具链版本。

img

二、SPM的优点

对比Cocoapods,SPM具有以下优点。

  • 无需安装,Xcode11以上版本自带
  • 苹果官方维护,不用担心和Cocoapods一样停止维护
  • 安装第三方库的时候比Cocoapods快(依赖源在github,有些要翻墙)
  • 使用SPM构建时比Cocoapods快

三、SPM缺点

  • 每次打开App 都会重新拉取 所有依赖的库
  • 更新时间长(访问github 还需要进行科学上网)
  • 支持文档少,
  • 远端仓库对网络要求高

四、创建Package的两种方式:

1、常用命令:
mkdir SwiftPackageTest # 生成的Package的名称
cd SwiftPackageTest
swift package init --type library       # 初始化库包
swift build                              # 构建
swift test                               # 运行测试
swift run <executable-target>            # 运行可执行目标
swift package resolve                    # 解析依赖
swift package update                     # 更新依赖
基本使用

通过命令可以快速

# 创建一个库包
swift package init --name MyLib --type library

# 创建一个可执行包
swift package init --name MyLib --type executable

这将在当前目录生成一个标准的库包结构:

MyLib/
├── Sources/
│   └── MyLib/
│       └── MyLib.swift
├── Tests/
│   └── MyLibTests/
│       └── MyLibTests.swift
└── Package.swift

Package.swift清单文件的内容通常如下:


MyLib.swift文件

Sources目录是实现代码的存放位置,MyLib.swift一般作为程序的入口,用于处理命令行参数并调用核心功能。

构建和测试

# 编译包
swift build

# 运行测试
swift test

# 运行包
swift run

2、使用Xcode界面创建

Xcode—> 工具栏File—>New—>Package—>Libary

QQ_1764917428650.png

五、Package的配置

// swift-tools-version:6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
    name: "MyLib",
    platforms: [.iOS(.v18), .macOS(.v15)], // 指定包所支持的平台和最低版本
    products: [
        .library(name: "MyLib", targets: ["MyLib"]) // 指编译后的包,对外提供
    ],
    dependencies: [ // 声明此包所依赖的外部包
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0")
    ],
    targets: [ // 定义包的相关信息
        .target(
            name: "MyLib",
            dependencies: ["Alamofire"],
            resources: [.process("Resources")]
        ),
        .testTarget(
            name: "MyLibTests",
            dependencies: ["MyLib"]
        )
    ]
)

  • name: Swift包的名字,或者‘ nil ’使用包的Git URL来推断名字。

  • defaultLocalization:资源的默认本地化。

  • platforms:具有自定义部署目标的受支持平台列表。

    • 支持的平台和对应的系统版本
    • platforms:[
    • .macOS(.v11), .iOS(.v12),.tvOS(.v12)
    • ]
  • pkgConfig: C模块的名称。如果存在,Swift包管理器会搜索 <名称>。获取系统目标所需的附加标志。

  • providers:系统目标的包提供程序。

  • products:此包提供给客户使用的产品列表。

    编译后的产物一般分为两种 可执行文件 静态库或动态库

  • dependencies:包依赖列表。
  • 添加依赖的包,一般指向包源的git路径和版本环境,或者包依赖的本地路径

  • 依赖包的添加支持以下五种方式

    • git源 + 确定的版本号
    • git源 + 版本区间
    • git源 + commit号
    • git源 + 分支名
    • 本地路径
.package(url: "https://github.com/Alamofire/Alamofire.git", .exact("1.2.3")),
.package(url:"https://github.com/Alamofire/Alamofire.git", .branch("master")),
.package(url:"https://github.com/Alamofire/Alamofire.git", from: "1.2.3"),
.package(url:"https://github.com/Alamofire/Alamofire.git",.revision("e74b07278b926c9ec6f9643455ea00d1ce04a021"),
.package(url: "https://github.com/Alamofire/Alamofire.git", "1.2.3"..."4.1.3"),
.package(path: "../Foo"),
  • targets:作为这个包的一部分的目标列表。
  • target是Package的基本构建,和xcodeproject一样,Package可以有多个target

  • target分为三种类型

    • 常规性 .regular
    • 测试类型 .test
    • 系统库类型 .system
  • swiftLanguageModes:此包兼容的Swift语言模式列表。

六、在Xcode中导入包

  1. 在Xcode中打开你的项目。
  2. 选择菜单栏的File > Add Packages...。
  3. 在弹出的窗口中,选择Add Local添加本地的package,或输入包存在的网址。
  4. 选择完成后,点击Add Package,Xcode会自动解析并下载该包及其所有依赖项。
  5. 依赖的包会出现在项目导航器的Package Dependencies部分,然后可以在代码中直接import使用。

在Xcode中删除包 如果在Xcode中导入包后,无法在Package Dependencies部分删除包,可以在项目.xcodeproj包内内容下的project.pbxproj里进行包的删除,删除后保存文件即可。

参考:juejin.cn/post/743693…

iOS UIKit 全体系知识手册(Objective-C 版)

作者 如此风景
2025年12月5日 11:58

UIKit 是 iOS/iPadOS 开发的核心 UI 框架,基于 Objective-C 构建,封装了所有可视化界面、交互、布局、渲染相关的能力,是构建 iOS 应用的基础。以下从「基础架构→核心组件→布局→事件→渲染→适配→优化→调试」全维度拆解 UIKit 知识体系,覆盖开发全场景。

一、UIKit 基础核心(框架基石)

1. UIKit 定位与依赖

  • 核心作用:提供 iOS 应用的可视化界面、用户交互、布局管理、事件处理等能力,是上层业务与底层系统(Core Graphics/Core Animation/Foundation)的桥梁。

  • 依赖关系

  - 基于 Foundation(数据处理:NSString/NSDictionary 等);

  - 依赖 Core Graphics(绘图)、Core Animation(动画/渲染)、Core Text(文本排版);

  - 兼容 AppKit(macOS)部分逻辑,但针对移动设备做了轻量化适配。

  • 核心设计思想:基于「响应者链」+「MVC 架构」,视图(UIView)负责展示,控制器(UIViewController)负责逻辑,模型(Model)负责数据。

2. 应用入口与生命周期

(1)应用级入口(UIApplication)

UIApplication 是应用的「单例管家」,管理应用生命周期、事件分发、状态栏、URL 跳转等:


// 获取应用单例

UIApplication *app = [UIApplication sharedApplication];

// 设置状态栏样式(iOS 13+ 需在 Info.plist 配置 View controller-based status bar appearance = NO)

app.statusBarStyle = UIStatusBarStyleLightContent;

// 打开URL

[app openURL:[NSURL URLWithString:@"https://www.apple.com"] options:@{} completionHandler:nil];

(2)应用代理(UIApplicationDelegate / SceneDelegate)

  • iOS 12 及以下:通过 UIApplicationDelegate 管理应用生命周期(全局唯一);

  • iOS 13+:引入 UISceneDelegate 管理「场景(Scene)」生命周期(支持多窗口),UIApplicationDelegate 仅负责应用级初始化。

核心生命周期方法(UIApplicationDelegate) 说明
application:didFinishLaunchingWithOptions: 应用启动完成(初始化根控制器)
applicationDidBecomeActive:              应用进入前台(可交互)
applicationWillResignActive:              应用退至后台(如来电、下拉通知)
applicationDidEnterBackground:            应用完全后台(需保存数据)
applicationWillEnterForeground:           应用即将前台(恢复界面)
applicationWillTerminate:                 应用即将退出(最后清理)

(3)UIViewController 生命周期(核心)

控制器是「视图的管理者」,其生命周期决定了视图的创建/销毁,OC 核心方法如下:


@interface ViewController ()
@end

@implementation ViewController

// 1. 初始化(代码创建时调用)

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) {

        // 初始化数据、配置

    }

    return self;

}

// 2. 加载视图(视图首次创建,懒加载)

- (void)loadView {

    // 手动创建根视图(若不用XIB/Storyboard)

    self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];

    self.view.backgroundColor = [UIColor whiteColor];

}

// 3. 视图加载完成(初始化控件、布局)

- (void)viewDidLoad {

    [super viewDidLoad];

    // 核心初始化逻辑(仅执行一次)

}

// 4. 视图即将显示(每次显示前调用,如跳转返回)

- (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];

    // 更新界面数据、刷新布局

}

// 5. 视图已显示(可执行动画、网络请求)

- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];

}
// 6. 视图即将隐藏

- (void)viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];

    // 暂停动画、移除监听

}

// 7. 视图已隐藏

- (void)viewDidDisappear:(BOOL)animated {

    [super viewDidDisappear:animated];

}

// 8. 内存警告(释放非必要资源)

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

}

// 9. 视图销毁(控制器释放前)

- (void)dealloc {

    // 移除通知、释放强引用(避免内存泄漏)

    NSLog(@"控制器销毁");

}

@end

3. 响应者体系(UIResponder)

UIKit 所有可交互元素都继承自 UIResponder(响应者),构成「响应者链」处理事件(触摸、手势、键盘等):

  • 核心响应者UIApplicationUIWindowUIViewControllerUIView → 子视图;

  • 核心方法

  // 触摸事件
  - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
  - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
  - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
  - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
  // 成为第一响应者(如输入框唤起键盘)
  - (BOOL)becomeFirstResponder;
  // 放弃第一响应者(如输入框收起键盘)
  - (BOOL)resignFirstResponder;
  • 事件传递规则

  1. 系统通过 hitTest:withEvent: 从父视图到子视图查找「最顶层可交互视图」;

  2. 找到后调用该视图的事件方法(如 touchesBegan:);

  3. 若该视图不处理,事件沿响应者链向上传递(子视图→父视图→控制器→Window→Application)。

4. UIView 核心(视图基础)

UIView 是所有可视化元素的基类,负责展示、布局、事件接收,核心属性/方法:

核心属性          说明                                                        
frame           相对于父视图的位置+尺寸(CGRect),决定视图在父视图中的显示区域      
bounds          自身坐标系的位置+尺寸(origin 默认 (0,0),修改会偏移子视图)
center          相对于父视图的中心点坐标(CGPoint)
transform       形变(缩放、旋转、平移,基于 center)
backgroundColor 背景色(UIColor)
alpha           透明度(0~1,0 完全透明,1 不透明)
hidden          是否隐藏(YES 隐藏,不参与布局/事件)
clipsToBounds   是否裁剪超出自身边界的子视图(YES 裁剪)
layer           底层 CALayer(负责渲染,UIView 是 CALayer 的封装)

二、UIKit 核心组件(常用控件)

1. 基础交互控件

控件类              用途                     核心 OC 示例
UILabel           文本展示                 UILabel *label = [[UILabel alloc] init]; label.text = @"Hello UIKit"; label.font = [UIFont systemFontOfSize:16];
UIButton          按钮(点击交互) UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem]; [btn setTitle:@"点击" forState:UIControlStateNormal]; [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
UITextField       单行文本输入             UITextField *tf = [[UITextField alloc] init]; tf.placeholder = @"请输入内容"; tf.keyboardType = UIKeyboardTypeDefault;
UITextView        多行文本输入/展示        UITextView *tv = [[UITextView alloc] init]; tv.text = @"多行文本"; tv.editable = YES;
UIImageView       图片展示                 UIImageView *iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon"]]; iv.contentMode = UIViewContentModeScaleAspectFit;
UISwitch          开关(开/关) UISwitch *sw = [[UISwitch alloc] init]; sw.on = YES; [sw addTarget:self action:@selector(switchChange:) forControlEvents:UIControlEventValueChanged];
UISlider          滑块(数值调节) UISlider *slider = [[UISlider alloc] init]; slider.minimumValue = 0; slider.maximumValue = 100; slider.value = 50;
UISegmentedControl 分段选择器               UISegmentedControl *seg = [[UISegmentedControl alloc] initWithItems:@[@"选项1", @"选项2"]]; seg.selectedSegmentIndex = 0;

2. 列表/集合控件(高频)

(1)UITableView(列表)

核心是「数据源+代理」模式,支持单行列表展示,OC 核心实现:


@interface TableViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView *tableView;

@property (nonatomic, strong) NSArray *dataArray;

@end

@implementation TableViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    self.dataArray = @[@"行1", @"行2", @"行3"];

    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];

    self.tableView.dataSource = self;

    self.tableView.delegate = self;

    // 注册单元格(复用)

    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellID"];

    [self.view addSubview:self.tableView];

}

// 数据源:行数

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.dataArray.count;

}

// 数据源:单元格内容

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath];

    cell.textLabel.text = self.dataArray[indexPath.row];

    return cell;

}

// 代理:单元格点击

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    NSLog(@"点击第%ld行", indexPath.row);

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

}

@end

(2)UICollectionView(集合视图)

支持网格、瀑布流等自定义布局,OC 核心实现:


@interface CollectionViewController () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

@property (nonatomic, strong) UICollectionView *collectionView;

@end

@implementation CollectionViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    // 布局配置(流式布局)

    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];

    layout.itemSize = CGSizeMake(100, 100); // 单元格尺寸

    layout.minimumInteritemSpacing = 10; // 列间距

    layout.minimumLineSpacing = 10; // 行间距

    self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];

    self.collectionView.dataSource = self;

    self.collectionView.delegate = self;

    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellID"];

    [self.view addSubview:self.collectionView];

}

// 数据源:单元格数量

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 12;
}

// 数据源:单元格内容

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellID" forIndexPath:indexPath];

    cell.backgroundColor = [UIColor lightGrayColor];

    return cell;

}

@end

3. 容器控件(页面导航/布局)

控件类                  用途                     核心 OC 示例                                                                
UIScrollView          可滚动视图(基础) UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; scrollView.contentSize = CGSizeMake(375, 1000); scrollView.showsVerticalScrollIndicator = YES;
UINavigationController 导航控制器(页面栈) UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; nav.navigationBar.barTintColor = [UIColor blueColor];
UITabBarController    标签栏控制器(底部切换) UITabBarController *tabBarVC = [[UITabBarController alloc] init]; tabBarVC.viewControllers = @[nav1, nav2]; tabBarVC.tabBar.tintColor = [UIColor redColor];
UIPageViewController  分页控制器(左右滑切换) UIPageViewController *pageVC = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];

4. 弹窗/提示控件

控件类                用途                     核心 OC 示例                                                                
UIAlertController   弹窗(警告/操作) UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"确定删除?" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {}]]; [self presentViewController:alert animated:YES completion:nil];
UIActivityIndicatorView 加载指示器         UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithStyle:UIActivityIndicatorViewStyleLarge]; [indicator startAnimating];
UIRefreshControl    下拉刷新                 UIRefreshControl *refresh = [[UIRefreshControl alloc] init]; [refresh addTarget:self action:@selector(refreshData:) forControlEvents:UIControlEventValueChanged]; self.tableView.refreshControl = refresh;

三、布局体系(UIKit 核心能力)

1. 基础布局(Frame/Bounds/Center)

手动控制视图位置,适合简单布局:


UIView *boxView = [[UIView alloc] init];

boxView.frame = CGRectMake(20, 100, 100, 100); // x:20, y:100, 宽100, 高100

boxView.center = CGPointMake(187.5, 150); // 中心点(父视图宽375,水平居中)

boxView.bounds = CGRectMake(-10, -10, 100, 100); // 自身坐标系偏移,子视图会右移/下移10pt

[self.view addSubview:boxView];

2. 自动布局(Auto Layout)

通过「约束」定义视图关系,适配多屏幕,OC 原生实现:


UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];

btn.translatesAutoresizingMaskIntoConstraints = NO; // 必须关闭自动掩码

[self.view addSubview:btn];

// 创建约束:按钮左/右间距20pt,顶部100pt,高度44pt

NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:btn attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0 constant:20];

NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:btn attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:-20];

NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:btn attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];

NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:btn attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:44];

// 添加约束

[self.view addConstraints:@[leading, trailing, top]];

[btn addConstraint:height];

Masonry 封装(OC 主流)


#import "Masonry.h"

[btn mas_makeConstraints:^(MASConstraintMaker *make) {

    make.leading.equalTo(self.view).offset(20);

    make.trailing.equalTo(self.view).offset(-20);

    make.top.equalTo(self.view).offset(100);

    make.height.mas_equalTo(44);

}];

3. 尺寸适配(Size Classes + Trait Collection)

适配多设备/横竖屏,OC 实现:


// 监听尺寸类变化

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {

    [super traitCollectionDidChange:previousTraitCollection];

    // 宽屏(如iPad/手机横屏)

    if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {

        [self.btn mas_updateConstraints:^(MASConstraintMaker *make) {

            make.height.mas_equalTo(60);

        }];

    } else { // 窄屏(手机竖屏)

        [self.btn mas_updateConstraints:^(MASConstraintMaker *make) {

            make.height.mas_equalTo(44);

        }];

    }

    [self.view layoutIfNeeded];

}

4. 安全区域(Safe Area)

适配刘海屏/底部横条,OC 实现:


// 约束适配安全区域

[btn mas_makeConstraints:^(MASConstraintMaker *make) {

    make.leading.equalTo(self.view.safeAreaLayoutGuide).offset(20);

    make.trailing.equalTo(self.view.safeAreaLayoutGuide).offset(-20);

    make.top.equalTo(self.view.safeAreaLayoutGuide).offset(20);

}];

四、事件处理(交互核心)

1. 触摸事件(UITouch)

自定义视图触摸处理:


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [touches anyObject];

    CGPoint point = [touch locationInView:self.view]; // 触摸点坐标

    NSLog(@"触摸位置:%@", NSStringFromCGPoint(point));

}

2. 手势识别(UIGestureRecognizer)

OC 核心示例(点击/长按/滑动):


// 点击手势

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];

tap.numberOfTapsRequired = 1; // 点击次数

[self.view addGestureRecognizer:tap];

// 长按手势

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGesture:)];

longPress.minimumPressDuration = 1.0; // 长按时长(秒)

[self.view addGestureRecognizer:longPress];


// 滑动手势

UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];

swipe.direction = UISwipeGestureRecognizerDirectionRight; // 滑动方向

[self.view addGestureRecognizer:swipe];

3. 响应者链拦截(hitTest:withEvent:)

自定义视图可点击区域:


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    // 若视图隐藏/透明/不可交互,不响应事件

    if (self.hidden || self.alpha <= 0.01 || !self.userInteractionEnabled) {

        return nil;

    }

    // 检查点是否在视图范围内

    if (![self pointInside:point withEvent:event]) {

        return nil;

    }

    // 优先返回子视图(倒序遍历,顶层视图优先)

    for (UIView *subview in [self.subviews reverseObjectEnumerator]) {

        CGPoint subPoint = [subview convertPoint:point fromView:self];

        UIView *hitView = [subview hitTest:subPoint withEvent:event];

        if (hitView) {

            return hitView;

        }

    }

    return self;

}

五、渲染与动画

1. 视图渲染(CALayer + drawRect:)

(1)CALayer 基础(UIView 底层渲染)


// 给视图添加圆角(通过layer)

self.view.layer.cornerRadius = 10;

self.view.layer.masksToBounds = YES; // 裁剪圆角

self.view.layer.borderWidth = 1.0;

self.view.layer.borderColor = [UIColor grayColor].CGColor;

  


// 阴影(注意:masksToBounds=NO 才生效)

self.view.layer.shadowColor = [UIColor blackColor].CGColor;

self.view.layer.shadowOffset = CGSizeMake(2, 2);

self.view.layer.shadowOpacity = 0.5;

self.view.layer.shadowRadius = 4;

(2)自定义绘制(drawRect:)


- (void)drawRect:(CGRect)rect {

    // 获取绘图上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 设置画笔颜色

    CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);

    // 设置线宽

    CGContextSetLineWidth(ctx, 2.0);

    // 绘制矩形

    CGContextAddRect(ctx, CGRectMake(20, 20, 100, 100));

    // 绘制路径

    CGContextStrokePath(ctx);

}

2. UIView 动画(基础动画)


// 平移动画

[UIView animateWithDuration:0.3 animations:^{

    self.btn.center = CGPointMake(self.btn.center.x + 100, self.btn.center.y);

} completion:^(BOOL finished) {

    // 动画完成回调

}];

// 组合动画(缩放+旋转)

[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{

    self.btn.transform = CGAffineTransformMakeScale(1.5, 1.5); // 缩放

    self.btn.transform = CGAffineTransformRotate(self.btn.transform, M_PI_4); // 旋转45度

} completion:nil];

// 转场动画

[UIView transitionWithView:self.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{

    [self.view addSubview:self.newView];

} completion:nil];

3. Core Animation(核心动画,底层)


// 关键帧动画(路径动画)

CAKeyframeAnimation *keyFrame = [CAKeyframeAnimation animationWithKeyPath:@"position"];

CGMutablePathRef path = CGPathCreateMutable();

CGPathMoveToPoint(path, NULL, 20, 100);

CGPathAddLineToPoint(path, NULL, 355, 100);

CGPathAddLineToPoint(path, NULL, 355, 500);

CGPathAddLineToPoint(path, NULL, 20, 500);

keyFrame.path = path;

keyFrame.duration = 2.0;

[self.btn.layer addAnimation:keyFrame forKey:@"keyFrameAnimation"];

六、多态适配(暗黑模式/动态字体)

1. 暗黑模式(iOS 13+)


// 动态颜色(适配暗黑/浅色模式)

UIColor *dynamicColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {

    if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {

        return [UIColor whiteColor]; // 暗黑模式

    } else {

        return [UIColor blackColor]; // 浅色模式

    }

}];

self.view.backgroundColor = dynamicColor;

// 监听模式变化

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {

    [super traitCollectionDidChange:previousTraitCollection];

    if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {

        // 更新颜色/图片

        self.label.textColor = dynamicColor;

    }

}

2. 动态字体(适配字体大小)


// 动态字体(跟随系统字体大小)

UIFont *dynamicFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

self.label.font = dynamicFont;

// 监听字体大小变化

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fontSizeChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil];

七、性能优化

1. 列表优化(UITableView/UICollectionView)

  • 复用单元格(dequeueReusableCellWithIdentifier:);

  • 缓存单元格高度(避免重复计算);

  • 异步加载图片(SDWebImage 等);

  • 减少离屏渲染(避免圆角+阴影同时设置);

  • 禁用不必要的动画(cell.selectionStyle = UITableViewCellSelectionStyleNone)。

2. 渲染优化

  • 避免频繁调用 setNeedsLayout/layoutIfNeeded

  • 自定义绘制优先用 CALayer 而非 drawRect:

  • 开启光栅化(layer.shouldRasterize = YES,仅适合静态视图);

  • 减少透明视图(alpha < 1 会触发离屏渲染)。

3. 内存优化

  • 避免循环引用(block 中用 weakSelf);

  • 图片压缩(UIImageJPEGRepresentation/UIImagePNGRepresentation);

  • 及时释放强引用(dealloc 中移除通知/定时器);

  • 懒加载控件(避免一次性创建大量视图)。

八、调试工具

1. Xcode 内置工具

  • Debug View Hierarchy:可视化查看视图层级、约束、frame;

  • Instruments

  - Core Animation:检测离屏渲染、帧率;

  - Time Profiler:检测卡顿;

  - Allocations:检测内存泄漏;

  • 控制台日志:打印约束冲突、视图信息(NSLog(@"frame: %@", NSStringFromCGRect(self.view.frame)))。

2. 约束冲突排查

  • 控制台日志中定位「Unable to simultaneously satisfy constraints」;

  • 降低非核心约束优先级(constraint.priority = UILayoutPriorityDefaultHigh);

  • 动态激活/禁用约束(constraint.active = YES/NO)。

九、进阶特性

1. 自定义控件

继承 UIView/UIControl 实现自定义交互控件:


@interface CustomControl : UIControl

@property (nonatomic, assign) CGFloat progress;

@end

@implementation CustomControl

- (void)setProgress:(CGFloat)progress {

    _progress = progress;

    [self setNeedsDisplay]; // 触发重绘

}

- (void)drawRect:(CGRect)rect {

    // 绘制进度条

    CGRect progressRect = CGRectMake(0, 0, rect.size.width * self.progress, rect.size.height);

    [[UIColor blueColor] setFill];

    UIRectFill(progressRect);

}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [touches anyObject];

    CGPoint point = [touch locationInView:self];

    self.progress = point.x / self.bounds.size.width;

    [self sendActionsForControlEvents:UIControlEventValueChanged]; // 触发值变化事件

}

@end

2. 文本排版(NSAttributedString)


NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:@"富文本示例"];

// 设置字体

[attStr addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:18] range:NSMakeRange(0, 3)];

// 设置颜色

[attStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 3)];

// 设置下划线

[attStr addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(3, 2)];

self.label.attributedText = attStr;

十、最佳实践

  1. MVC 分层:控制器仅负责逻辑调度,视图仅负责展示,模型仅负责数据;

  2. 控件封装:将重复的控件逻辑封装为分类/子类(如 UIButton+Custom);

  3. 兼容性处理:通过 @available 适配不同 iOS 版本:


   if (@available(iOS 13.0, *)) {

       // iOS 13+ 逻辑

   } else {

       // 低版本逻辑

   }

  1. 避免生命周期陷阱viewDidLoad 仅初始化,viewWillAppear 处理每次显示的逻辑;

  2. 响应链优化:减少不必要的 userInteractionEnabled = NO,避免事件传递卡顿。

总结

UIKit 是 iOS 开发的「基石框架」,核心围绕「视图(UIView)- 控制器(UIViewController)- 事件(UIResponder)」展开,掌握「布局体系」「事件处理」「渲染优化」是关键。实际开发中,优先用 Masonry 简化 Auto Layout,结合 Size Classes/暗黑模式适配多场景,通过 Instruments 定位性能问题,可高效构建稳定、适配性强的 iOS 界面。

❌
❌