基于 UIKit 与 Combine 的 MVVM 架构实时计算界面
功能需求
- 界面包含三个输入框和一个结果显示标签。
- 标签需动态展示三个输入框中数值的总和。
- 若输入非数字内容,自动视为零处理。
- 初始值分别为:第一个输入框为1,第二个为2,第三个为3。
项目初始化
在 Xcode 中选择 File > New > Project,创建 iOS 应用工程。
填写项目名称为 CombineExample,选择任意目录后点击 Create 完成创建。
随后关闭当前工程以进行后续配置。
集成 CombineCocoa 依赖
在项目导航器中选中工程名,右键选择 Add Packages...。
在搜索框中输入:https://github.com/CombineCommunity/CombineCocoa.git,回车后添加该包。
确认后,项目依赖列表将显示 CombineCocoa 0.4.0 已成功引入。
数据模型层设计
新建文件 NumbersViewModel.swift,定义视图模型逻辑:
import Foundation
import Combine
class NumbersViewModel: ObservableObject {
@Published var inputA = "1"
@Published var inputB = "2"
@Published var inputC = "3"
@Published var totalDisplay = ""
init() {
// 合并三个输入字段的值,转换为整数求和,并更新结果
$inputA.combineLatest($inputB, $inputC)
.map { first, second, third in
let sum = (Int(first) ?? 0) + (Int(second) ?? 0) + (Int(third) ?? 0)
return String(sum)
}
.assign(to: &$totalDisplay)
}
}
界面布局设置
打开 Main.storyboard,在主视图控制器的根视图中拖入三个 UITextField 及一个 UILabel,按需排列。
绑定控件引用
在 ViewController.swift 中声明对应的 IBOutlet 属性:
@IBOutlet weak var textFieldA: UITextField!
@IBOutlet weak var textFieldB: UITextField!
@IBOutlet weak var textFieldC: UITextField!
@IBOutlet weak var resultLabel: UILabel!
使用 Assistant Editor 并按住 Control 键,分别将这些属性连接到对应组件。
实现 MVVM 数据流
在 ViewController 类中加入视图模型实例及绑定逻辑:
import UIKit
import Combine
import CombineCocoa
class ViewController: UIViewController {
private var viewModel: NumbersViewModel!
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
viewModel = NumbersViewModel()
// 反向绑定:视图模型变化 → 控件显示
viewModel.$inputA.assign(to: \.text, on: textFieldA, owner: self).store(in: &cancellables)
viewModel.$inputB.assign(to: \.text, on: textFieldB, owner: self).store(in: &cancellables)
viewModel.$inputC.assign(to: \.text, on: textFieldC, owner: self).store(in: &cancellables)
viewModel.$totalDisplay.assign(to: \.text, on: resultLabel, owner: self).store(in: &cancellables)
// 正向绑定:用户输入 → 视图模型更新(过滤非空输入)
textFieldA.textPublisher.compactMap { $0 }.assign(to: &viewModel.$inputA).store(in: &cancellables)
textFieldB.textPublisher.compactMap { $0 }.assign(to: &viewModel.$inputB).store(in: &cancellables)
textFieldC.textPublisher.compactMap { $0 }.assign(to: &viewModel.$inputC).store(in: &cancellables)
}
}