ios基础-MVC-UIView


05 | MVC 模式:iOS 中的架构基石

5.1 什么是 MVC

MVC 是 Model-View-Controller 的缩写,是 iOS 开发中最基础、最经典的架构模式。它将程序分为三个角色:

角色 职责 典型实现
Model(模型) 管理数据和业务逻辑 数据类、网络请求、数据库操作
View(视图) 负责界面展示和用户交互 UIView 及其子类(UILabel、UIButton 等)
Controller(控制器) 协调 Model 和 View,处理业务逻辑 UIViewController 及其子类

5.2 三者的关系与数据流

复制代码
用户操作 → View → Controller → Model(更新数据)
                                    ↓
               View ← Controller ← Model(数据变化通知)

核心原则:

  • View 不直接访问 Model:视图只负责展示,不知道数据从哪来
  • Model 不依赖 View 和 Controller:模型是独立的,可以脱离界面使用
  • Controller 是桥梁:负责从 Model 获取数据,更新 View;接收 View 的用户事件,操作 Model

5.3 iOS 中 MVC 的特殊性

iOS 的 MVC 有时被称为 "Massive View Controller"(臃肿的控制器),因为 Controller 往往承担过多职责:

  • 页面跳转
  • 数据请求
  • 视图布局
  • 事件处理

Apple 的 MVC 中,View 和 Model 之间是严格隔离的,所有交互都必须经过 Controller。这和传统 Web MVC 有所不同。

5.4 实际代码示例

swift 复制代码
// ===== Model =====
class UserModel {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

// ===== View =====
class ProfileView: UIView {
    let nameLabel = UILabel()
    let ageLabel = UILabel()
    
    func configure(with user: UserModel) {
        nameLabel.text = user.name
        ageLabel.text = "\(user.age) 岁"
    }
}

// ===== Controller =====
class ProfileViewController: UIViewController {
    private let profileView = ProfileView()
    private var user = UserModel(name: "zzb", age: 25)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        profileView.configure(with: user)  // Controller 从 Model 取数据,传给 View
    }
}

5.5 MVC 的优缺点

优点:

  • 职责清晰,便于团队协作
  • iOS 框架原生支持
  • 入门门槛低

缺点:

  • Controller 容易变得臃肿
  • 不利于复杂项目的维护和测试
  • 后续可考虑 MVVM、VIPER 等进阶架构

5.6 复习要点

  • 能说出 M、V、C 各自的职责
  • 理解数据流方向(View ↔ Controller ↔ Model)
  • 知道为什么 iOS 的 Controller 容易"膨胀"
  • 能用简单的代码实现一个 MVC 结构

06 | iOS 中的视图 UIView

6.1 UIView 是什么

UIView 是所有 iOS 界面元素的基类。你在屏幕上看到的一切------按钮、标签、图片、文本框------都是 UIView 的子类。

复制代码
UIView
├── UILabel(文本标签)
├── UIButton(按钮)
├── UIImageView(图片视图)
├── UITextField(文本输入框)
├── UITextView(多行文本)
├── UIScrollView(滚动视图)
├── UITableView(表格视图)
├── UICollectionView(集合视图)
└── ... 等等

6.2 UIView 的核心属性

位置和尺寸
swift 复制代码
// frame:相对于父视图的位置和大小(绝对坐标)
view.frame = CGRect(x: 20, y: 100, width: 200, height: 50)

// bounds:相对于自身坐标系的位置和大小(通常 origin 为 0,0)
view.bounds = CGRect(x: 0, y: 0, width: 200, height: 50)

// center:视图中心点坐标
view.center = CGPoint(x: 187.5, y: 406)

frame vs bounds 的区别:

  • frame = 在父视图坐标系中的位置 + 大小
  • bounds = 在自身坐标系中的大小(用于内部绘制)
  • 旋转/缩放时,frame 会变,bounds 不变
