SwiftUI实战:构建现代化iOS应用界面的架构思考
去年双11那会儿,我还在上一家公司做产品经理,天天和开发团队扯皮“这个按钮能不能再大2px”、“用户路径是不是太深了”。结果今年年初,我直接转岗成了iOS开发——没错,就是那个自己画原型、自己写代码、自己测Bug的斜杠青年。
深夜写代码成了我的日常节奏,咖啡当水喝,键盘敲到冒烟。三年多下来,项目上线了、KPI完成了,但总觉得少了点什么。最近开始认真考虑换个环境,于是狠狠补了一波SwiftUI,想把之前产品视角下的“理想界面”真正用代码实现出来。
为什么是SwiftUI?
坦白说,刚接触SwiftUI时我是抗拒的。毕竟之前用UIKit写了两年,各种delegate、dataSource、AutoLayout已经刻进DNA了。但现实很骨感:Apple在WWDC上反复强调“声明式UI是未来”,App Store审核也越来越偏爱符合VisionOS设计语言的应用。再加上我们团队新来的设计师动不动就甩来一套Figma文件,里面全是动态渐变、交互动效、响应式布局——UIKit硬写?怕不是要肝到明年。
更重要的是,作为曾经的产品经理,我深知界面即体验。用户不会关心你用了什么框架,他们只在乎滑得顺不顺、加载快不快、崩不崩溃。而SwiftUI天然支持响应式、状态驱动、跨平台(至少Apple全家桶),这不正好契合“现代化”的定义吗?
架构设计:别让View变成一锅粥
很多团队一上来就狂写@State、@Binding,结果三个月后代码变成意大利面条——View里塞网络请求、业务逻辑、数据处理,改个颜色都要翻半天。我吃过这亏,所以这次从第一天就定下规矩:
View只负责展示,逻辑交给ViewModel,数据交给Repository
听起来像老生常谈?但执行起来真没那么简单。举个例子,我们有个“资产看板”页面,要展示用户的数字资产余额——对,这里就涉及区块链数据。链上查询慢、接口不稳定、还要处理钱包地址绑定,如果全塞进View里,那画面我不敢看。
于是我们搞了个分层架构:
View (SwiftUI)
↓
ViewModel (ObservableObject)
↓
Repository (负责区块链API调用 + 本地缓存)
↓
Network Layer / Wallet SDK
关键在于,ViewModel不直接持有任何UI组件,也不依赖UIKit。它只暴露@Published属性供View监听,比如:
class AssetViewModel: ObservableObject {
@Published var balance: String = "Loading..."
@Published var isLoading = false
private let repository: AssetRepository
init(repository: AssetRepository) {
self.repository = repository
}
func fetchBalance(for address: String) {
isLoading = true
Task {
do {
let balance = try await repository.getBalance(from: address)
await MainActor.run {
self.balance = balance.formatted()
self.isLoading = false
}
} catch {
// 统一错误处理,比如弹Toast
print("Failed to fetch balance: $error)")
await MainActor.run {
self.isLoading = false
}
}
}
}
}
这样,View只需要一行代码就能绑定状态:
struct AssetView: View {
@StateObject private var viewModel = AssetViewModel(repository: .live)
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView()
} else {
Text("余额: \(viewModel.balance)")
.font(.title2)
.fontWeight(.semibold)
}
}
.onAppear {
viewModel.fetchBalance(for: "0x...") // 实际从用户上下文获取
}
}
}
工具链:提升效率的隐形推手
说到工具,我必须吐槽一下:很多团队还在用手动截图+人工核对做UI验收,效率低到令人发指。自从我引入了以下组合拳,前端同学终于不用半夜被产品经理call起来改间距了:
| 工具 | 用途 | 效果 |
|---|---|---|
swiftformat + swiftlint |
代码格式化 & 规范检查 | PR不再因缩进吵架 |
SnapshotTesting |
UI快照测试 | 自动捕获视觉回归 |
Xcode Previews |
实时预览 | 设计师直接看效果,不用等编译 |
特别是Xcode Previews,简直是跨部门协作神器。以前设计师提需求:“这个卡片阴影要更柔和一点”,我得改完打包发TestFlight,等他反馈再改。现在直接在Preview里调参数,实时看到效果,他坐我旁边都能点头说“就这个!”
struct AssetCard_Previews: PreviewProvider {
static var previews: some View {
AssetCard(balance: "5.23 ETH")
.preferredColorScheme(.light)
AssetCard(balance: "5.23 ETH")
.preferredColorScheme(.dark)
}
}
运营需求?别让代码变成打补丁现场
上周五晚上,运营同学突然找我:“老板说下周要上线一个活动Banner,能加个轮播图吗?最好还能埋点统计点击率。”
我差点一口老血喷出来——这都快下班了,还来需求?但转念一想,这不正是考验架构弹性的时候?
如果View里硬编码Banner逻辑,下次换活动又得改代码、走审核,黄花菜都凉了。所以我们搞了个可配置化运营组件:
struct OperationalBannerView: View {
let config: BannerConfig // 从远程配置中心拉取
var body: some AssistantView {
if config.isEnabled {
AsyncImage(url: config.imageUrl) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.onTapGesture {
trackEvent("banner_click", params: ["id": config.id])
openURL(config.targetURL)
}
} placeholder: {
Rectangle().foregroundColor(.gray.opacity(0.2))
}
}
}
}
BannerConfig结构体由后端通过Firebase Remote Config或自建配置服务下发,包含图片URL、跳转链接、是否启用等字段。这样一来,运营改Banner再也不用求开发,真正实现“所见即所得”。
App Store审核那些坑
说到上架,Apple最近对区块链相关应用审核越来越严。我们第一次提交时,因为没在设置里提供“清除钱包缓存”选项,直接被拒。理由是:“App collects user data without clear deletion mechanism.”
后来学乖了,在SwiftUI里专门做了个隐私设置页面:
struct PrivacySettingsView: View {
@AppStorage("walletAddress") private var walletAddress: String = ""
var body: some View {
Form {
Section("账户安全") {
Button("解绑钱包", role: .destructive) {
walletAddress = ""
// 同时清除Keychain中的私钥引用
WalletManager.shared.clearSession()
}
}
}
.navigationTitle("隐私与安全")
}
}
另外,Apple明确要求不能有“挖矿”、“代币交易”等诱导性文案。我们原本有个按钮叫“立即挖矿”,改成“查看收益”才过审。这些细节,光靠技术不够,还得懂运营合规。
写在最后:从产品到开发的视角转换
回头看这段SwiftUI实战经历,最大的收获不是写了多少行代码,而是用工程思维重新理解用户体验。以前做产品时,总觉得“加个功能很简单”;现在写代码才知道,一个看似简单的交互动画,背后可能是性能、兼容性、可维护性的多重权衡。
如果你也在考虑转型,或者正在用SwiftUI重构老项目,我的建议是:
别只盯着语法糖,先想清楚架构。 声明式UI的优势在于“描述是什么”,而不是“怎么做”。把状态管理、数据流、副作用隔离清楚,后期迭代才能游刃有余。
至于跳槽?我已经投了几家Web3方向的公司,他们正好需要既懂产品又会写SwiftUI的人——毕竟,能把区块链数据优雅地展示在iOS界面上,还不被App Store拒掉,这种人不多 😏
深夜码字完毕,咖啡见底,明天继续肝。希望这篇带点人味儿的分享,能帮你在现代化iOS开发路上少踩几个坑。

评论 0