SwiftUI实战:从零搭建现代化iOS界面的血泪经验
上周五晚上十点半,我正用 VSCode 改一个 Spark 作业的 UDF,突然收到产品经理的消息:“兄弟,咱们那个 iOS 客户端能不能下周上线?就你之前提过的那个 Swift 原型?”
我差点一口老血喷在键盘上——那只是我在公司 hackathon 上随手写的 demo 啊!
但转念一想,正好最近被 Rust 的所有权模型折磨得神志不清,不如换换脑子,回归一下 Apple 生态。于是,作为一枚天天和 Spark 打交道、坐标深圳某腾讯系公司的“大数据民工”,我硬着头皮接下了这个“副业”任务。
为什么选 SwiftUI?
其实一开始我是拒绝的。毕竟咱主业是 Scala + PySpark + Hive,写 iOS 应用?上一次写还是大学 iOS 课交作业的时候。但这次情况特殊:
- 项目要快,最好两周内上线
- 团队没人会 Objective-C(95 后新兵居多)
- 设计稿全是 SF Symbols、圆角卡片、动态字体——典型的 Apple Design Guidelines 风格
SwiftUI 虽然“年轻”,但它声明式、响应式的特性,配合 Xcode 的预览功能,开发效率确实高。尤其是对像我这种“非专业 iOS 工程师”来说,比 UIKit 友好多了。
当然,Xcode 还是那个祖传 IDE —— 内存吃得多、插件少、调试反人类。所以我依然坚持用 VSCode 写逻辑代码(装了 Swift 语法高亮和格式化插件),只在需要预览或真机调试时才切回 Xcode。
实战案例:一个「数据看板」类 App
我们的需求很简单:展示用户行为日志的聚合指标(比如 DAU、留存率),支持下拉刷新、深色模式、国际化。听起来 trivial,但踩的坑一点不少。
坑1:状态管理别乱搞!
一开始我图省事,把所有状态塞进 @State,结果页面一复杂,状态同步就崩了。比如:
struct DashboardView: View {
@State private var dau = 0
@State private var retention = 0.0
// ...一堆 state
}
后来改用 ViewModel + @ObservedObject,配合 Combine 或 async/await 拉数据,结构清晰多了:
class MetricsViewModel: ObservableObject {
@Published var dau: Int = 0
@Published var isLoading = false
func fetchMetrics() async {
isLoading = true
do {
let data = try await API.fetchDailyMetrics()
DispatchQueue.main.async {
self.dau = data.dau
}
} catch {
print("Fetch failed: $error)")
}
isLoading = false
}
}
提醒一句:别在 ViewModel 里直接操作 UI 线程!虽然 Swift 并发模型很香,但
@Published必须在主线程更新,否则 Xcode 会给你一个华丽丽的 crash。
坑2:适配深色模式?没那么简单!
Apple 强推深色模式,App Store 审核也默认要求支持。但你以为只要换个背景色就完事了?
错!SF Symbols 在深色模式下会自动反转,但自定义图标不会。我们有个小火箭 icon,在浅色模式是白色轮廓,深色模式下就“消失”了。
解决方案:用 Asset Catalog 里的 Appearances → Any, Dark 分别提供两套图片。或者更优雅点,用 Color.accentColor + foregroundColor(.primary) 让系统自动处理。
Image("rocket")
.foregroundColor(.primary) // 自动适配深浅色
坑3:国际化不是加个 Localizable.strings 就行
我们产品要上架中国、美国、日本三区。本来以为把文案抽成 NSLocalizedString("dashboard.title", comment: "") 就 OK,结果测试说日文版文字被截断。
原因?不同语言文本长度差异巨大!
| 语言 | “数据概览” 字符数 |
|---|---|
| 中文 | 4 |
| 英文 | 12 ("Data Overview") |
| 日文 | 6 (“データ概要”) |
解决方案:布局别写死宽度!用 GeometryReader 或 frame(maxWidth: .infinity) 让容器自适应。同时在 Xcode 的 Preview 里直接切换语言测试:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.environment(\.locale, Locale(identifier: "zh-CN"))
ContentView()
.environment(\.locale, Locale(identifier: "en-US"))
ContentView()
.environment(\.locale, Locale(identifier: "ja-JP"))
}
}
}
这招救了我好几次,不用反复打包真机。
审核那些事儿:别被 App Store 耍了
上线前最怕什么?审核被拒。
我们第一次提交,因为没提供隐私政策 URL,直接被拒。第二次,因为测试账号没写清楚,又被打回来。第三次……算了不说了。
血泪建议:
- 在 App Store Connect 的 App 隐私 部分如实填写数据采集类型(哪怕你没采集!)
- 提供一个能登录的测试账号,并在审核备注里写清楚操作路径
- 如果用了第三方 SDK(比如 Firebase),确保它们的隐私协议合规
对了,SwiftUI 有个隐藏福利:默认符合 Apple 的无障碍规范(Accessibility)。比如 Text 自动支持 VoiceOver,Button 有合适的点击区域。但如果你自定义了手势或复杂动画,记得手动加 .accessibilityLabel()。
开发心得:SwiftUI 适合谁?
经过这两周肝到凌晨的折腾,我对 SwiftUI 有了新认识:
✅ 适合快速原型、中小型应用:声明式写法+实时预览,迭代速度飞快
✅ 适合 Apple 生态深度用户:和 WatchOS、macOS 共享代码越来越成熟
❌ 不适合重度定制 UI:比如要做抖音那种全屏手势交互,还是得回 UIKit
❌ 调试工具弱:View 层级没法像 Android Studio 那样 inspect,遇到布局错乱只能靠猜
另外,Swift 的语法糖虽甜,但别滥用。比如 some View 返回类型、ViewBuilder 隐式闭包,新手很容易写出“编译器都看不懂”的代码。
最后:为什么大数据工程师要学 iOS?
有人问我:“你一个搞 Spark 的,折腾 SwiftUI 图啥?”
其实很简单:技术视野不能窄。在深圳这片卷王之地,光会写 ETL 已经不够了。懂点前端、移动端,不仅能和客户端同学高效沟通,关键时刻还能救场。
而且说实话,Swift 的语法比 Scala 清爽多了(别打我)。Rust 我还在啃,但 SwiftUI 至少让我找回了一点“写代码的快乐”——那种所见即所得、改一行立刻看到效果的爽感,是调 Spark 参数永远给不了的。
所以,如果你也像我一样,白天写 DataFrame,晚上想搞点副业,不妨试试 SwiftUI。别怕,Xcode 再烂,也比 YARN 的日志好读。
项目已上线 App Store,名字就不透露了(怕被老板发现我在摸鱼写博客)。代码脱敏后会开源,感兴趣的朋友可以关注我的 GitHub。
搞定收工,回去继续跑我的 Spark job 了。

评论 0