外观属性
swift 复制代码
view.backgroundColor = .systemBlue     // 背景色
view.alpha = 0.8                       // 透明度(0.0~1.0)
view.isHidden = false                  // 是否隐藏
view.layer.cornerRadius = 10           // 圆角
view.layer.borderWidth = 1.0           // 边框宽度
view.layer.borderColor = UIColor.gray.cgColor  // 边框颜色
view.clipsToBounds = true              // 裁剪超出部分
view.tag = 100                         // 标签(可通过 viewWithTag 查找)
用户交互
swift 复制代码
view.isUserInteractionEnabled = true  // 是否允许交互
view.isMultipleTouchEnabled = false   // 是否支持多点触控

6.3 视图层级(View Hierarchy)

swift 复制代码
// 添加子视图
parentView.addSubview(childView)

// 从父视图移除
childView.removeFromSuperview()

// 插入到指定层级
parentView.insertSubview(childView, at: 0)       // 最底层
parentView.insertSubview(childView, belowSubview: otherView)
parentView.insertSubview(childView, aboveSubview: otherView)

// 调整层级
parentView.bringSubviewToFront(childView)  // 移到最前面
parentView.sendSubviewToBack(childView)    // 移到最后面

// 获取关系
let parent = childView.superview      // 父视图
let children = parentView.subviews    // 所有子视图
let found = parentView.viewWithTag(100)  // 通过 tag 查找

6.4 坐标系

iOS 的坐标系原点在左上角

  • X 轴向右增大

  • Y 轴向下增大(和数学坐标系相反)

    (0,0) ──────────────→ X

    │ (x, y) ●


    Y

6.5 使用代码创建视图

swift 复制代码
override func viewDidLoad() {
    super.viewDidLoad()
    
    // 1. 创建视图
    let redView = UIView()
    redView.frame = CGRect(x: 50, y: 100, width: 200, height: 200)
    redView.backgroundColor = .systemRed
    redView.layer.cornerRadius = 16
    redView.clipsToBounds = true
    
    // 2. 添加到视图层级
    view.addSubview(redView)
    
    // 3. 添加子标签
    let label = UILabel()
    label.text = "Hello, iOS!"
    label.frame = CGRect(x: 0, y: 80, width: 200, height: 40)
    label.textAlignment = .center
    redView.addSubview(label)
}

6.6 Auto Layout 基础

除了 frame 布局,现代 iOS 开发更常用 Auto Layout(约束布局):

swift 复制代码
let label = UILabel()
label.text = "Auto Layout"
label.translatesAutoresizingMaskIntoConstraints = false  // 关键!关闭自动约束转换
view.addSubview(label)

NSLayoutConstraint.activate([
    label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20)
])

6.7 复习要点

  • 理解 UIView 是所有 UI 元素的基类
  • 掌握 frame 和 bounds 的区别
  • 会用代码创建视图并添加到层级
  • 理解 iOS 坐标系(原点在左上角)
  • 掌握子视图的增删和层级调整方法

07 | 了解 UIView 的生命周期

7.1 生命周期总览

UIView 从创建到销毁,经历以下关键阶段:

复制代码
init → layoutSubviews → draw → willMoveToSuperview
  → didMoveToSuperview → willMoveToWindow → didMoveToWindow
  → ... 运行中 ...
  → removeFromSuperview → dealloc

7.2 关键生命周期方法

初始化阶段
swift 复制代码
// 代码初始化(最常用)
override init(frame: CGRect) {
    super.init(frame: frame)
    // 自定义初始化逻辑
    setupUI()
}

// 从 Storyboard/XIB 加载时调用
required init?(coder: NSCoder) {
    super.init(coder: coder)
    setupUI()
}
布局阶段
swift 复制代码
// 布局子视图 ------ 非常重要的方法
override func layoutSubviews() {
    super.layoutSubviews()
    // 每次视图需要重新布局子视图时调用
    // 场景:旋转屏幕、约束变化、子视图 frame 需要动态计算
    
    // ⚠️ 不要在 layoutSubviews 中调用 setNeedsLayout(),会导致死循环
}

// 返回合适的尺寸
override func sizeThatFits(_ size: CGSize) -> CGSize {
    return CGSize(width: size.width, height: 44)
}

