Skip to content
  • 命令式 UI(UIKit)vs 声明式 UI(SwiftUI)
  • 👍 SwiftUI 的 Stacks: HStack, VStack, ZStack
  • SF Symbols
    • renderingMode/.foregroundColor()
    • symbolRenderingMode/.foregroundStyle() 多色调
  • #Preview 宏
  • 👍 SwiftUI 的状态管理
SwiftUI 设计哲学

为什么 SwiftUI 的 View 是 struct?View 是一次性快照,频繁创建和销毁代价小

SwiftUI 的 View 协议定义里是这样的:

swift
protocol View {
    associatedtype Body : View
    @ViewBuilder var body: Self.Body { get }
}

SwiftUI 利用这个特性做声明式 UI:每次 state 变化,SwiftUI 会重新计算 View 的 body,看似频繁“重建”,但只是生成一棵新的描述树(diff 树),最后 SwiftUI 框架自己去比对差异(reconciliation),只更新真正需要变的地方

那数据怎么保持?状态是持久的,SwiftUI 框架帮你存

SwiftUI 的状态管理

漏掉了一个 @Observable

属性包装器数据存放在哪生命周期管理常见用途举例场景
@StateSwiftUI 内部(轻量级存储)当前 View 内部保存简单值按钮计数、开关状态
@Binding父 View 的状态跟随父 View子 View 修改父 View 的数据开关组件绑定布尔值
@StateObjectView 内部(强持有类对象)当前 View 内,只创建一次管理复杂对象网络请求器、播放器管理器
@ObservedObject外部传入不负责持有子 View 观察父传进来的对象子页面显示数据列表
@EnvironmentObjectEnvironment(父层注入)父层管理,全局共享全局状态用户登录状态、主题、购物车
@AppStorageUserDefaults持久化存储跨启动保存简单数据用户偏好设置(是否开启夜间模式)
@SceneStorage系统 Scene 存储当前 Scene 生命周期内界面状态暂存输入框内容、当前选中的 Tab
项目作用生命周期适用场景
ObservableObject(协议)任何想被 SwiftUI 观察的类需遵循的协议,暴露 objectWillChange 发布器类本身实现让 SwiftUI 或 View 订阅对象变化;必须是 class
@Published标记类里的可变属性,会产生 $prop 发布者并触发变化通知所在类内部属性变化时自动通知订阅者;对复杂引用类型内部变化可能需要手动调用 objectWillChange.send()
  • some 是 Swift 的 opaque type(不透明类型)
例如这个案例中的 some View
swift
// 具体类型
var body: VStack<Text, Button> { ... } // 太长,不灵活

// 使用 some
var body: some View { ... } // 简洁,返回任意遵守 View 的类型

它告诉编译器,我返回某种具体的 View 类型,但调用者不用关心具体类型是什么,只保证它符合 View 协议,从而隐藏内部复杂类型,实现类型安全又简洁

  • Button 的用法好奇怪
SwiftUI 里 Button 的两种常见写法
swift
Button("点我") {
    print("按钮被点了")
}

等价于

swift

Button(action: {
    print("按钮被点了")
}) {
    Text("点我")
}

复杂的视图:使用 label 的方式,你可以塞任何视图

swift
Button {
    print("收藏了")
} label: {
    HStack {
        Image(systemName: "star")
        Text("收藏")
    }
}

这是 Swift 5.3 引入的 trailing closure 优化写法,即支持多个尾随闭包。它把 action 和 label 都改成了清爽的闭包形式

trailing closure

例子 1:单尾随闭包

swift
doSomething(action: { print("Hi") })

// 函数最后一个参数是闭包,可以写在括号外
doSomething {
    print("Hi")
}

例子 2:多个尾随闭包(Swift 5.3 之后)

swift
Button(action: {
    print("点了")
}, label: {
    Text("按钮")
})

Button {
    print("点了")
} label: {
    Text("按钮")
}

例子 3:动画

swift
withAnimation(animation: .easeInOut, {
    self.show.toggle()
})

withAnimation(.easeInOut) {
    self.show.toggle()
}
  • SwiftUI 项目如何使用三方库
  • NavigationStack 和 NavigationView 的区别 新/旧
  • Info.plist 是 iOS/macOS 系统了解你的应用的重要配置文件,申请敏感权限时必须在里面声明用途,否则 app 会被系统拦截
  • App Lifecycle
    • @mainApp 协议
  • Codable 和 Decodable -> JSON 处理协议
  • DispatchQueue.main.async { ... } UI 改动必须在主,耗时操作丢后台,拿结果回来再上主。说明:网络请求不会在主线程执行,否则 UI 会卡死,所以网络请求在后台执行完成之后,靠 DispatchQueue.main.async 把后台获取到的数据更新回回主线程
  • competed: @escaping (Result<[Appetizer], APError>
  • DispatchQueue.main.async { ... }@MainActor
  • .task {} 修饰器 和 Task {}
  • SwiftUI 典型的 ObservableObject 模式
swift
@MainActor
class NoteStore: ObservableObject {
    @Published var notes: [Note] = []
    // ...其他代码...
}
  • 使用 UserDefaults 作为持久化存储方案 @AppStorage
  • 其它持久化存储方案:Core Data(ORM、SQLite)、SwiftData
  • Swift 里 可选绑定(Optional Binding) 的用法
Details
swift
if let 常量名 = 可选值 {
    // 可选值不是 nil,常量名里有真实值
} else {
    // 可选值是 nil
}

举例

swift
if let imageURL = landmark.imageURL {
    LandmarkImage(imageURL: imageURL)
} else {
    Image(systemName: "photo")
        .resizable()
        .frame(width: 50, height: 50)
        .foregroundColor(.gray)
}

struct Landmark {
    var imageName: String

    var imageURL: URL? {
        URL(string: "https://ehfowdktetfzytsfopbi.supabase.co/storage/v1/object/public/landmark-pic/\(imageName).jpg")
    }
}
  • 为啥 .navigationTitle() 不能直接贴在 NavigationView 上?
    • iOS 16+ 的变化:Apple 把 NavigationView 慢慢替换成 NavigationStack / NavigationSplitView