name: ios-uikit-architecture user-invocable: false description: 用于构建使用UIKit的iOS应用、实现MVVM/MVC/Coordinator模式或集成UIKit与SwiftUI时使用。 allowed-tools:
- Read
- Write
- Edit
- Bash
- Grep
- Glob
iOS - UIKit 架构
基于UIKit的iOS应用程序的架构模式和最佳实践。
关键概念
MVVM 架构
模型-视图-视图模型模式分离关注点:
- 模型:数据和业务逻辑
- 视图:UIViewController和UIView子类
- 视图模型:表示逻辑,转换模型数据以供显示
// Model
struct User {
let id: String
let firstName: String
let lastName: String
let email: String
}
// ViewModel
class UserProfileViewModel {
private let user: User
var displayName: String {
"\(user.firstName) \(user.lastName)"
}
var emailDisplay: String {
user.email.lowercased()
}
init(user: User) {
self.user = user
}
}
// View
class UserProfileViewController: UIViewController {
private let viewModel: UserProfileViewModel
init(viewModel: UserProfileViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = viewModel.displayName
emailLabel.text = viewModel.emailDisplay
}
}
Coordinator 模式
协调器处理导航流,从视图控制器中移除导航逻辑:
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get }
func start()
}
class AppCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let vc = HomeViewController()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
}
func showDetail(for item: Item) {
let detailCoordinator = DetailCoordinator(
navigationController: navigationController,
item: item
)
childCoordinators.append(detailCoordinator)
detailCoordinator.start()
}
}
依赖注入
通过初始化器注入依赖以提高可测试性:
protocol UserServiceProtocol {
func fetchUser(id: String) async throws -> User
}
class UserViewController: UIViewController {
private let userService: UserServiceProtocol
private let userId: String
init(userService: UserServiceProtocol, userId: String) {
self.userService = userService
self.userId = userId
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) not supported")
}
}
最佳实践
使用自动布局的程序化UI
class ProfileView: UIView {
private let avatarImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .headline)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
setupConstraints()
}
private func setupViews() {
addSubview(avatarImageView)
addSubview(nameLabel)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
avatarImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
avatarImageView.widthAnchor.constraint(equalToConstant: 80),
avatarImageView.heightAnchor.constraint(equalToConstant: 80),
nameLabel.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 12),
nameLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
])
}
}
使用可区分数据源的现代集合视图
class ItemListViewController: UIViewController {
enum Section { case main }
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
configureCollectionView()
configureDataSource()
}
private func configureCollectionView() {
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
let layout = UICollectionViewCompositionalLayout.list(using: config)
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(collectionView)
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> { cell, indexPath, item in
var content = cell.defaultContentConfiguration()
content.text = item.title
content.secondaryText = item.subtitle
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
collectionView, indexPath, item in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
}
func updateItems(_ items: [Item]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: true)
}
}
UIKit 和 SwiftUI 集成
在UIKit中托管SwiftUI:
class SettingsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swiftUIView = SettingsView()
let hostingController = UIHostingController(rootView: swiftUIView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.frame = view.bounds
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostingController.didMove(toParent: self)
}
}
在SwiftUI中包装UIKit:
struct MapViewRepresentable: UIViewRepresentable {
@Binding var region: MKCoordinateRegion
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.setRegion(region, animated: true)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapViewRepresentable
init(_ parent: MapViewRepresentable) {
self.parent = parent
}
}
}
常见模式
视图控制器生命周期管理
class DataViewController: UIViewController {
private var loadTask: Task<Void, Never>?
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
loadTask?.cancel()
}
private func loadData() {
loadTask = Task {
do {
let data = try await fetchData()
guard !Task.isCancelled else { return }
updateUI(with: data)
} catch {
showError(error)
}
}
}
}
使用闭包的内存管理
class NetworkViewController: UIViewController {
private let networkService: NetworkService
func fetchData() {
// 使用 [weak self] 防止循环引用
networkService.fetch { [weak self] result in
guard let self else { return }
switch result {
case .success(let data):
self.handleData(data)
case .failure(let error):
self.showError(error)
}
}
}
}
反模式
庞大的视图控制器
坏:把所有东西放在一个视图控制器中。
好:提取到单独的类型:
- 视图模型用于表示逻辑
- 协调器用于导航
- 自定义视图用于UI组件
- 服务用于网络/数据操作
故事板 segue 意大利面
坏:复杂的故事板与许多segue。
好:使用协调器进行程序化导航。
强制转换单元格
坏:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! CustomCell
好:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? CustomCell else {
fatalError("Unable to dequeue CustomCell")
}
相关技能
- ios-swiftui-patterns:现代声明式UI
- ios-swift-concurrency:异步数据加载