// 自动调整大小以适应内容
override func sizeToFit() {
    // 调用 sizeThatFits 后自动设置 frame
}
绘制阶段
swift 复制代码
// 自定义绘制 ------ 在 draw 方法里用 Core Graphics 绘图
override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    
    // 画一条线
    context.move(to: CGPoint(x: 0, y: 0))
    context.addLine(to: CGPoint(x: rect.width, y: rect.height))
    context.setStrokeColor(UIColor.red.cgColor)
    context.strokePath()
}

触发绘制的时机:

  • 视图首次出现
  • 调用 setNeedsDisplay() 标记需要重绘
  • 调用 setNeedsDisplay(_:) 指定区域重绘
视图层级变化
swift 复制代码
// 即将被添加到父视图
override func willMove(toSuperview newSuperview: UIView?) {
    super.willMove(toSuperview: newSuperview)
    // newSuperview 为 nil 表示即将从父视图移除
}

// 已经被添加到父视图
override func didMoveToSuperview() {
    super.didMoveToSuperview()
    // 此时 superview 已更新
}

// 即将被添加到窗口
override func willMove(toWindow newWindow: UIWindow?) {
    super.willMove(toWindow: newWindow)
}

// 已经被添加到窗口
override func didMoveToWindow() {
    super.didMoveToWindow()
    // window 不为 nil = 视图正在显示
    // window 为 nil = 视图已从显示中移除
}

7.3 刷新机制

swift 复制代码
// 标记需要重新布局(下一个 runloop 周期执行 layoutSubviews)
view.setNeedsLayout()

// 立即触发布局更新(同步执行 layoutSubviews)
view.layoutIfNeeded()

// 标记需要重新绘制(下一个 runloop 周期执行 draw)
view.setNeedsDisplay()

常用组合 --- 动画:

swift 复制代码
view.setNeedsLayout()
UIView.animate(withDuration: 0.3) {
    view.layoutIfNeeded()  // 在动画块中触发,产生平滑动画
}

7.4 生命周期调用顺序(完整版)

复制代码
1. init(frame:) / init(coder:)     --- 创建
2. willMove(toSuperview:)           --- 即将加入父视图
3. didMoveToSuperview()             --- 已加入父视图
4. willMove(toWindow:)              --- 即将加入 window
5. didMoveToWindow()                --- 已加入 window
6. layoutSubviews()                 --- 布局子视图
7. draw(_:)                         --- 绘制
8. [运行中,可能多次调用 6 和 7]
9. removeFromSuperview()            --- 移除
10. willMove(toSuperview: nil)      --- 即将离开父视图
11. didMoveToSuperview()            --- 已离开父视图
12. willMove(toWindow: nil)         --- 即将离开 window
13. didMoveToWindow()               --- 已离开 window
14. dealloc                         --- 释放

7.5 复习要点

  • 掌握 init(frame:) 和 init(coder:) 的区别
  • 理解 layoutSubviews 的调用时机和注意事项
  • 知道 setNeedsLayout 和 layoutIfNeeded 的区别
  • 了解 willMove/didMove 系列方法的用途
  • 能说出完整的生命周期调用顺序
相关推荐
kingbal2 小时前
Flutter:Flutter SDK版本管理工具FVM
android·flutter·ios·android-studio·window
他们都不看好你,偏偏你最不争气18 小时前
【iOS】Runtime - Part 2 && 消息发送:缓存、查找与转发
macos·ios·objective-c·cocoa
2501_915918411 天前
iOS App性能测试工具的实现方法与优化循环指南
android·ios·小程序·https·uni-app·iphone·webview
他们都不看好你,偏偏你最不争气1 天前
【iOS】Runtime - Part 1 && 对象与类的本质
macos·ios·objective-c·cocoa
黑科技iOS上架1 天前
Swift6.0多线程特性注意事项
ios
黑科技iOS上架1 天前
实测iOS深度混淆工具过审4.3、2.3.1能力
经验分享·ios
鹤卿1232 天前
(OC)UI学习——网易云仿写
ui·ios·objective-c
不自律的笨鸟2 天前
最新屏蔽 iOS 系统更新描述文件保姆级教程
ios