SwiftUI 入门:最全面的介绍 – wiki基地


SwiftUI 入门:最全面的介绍

引言:迎接声明式 UI 的新时代

在 iOS、macOS、watchOS 和 tvOS 开发领域,UIKit 和 AppKit 曾是构建用户界面的主要框架,它们采用的是传统的命令式 (Imperative) 编程范式。开发者需要精确地告诉系统“如何”去做:创建视图对象、设置属性、添加子视图、定义布局约束、处理事件等等。这就像是你在指挥一支军队,需要给每一个士兵下达详细的命令。

然而,随着应用程序界面的日益复杂以及多平台开发的兴起,命令式 UI 开发的维护成本和复杂性也随之增加。苹果在 2019 年的 WWDC 大会上,为开发者社区带来了一股清流——SwiftUI

SwiftUI 是一个全新的、现代化的、跨平台的声明式 (Declarative) UI 框架。它的核心思想是:开发者只需描述界面“应该是什么样子”,而无需关心“如何”去实现它。SwiftUI 会根据应用的状态自动更新 UI。这就像是你向设计师描述你想要的最终效果,而由设计师去操心具体的实现细节。

对于新手而言,直接学习 SwiftUI 是一个非常好的选择,因为它代表了苹果平台 UI 开发的未来方向,并且相比 UIKit/AppKit,在许多方面提供了更简洁、更高效的开发体验。

本文将为你提供一个最全面的 SwiftUI 入门指南,从核心概念到基本组件,带你一步步迈入声明式 UI 的世界。

第一章:SwiftUI 的核心理念与优势

1.1 声明式 UI vs. 命令式 UI

理解 SwiftUI 的关键在于理解声明式编程。

  • 命令式 UI (UIKit/AppKit):

    • 关注“如何”构建 UI。
    • 手动创建和管理视图对象(例如:UILabel(), UIButton())。
    • 手动设置视图属性(例如:label.text = "Hello", button.setTitle("Click", for: .normal))。
    • 手动定义视图之间的布局关系(例如:Auto Layout 约束)。
    • 在状态变化时,需要手动找到对应的视图并更新其属性。
    • 代码通常更冗长,逻辑分散在视图创建、配置、更新等多个地方。
  • 声明式 UI (SwiftUI):

    • 关注“是什么”样子。
    • 你描述视图的结构和属性,SwiftUI 负责根据当前状态渲染和更新界面。
    • 使用轻量级的结构体(Struct)来表示视图。
    • 视图是其状态的函数。当状态改变时,SwiftUI 会自动重新计算受影响的视图的“身体”(body),并更新 UI。
    • 布局是结构化的,通过组合 Stack(栈)、List(列表)等容器来实现。
    • 代码更简洁,逻辑更集中。

举例说明:

想象一下,你要显示一个随着点击次数增加而变化的文本标签和一个按钮。

  • 命令式 (UIKit 思路):

    1. 创建一个 UILabel 实例和一个 UIButton 实例。
    2. 创建一个变量 count = 0
    3. 设置 label.text = "Clicked: 0"
    4. 将标签和按钮添加到父视图。
    5. 为按钮添加一个点击事件处理方法。
    6. 在点击事件处理方法中:count += 1,然后找到之前的那个 label 实例,更新其文本 label.text = "Clicked: \(count)"
  • 声明式 (SwiftUI 思路):

    1. 创建一个变量 count,并用 @State 标记,表示这是视图的状态。
    2. 描述界面:一个 Text 视图,其内容显示 Clicked: \(count);一个 Button 视图,其标签是 “Click Me”。
    3. Button 的 action 闭包中:count += 1
      SwiftUI 会检测到 @State 标记的 count 变量发生了变化,自动重新渲染包含 Text 的视图,更新屏幕上的文本。你无需手动去找到 Text 视图并更新它。

SwiftUI 的核心就在于这种“状态驱动”的理念。你只需要管理好应用的状态,UI 会自然地随之变化。

1.2 SwiftUI 的主要优势

  • 跨平台: SwiftUI 可以轻松地在所有 Apple 平台(iOS, macOS, watchOS, tvOS)上共享大部分代码,大大提高了开发效率。
  • 更少的代码: 相比 UIKit/AppKit,SwiftUI 通常需要编写更少的代码来实现相同的功能。
  • 强大的预览功能: Xcode 的 Live Previews 允许你在代码编写的同时实时查看 UI 效果,甚至进行交互,极大地加快了迭代速度。
  • 更快的性能: SwiftUI 利用 Swift 的强大能力,通过优化布局和渲染过程来提高性能。
  • 现代化特性: 与 Swift 语言深度集成,充分利用了 Swift 的新特性,如 Result Builders。
  • 易于组合: SwiftUI 的视图是小型、可重用的构建块,易于组合和嵌套,构建复杂界面如同搭积木。

尽管 SwiftUI 相对年轻,生态系统仍在发展中,但在新项目或现有项目的局部采用 SwiftUI 已经成为主流趋势。

第二章:迈出第一步:创建你的第一个 SwiftUI 项目

要开始使用 SwiftUI,你需要一台安装了最新版本 Xcode 的 Mac 电脑。

  1. 打开 Xcode: 启动 Xcode 应用程序。
  2. 创建新项目: 在 Xcode 欢迎界面选择 “Create a new Xcode project”,或者通过菜单栏选择 “File” -> “New” -> “Project…”。
  3. 选择模板: 在项目模板选择界面,选择 “Multiplatform” -> “App”。点击 “Next”。
  4. 配置项目:
    • Product Name: 输入你的项目名称(例如:MyFirstSwiftUIApp)。
    • Team: 选择你的开发者团队(如果没有,可以选择 Personal Team)。
    • Organization Identifier: 输入一个反向域名风格的组织标识符(例如:com.yourcompany)。
    • Interface: 务必选择 “SwiftUI”
    • Life Cycle: 务必选择 “SwiftUI App”
    • Language: 务必选择 “Swift”。
    • Use Core Data: (可选)如果你需要使用 Core Data 持久化数据,可以勾选。
    • Include Tests: (可选)如果你想包含单元测试和 UI 测试,可以勾选。
  5. 保存项目: 点击 “Next”,选择一个目录来保存你的项目,然后点击 “Create”。

Xcode 会为你生成一个基本的 SwiftUI 项目结构。打开项目导航器(Command + 1),你会看到类似这样的文件结构:

  • MyFirstSwiftUIAppApp.swift: 这是你应用的入口点,遵循 App 协议。
  • ContentView.swift: 这是你应用的第一个视图,遵循 View 协议。

让我们看看 ContentView.swift 的内容:

“`swift
//
// ContentView.swift
// MyFirstSwiftUIApp
//
// Created by Your Name on Date.
//

import SwiftUI

struct ContentView: View {
var body: some View {
VStack {
Image(systemName: “globe”)
.imageScale(.large)
.foregroundColor(.accentColor)
Text(“Hello, world!”)
}
.padding()
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
“`

这个文件包含了两个主要部分:

  1. ContentView 结构体:它遵循 View 协议,是我们的主要视图。每个遵循 View 协议的类型都必须实现一个计算属性 body,它返回某种遵循 View 协议的类型(通常是一个视图容器或其他视图)。some View 是一个不透明返回类型,表示 body 会返回一个遵循 View 协议的视图,但具体类型由 SwiftUI 编译器决定。
  2. ContentView_Previews 结构体:它遵循 PreviewProvider 协议,用于在 Xcode 的 Canvas 区域实时预览 ContentView 的效果。previews 静态计算属性返回你想要预览的视图实例。

实时预览 (Canvas)

当你打开 ContentView.swift 文件时,Xcode 的中央区域会显示代码编辑器,而右侧(如果隐藏了,可以点击右上角的调整器按钮显示或通过菜单 Editor -> Canvas)会显示 Canvas 区域,显示你的 UI 预览。

如果 Canvas 显示 “Automatic preview updating paused” 或类似信息,点击 “Resume” 按钮(通常是一个播放图标),或者使用快捷键 Option + Command + P 来开始实时预览。

你可以在 Canvas 中看到一个包含地球图标和 “Hello, world!” 文本的界面。这就是 ContentViewbody 中描述的 UI 结构。

你甚至可以点击 Canvas 底部的 “Live Preview” 按钮(一个播放图标),让预览变得可交互,就像在真实设备上运行一样。

第三章:构建 UI 的基石:Views 和 Modifiers

在 SwiftUI 中,用户界面是由 View(视图)构建而成的。视图是构成 UI 的基本单元,它们可以是文本、图像、按钮、列表等。

3.1 Views (视图)

SwiftUI 中的 View 是轻量级的,通常是结构体(Struct)。它们遵循 View 协议,并且必须有一个 body 属性来描述其内容或布局。

一些基础的内置 Views:

  • Text("..."): 显示文本。
  • Image("...")Image(systemName: "..."): 显示图片,可以是 Asset Catalog 中的图片或 SF Symbols 图标。
  • Button(...) { ... }: 显示一个按钮,包含一个标签视图和一个点击时执行的 action 闭包。
  • TextField("Placeholder", text: $variable): 单行文本输入框。
  • SecureField("Placeholder", text: $variable): 安全文本输入框(用于密码)。
  • Toggle("Label", isOn: $variable): 开关控件。
  • Slider(value: $variable, in: range): 滑块控件。
  • Stepper("Label", value: $variable, in: range): 步进器控件。
  • DatePicker(...): 日期选择器。

还有一些重要的容器 Views,用于组织和布局其他视图:

  • VStack { ... }: 垂直堆叠子视图。
  • HStack { ... }: 水平堆叠子视图。
  • ZStack { ... }: Z轴(前后)堆叠子视图,后添加的视图显示在前面。
  • List { ... }: 显示可滚动的数据列表。
  • ScrollView { ... }: 显示可滚动的内容区域。

示例:

“`swift
struct BasicViewsExample: View {
@State private var buttonTappedCount = 0

var body: some View {
    VStack(spacing: 20) { // 垂直堆叠,间距为 20
        Text("欢迎来到 SwiftUI!") // 文本视图
            .font(.largeTitle) // 应用大标题字体

        Image(systemName: "star.fill") // SF Symbols 星星图标
            .resizable() // 使图片可调整大小
            .scaledToFit() // 按比例缩放以适应可用空间
            .frame(width: 50, height: 50) // 设置固定大小
            .foregroundColor(.yellow) // 设置前景色

        Button { // 按钮的 action
            buttonTappedCount += 1
        } label: { // 按钮的标签(外观)
            HStack { // 使用 HStack 组合图标和文本作为按钮标签
                Image(systemName: "hand.tap.fill")
                Text("点我")
            }
        }
        .buttonStyle(.borderedProminent) // 应用按钮样式

        Text("按钮已点击 \(buttonTappedCount) 次") // 文本显示状态变化
    }
    .padding() // 在 VStack 周围添加内边距
}

}

// 在 PreviewProvider 中添加 BasicViewsExample() 进行预览
struct BasicViewsExample_Previews: PreviewProvider {
static var previews: some View {
BasicViewsExample()
}
}
“`

在这个例子中,我们组合了 VStackTextImageButton 来构建一个简单的界面。注意 VStack 接受一个 spacing 参数来控制子视图之间的间距。

3.2 Modifiers (修饰符)

视图本身通常只负责其核心功能(例如,Text 就是显示文本)。要改变视图的外观、行为或布局,你需要使用 Modifiers

Modifier 是 View 协议上的方法,它们接收一个视图,并返回一个新的、经过修改的视图。因为 Modifier 返回的是新的视图,所以可以链式调用多个 Modifier。

语法:

OriginalView().modifier1().modifier2().modifier3(...)

示例:

swift
Text("修饰符的力量")
.font(.title) // 设置字体为标题字体
.foregroundColor(.blue) // 设置文本颜色为蓝色
.padding() // 在文本周围添加默认内边距
.background(.gray) // 设置背景颜色为灰色
.cornerRadius(10) // 设置圆角半径为 10
.shadow(radius: 5) // 添加阴影效果

常用的 Modifiers:

  • font(...): 设置字体样式。
  • foregroundColor(...): 设置文本或图标颜色。
  • padding(...): 添加内边距。
  • background(...): 设置背景。
  • border(...): 添加边框。
  • cornerRadius(...): 设置圆角。
  • shadow(...): 添加阴影。
  • opacity(...): 设置透明度。
  • rotationEffect(...), scaleEffect(...): 应用旋转或缩放变换。
  • frame(width:height:alignment:): 设置视图的尺寸和对齐方式。
  • offset(x:y:): 偏移视图位置。
  • onTapGesture { ... }: 添加点击手势识别。

理解 View 和 Modifier 是 SwiftUI 的基础。View 定义了元素的类型,Modifier 定义了元素如何呈现。

第四章:布局之道:Stacks, Spacers, 和 Alignment

SwiftUI 提供了强大的、基于 Stack 的自动布局系统。你无需像 Auto Layout 那样手动设置约束(尽管在某些复杂情况下可以桥接)。

4.1 Stacks (堆叠)

VStack, HStack, ZStack 是最基本的布局容器。

  • VStack (Vertical Stack): 将子视图垂直排列。
    swift
    VStack(alignment: .leading, spacing: 10) { // 垂直排列,左对齐,间距 10
    Text("第一行")
    Text("第二行")
    Text("第三行,可能比较长")
    }

    可以指定 alignment(如 .leading, .center, .trailing)和 spacing

  • HStack (Horizontal Stack): 将子视图水平排列。
    swift
    HStack(alignment: .center, spacing: 20) { // 水平排列,垂直居中对齐,间距 20
    Image(systemName: "star")
    Text("收藏")
    Image(systemName: "heart")
    Text("喜欢")
    }

    可以指定 alignment(如 .top, .center, .bottom, .firstTextBaseline, .lastTextBaseline)和 spacing

  • ZStack (Z Stack): 将子视图沿 Z 轴(前后)排列,后添加的视图显示在前面。常用于创建叠加效果,如在背景图片上叠加文本。
    swift
    ZStack(alignment: .bottomTrailing) { // Z 轴排列,底部右侧对齐
    Image("background") // 背景图片
    .resizable()
    .scaledToFill()
    Text("水印文本") // 水印文本
    .padding(5)
    .foregroundColor(.white)
    .background(.black.opacity(0.5))
    .cornerRadius(5)
    }

    可以指定 alignment 来控制子视图在 Z 轴上的对齐方式。

4.2 Spacer (间隔器)

Spacer() 是一个灵活的空间视图,它会尽可能地扩张,将其他视图推开。常用于在 Stack 中创建可变空间。

示例:

“`swift
HStack {
Text(“左边”)
Spacer() // 间隔器会推开左右两边的视图
Text(“右边”)
}

VStack {
Text(“顶部”)
Spacer() // 间隔器会推开上下两边的视图
Text(“底部”)
}
“`

在 Stack 中,多个 Spacer 会平均分配可用空间。

4.3 Padding (内边距)

padding() Modifiers 用于在视图的四周添加内边距。

  • .padding(): 添加默认大小的内边距到所有边缘。
  • .padding(.leading): 只在左侧添加内边距。
  • .padding(.horizontal): 在水平方向(左、右)添加内边距。
  • .padding([.top, .bottom], 10): 在顶部和底部添加 10 点的内边距。
  • .padding(20): 在所有边缘添加 20 点的内边距。

4.4 布局原则

  • 由内而外: SwiftUI 的布局通常是从内层视图向外层容器传递尺寸信息。子视图会建议它们的大小,父视图会根据这些建议和自己的可用空间来决定子视图的最终位置和大小。
  • 适应内容: 视图默认会尝试适应其内容的大小。例如,Text 视图的大小会根据其文本内容自动调整。
  • 使用 frame() 控制尺寸: 如果需要精确控制视图的最小、最大或固定尺寸,可以使用 frame() Modifier。
  • 使用 alignment 控制对齐: 在 Stack 中使用 alignment 参数来控制子视图的对齐方式。

掌握 Stack、Spacer 和 Padding 是构建复杂 SwiftUI 界面的基础。

第五章:响应用户交互:State 和 Binding

SwiftUI 的核心是“状态驱动”。当应用的状态改变时,UI 会自动更新。管理和响应状态变化是 SwiftUI 开发中最重要的概念之一。

5.1 @State (本地状态)

@State 是一个属性包装器 (Property Wrapper),用于管理视图内部的、私有的、简单的值类型状态(如 Int, Bool, String 等)。

@State 标记的变量发生变化时,SwiftUI 会知道包含该 @State 变量的视图及其子视图需要重新计算 body,从而更新 UI。

@State 变量应该声明为 private,因为它们是视图的本地状态,不应该从外部直接访问或修改。

示例:计数器

“`swift
import SwiftUI

struct CounterView: View {
@State private var count = 0 // 使用 @State 声明一个私有状态变量

var body: some View {
    VStack {
        Text("当前计数: \(count)") // 文本显示 count 的值
            .font(.title)

        Button("增加计数") { // 按钮 action 改变 count
            count += 1
        }
    }
    .padding()
}

}

struct CounterView_Previews: PreviewProvider {
static var previews: some View {
CounterView()
}
}
“`

在这个例子中,每当点击按钮,count 变量的值就会增加。由于 count@State 标记,SwiftUI 会自动检测到这个变化,并重新渲染 CounterViewbody。结果就是屏幕上的文本会立即更新,显示新的计数。

5.2 @Binding (状态绑定)

@Binding 也是一个属性包装器,它创建了一个对由其他地方(通常是父视图或数据源)管理的 @State 或其他数据源的引用。它提供了一种在两个视图之间建立双向连接的方式。

使用 @Binding 的视图并不拥有状态数据,它只是持有一个引用。当 @Binding 变量的值改变时,它会修改原始数据源,从而触发原始数据源拥有的视图更新。

这在构建可重用子视图时非常有用。子视图不需要知道状态数据是如何管理的,只需要通过 Binding 来读取和修改它。

@Binding 变量在声明时不需要赋初值,但在使用时,需要在父视图创建子视图时,将父视图的 @State 变量前面加上 $ 符号传递进去。$ 符号会创建一个 Binding

示例:带开关的自定义视图

假设我们想创建一个可重用的视图 ToggleSettingView,它包含一个标签和一个开关,开关的状态由外部控制。

“`swift
import SwiftUI

// 父视图,拥有状态
struct SettingsView: View {
@State private var isFeatureEnabled = true // 父视图的状态

var body: some View {
    VStack {
        Text("主设置面板")
            .font(.headline)

        // 使用子视图,通过 $isFeatureEnabled 传递状态绑定
        ToggleSettingView(labelText: "启用某功能", isOn: $isFeatureEnabled)

        Text("功能状态: \(isFeatureEnabled ? "已启用" : "已禁用")")
            .padding(.top)
    }
    .padding()
}

}

// 子视图,接收一个 Binding
struct ToggleSettingView: View {
let labelText: String // 标签文本
@Binding var isOn: Bool // 接收一个 Bool 类型的绑定

var body: some View {
    HStack {
        Text(labelText)
        Spacer()
        Toggle("", isOn: $isOn) // Toggle 内部也使用 Binding 来管理自身状态,这里我们将接收到的 Binding 传递给它
            .labelsHidden() // 隐藏 Toggle 自身的标签,因为它已经在 HStack 中显示了
    }
    .padding(.horizontal)
}

}

struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}
“`

在这个例子中:

  1. SettingsView 拥有 @State private var isFeatureEnabled = true
  2. ToggleSettingView 声明了 @Binding var isOn: Bool
  3. SettingsViewbody 中创建 ToggleSettingView 时,我们将 $isFeatureEnabled 传递给了 isOn 参数。这个 $ 符号创建了一个 BindingisFeatureEnabled
  4. ToggleSettingView 内部的 Toggle 使用它接收到的 $isOn Binding。当用户切换 Toggle 时,它会通过 Binding 修改父视图的 isFeatureEnabled 变量。
  5. 由于 isFeatureEnabled@State 变量,其变化会触发 SettingsView 重新渲染,从而更新下方的状态文本。

@State@Binding 是 SwiftUI 数据流管理中最基础也是最重要的两个概念。

第六章:数据流管理:跨视图共享数据

除了 @State@Binding 用于简单或本地状态,SwiftUI 还提供了其他方式来管理更复杂、更全局或需要在多个不直接相连的视图之间共享的数据。

6.1 @ObservableObject 和 @StateObject

当你的数据模型是一个类(Class)时,你需要使用不同的属性包装器。遵从 ObservableObject 协议的类可以在其属性变化时发布通知。SwiftUI 提供了 @Published 属性包装器来简化这个过程。

  • @ObservedObject: 用于引用一个已存在ObservableObject 实例。当 ObservableObject 中的 @Published 属性变化时,使用 @ObservedObject 的视图会更新。但 @ObservedObject 不拥有这个对象的生命周期,如果拥有者(如父视图)被销毁,它引用的对象也可能被销毁。
  • @StateObject: (iOS 14+, macOS 11+) 用于在视图内部创建和拥有一个 ObservableObject 实例。SwiftUI 会确保在视图的整个生命周期内(即使视图的 body 被多次重新计算),@StateObject 引用的是同一个对象实例。这是管理视图局部复杂状态的首选方式。

示例:可观察的对象模型

“`swift
import SwiftUI

// 1. 定义一个 ObservableObject 类
// 通常这是一个 Class
class UserSettings: ObservableObject {
// 使用 @Published 标记属性,使其变化时发布通知
@Published var username = “Guest”
@Published var isLoggedIn = false
}

// 2. 在视图中使用 @StateObject 拥有并管理这个对象
struct UserProfileView: View {
// 使用 @StateObject 拥有并初始化 UserSettings 实例
@StateObject var settings = UserSettings()

var body: some View {
    VStack(spacing: 20) {
        Text("用户设置")
            .font(.largeTitle)

        TextField("用户名", text: $settings.username) // TextField 的 text 参数需要 Binding
            .textFieldStyle(.roundedBorder)
            .padding(.horizontal)

        Toggle("登录状态", isOn: $settings.isLoggedIn) // Toggle 的 isOn 参数需要 Binding
            .padding(.horizontal)

        Text("欢迎,\(settings.username)!")
            .foregroundColor(settings.isLoggedIn ? .green : .red)

        Button("重置用户名") {
            settings.username = "Guest"
        }
    }
    .padding()
}

}

struct UserProfileView_Previews: PreviewProvider {
static var previews: some View {
UserProfileView()
}
}
“`

在这个例子中:

  1. UserSettings 类遵循 ObservableObject,其属性 usernameisLoggedIn@Published 标记。
  2. UserProfileView 使用 @StateObject var settings = UserSettings() 来创建并持有 UserSettings 的一个实例。
  3. 视图中的 TextFieldToggle 通过 $settings.username$settings.isLoggedIn 分别创建了对 settings 对象属性的 Binding
  4. 当用户在 TextField 中输入文本或切换 Toggle 时,这些 Binding 会修改 settings 对象的属性。
  5. 由于 settingsObservableObject,并且其 @Published 属性发生了变化,UserProfileView 会收到通知并自动更新 UI。

如果你只是需要在某个子视图中引用一个由父视图或其他地方已经创建好的 ObservableObject 实例(不拥有它),则应该使用 @ObservedObject var settings: UserSettings

6.3 @EnvironmentObject

@EnvironmentObject 提供了一种在视图层级中共享 ObservableObject 实例的方式,避免了通过层层初始化器手动传递对象。

你可以在某个祖先视图中使用 .environmentObject(yourObject) Modifier 将一个 ObservableObject 实例注入到环境中。然后,任何后代视图都可以使用 @EnvironmentObject var object: YourObjectType 来访问这个对象,而无需手动传递。

这非常适合在整个应用或应用的某个大部分区域共享数据,例如用户会话、设置、核心数据上下文等。

示例:跨层级共享设置

“`swift
import SwiftUI

// 与上面相同的 UserSettings 类

// 父视图,注入 EnvironmentObject
struct AppRootView: View {
// 创建并拥有 UserSettings 实例
@StateObject private var settings = UserSettings()

var body: some View {
    NavigationView { // 或 NavigationStack
        VStack {
            Text("应用根视图")
            // ... 其他视图 ...
            NavigationLink("前往设置", destination: SettingsDetailView()) // 导航到子视图
        }
        .navigationTitle("主页")
    }
    .environmentObject(settings) // 将 settings 对象注入到环境中
}

}

// 子视图,从环境中获取 EnvironmentObject
struct SettingsDetailView: View {
// 从环境中获取 UserSettings 对象,无需手动传递
@EnvironmentObject var settings: UserSettings

var body: some View {
    VStack(spacing: 20) {
        Text("设置详情")
            .font(.title)

        TextField("用户名", text: $settings.username)
            .textFieldStyle(.roundedBorder)
            .padding(.horizontal)

        Toggle("登录状态", isOn: $settings.isLoggedIn)
            .padding(.horizontal)
    }
    .navigationTitle("设置")
    .padding()
}

}

struct AppRootView_Previews: PreviewProvider {
static var previews: some View {
AppRootView().environmentObject(UserSettings()) // 预览时也需要注入 EnvironmentObject
}
}
“`

注意:使用 @EnvironmentObject 的视图在预览时也需要通过 .environmentObject() Modifier 注入相应的对象,否则预览会崩溃。

@EnvironmentObject 让跨多个视图层级的数据共享变得非常方便,但滥用可能导致视图之间的依赖关系不清晰,因此应谨慎使用,主要用于全局或半全局的数据。

第七章:列表和导航:List 和 NavigationView (或 NavigationStack)

7.1 List (列表)

List 是 SwiftUI 中显示数据集合的主要方式,类似于 UIKit 的 UITableView 或 AppKit 的 NSTableView。它可以显示静态内容,也可以基于数据集合动态生成行。

静态列表:

swift
List {
Text("第一项")
Text("第二项")
HStack {
Image(systemName: "star")
Text("带图标的项")
}
}

动态列表:

动态列表通常使用一个遵循 Identifiable 协议的数据集合。如果你的数据模型本身没有 ID,可以使用 id: \.selfid: \.uuidString (如果数据是 String 等具有唯一属性的类型) 来指定唯一的标识键。

“`swift
struct TodoItem: Identifiable {
let id = UUID() // 遵循 Identifiable
var task: String
var isCompleted = false
}

struct TodoListView: View {
@State private var todos = [
TodoItem(task: “学习 SwiftUI”),
TodoItem(task: “构建第一个应用”, isCompleted: true),
TodoItem(task: “探索更多功能”)
]

var body: some View {
    List {
        ForEach(todos) { todo in // 使用 ForEach 迭代数据集合
            HStack {
                Text(todo.task)
                Spacer()
                if todo.isCompleted {
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundColor(.green)
                }
            }
        }
        // 列表还支持 Section、editActions 等
    }
    .navigationTitle("待办事项") // 通常在与 NavigationView 结合时设置标题
}

}
“`

ForEach 是 SwiftUI 中用于基于集合生成视图的重要结构。

7.2 NavigationView (或 NavigationStack)

NavigationView (在 iOS 16+, macOS 13+ 中推荐使用 NavigationStack) 用于管理应用中的导航流程,例如在屏幕之间切换(push 和 pop)。它在顶部显示一个导航栏,可以在其中放置标题和按钮。

使用 NavigationView

“`swift
struct AppNavigationView: View {
var body: some View {
NavigationView { // 将需要导航的视图包裹在 NavigationView 中
VStack {
Text(“这是主页”)
.navigationTitle(“主页”) // 设置当前视图的导航栏标题

            // NavigationLink 用于导航到另一个视图
            NavigationLink("前往详情页") { // label 是链接的外观,destination 是目标视图
                DetailView()
            }
        }
    }
}

}

struct DetailView: View {
var body: some View {
VStack {
Text(“这是详情页”)
}
.navigationTitle(“详情页”) // 设置详情页的导航栏标题
// NavigationLink(“返回主页”) { … } // 返回通常使用 navigationBarBackButtonHidden()
}
}
“`

使用 NavigationStack (iOS 16+, macOS 13+ 推荐):

NavigationStack 提供了更灵活的导航方式,尤其是对于程序化导航。

“`swift
struct AppNavigationStackView: View {
// NavigationStack 通过路径(path)来管理导航状态
@State private var path: [String] = [] // 示例:使用 String 数组作为路径

var body: some View {
    NavigationStack(path: $path) { // 绑定路径变量
        VStack {
            Text("这是主页 (NavigationStack)")
                .navigationTitle("主页")

            // NavigationLink 的 value 参数决定了导航时添加到路径中的值
            NavigationLink("前往详情页 A", value: "DetailA")
            NavigationLink("前往详情页 B", value: "DetailB")

            Button("程序化导航到详情页 A") {
                path.append("DetailA") // 通过修改 path 数组进行导航
            }
        }
        // 接收 NavigationLink 的 value,并根据 value 导航到不同的 destination
        .navigationDestination(for: String.self) { value in
            if value == "DetailA" {
                DetailViewA()
            } else if value == "DetailB" {
                DetailViewB()
            } else {
                Text("未知详情页")
            }
        }
    }
}

}

struct DetailViewA: View {
var body: some View {
Text(“这是详情页 A”)
.navigationTitle(“详情 A”)
}
}

struct DetailViewB: View {
var body: some View {
Text(“这是详情页 B”)
.navigationTitle(“详情 B”)
}
}
“`

对于新项目,推荐优先使用 NavigationStack,但如果你需要兼容旧系统版本(iOS 15 及以下),可能需要使用 NavigationView 或考虑条件编译。

第八章:预览的力量:实时迭代开发

Xcode 的 Canvas 预览是 SwiftUI 开发中最具生产力的特性之一。它允许你几乎实时地看到代码改动带来的 UI 变化,甚至可以进行交互。

8.1 PreviewProvider (旧版) / #Preview (新版)

在旧版本的 SwiftUI 项目中,每个 View 文件底部都有一个遵循 PreviewProvider 的结构体,用于提供预览。

swift
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView() // 提供 MyView 的一个实例进行预览
.previewDevice("iPhone 14") // 可以指定预览的设备
.preferredColorScheme(.dark) // 可以指定颜色模式
}
}

在 Xcode 15+ 和 iOS 17+ 项目模板中,预览使用 #Preview 宏,更加简洁:

“`swift

Preview {

ContentView()
    .previewDisplayName("默认预览") // 设置预览名称
    .previewLayout(.sizeThatFits) // 调整预览大小模式

}

Preview(“黑暗模式”) { // 可以创建多个预览

ContentView()
    .preferredColorScheme(.dark)

}
“`

8.2 预览功能

  • 实时更新: 当你修改代码时,Canvas 会自动更新以反映变化。
  • 交互式预览 (Live Preview): 点击 Canvas 底部的播放按钮,预览会变成可交互模式,你可以像在模拟器或设备上一样操作 UI。这对于测试按钮、手势、动画等非常有用。
  • 多设备/状态预览: 你可以在 PreviewProvider#Preview 中返回多个视图实例,设置不同的 Modifier (如设备、方向、动态字体大小、本地化语言等),同时预览在不同配置下的效果。
  • 检查器集成: 在 Canvas 中选择一个视图,可以使用右侧的属性检查器来查看和修改其属性。许多修改会直接反映在代码中。

充分利用预览功能,可以极大地提高你的开发效率和调试速度。

第九章:SwiftUI 的优势与考量

优势回顾:

  • 跨平台: 代码复用性高,降低多平台开发成本。
  • 开发效率高: 声明式语法简洁,配合强大预览功能,迭代快。
  • 现代化: 与 Swift 语言紧密集成,利用新特性。
  • 自动布局和响应式设计: 易于构建适应不同屏幕尺寸和方向的界面。
  • 性能提升: 框架层面的优化。

需要考量的地方:

  • 生态系统相对年轻: 相比 UIKit/AppKit,SwiftUI 的发布时间较短,一些复杂或特定的功能(如某些自定义手势、特定控制器集成等)可能需要桥接 UIKit/AppKit 或尚不成熟。
  • 学习曲线: 对于习惯了命令式编程的开发者,理解声明式思维、数据流管理、View 的值类型特性等需要时间适应。
  • 版本兼容性: 某些新功能或改进可能只在最新的操作系统版本上可用,需要根据项目需求考虑兼容性。
  • 文档和社区资源: 随着时间推移正在快速增长,但早期可能不如 UIKit/AppKit 那样丰富。

第十章:下一步学习资源

恭喜你!通过阅读本文,你已经对 SwiftUI 的核心概念有了全面的了解,并掌握了入门所需的基础知识。但这仅仅是一个开始,SwiftUI 的世界还有很多值得探索。

  • Apple 官方文档: 这是最权威的学习资源。查阅 Views、Modifiers、Property Wrappers 等的详细文档。
  • WWDC 视频: Apple 每年都会发布大量关于 SwiftUI 的精彩视频,涵盖了从基础到高级的各种主题。
  • Apple 官方教程: Apple 提供了一些非常棒的入门教程,手把手教你构建一个 SwiftUI 应用。
  • 社区资源: 大量优秀的博客、教程网站、书籍和开源项目。搜索你感兴趣的主题,多看、多练、多思考。
  • 实践: 最重要的是动手实践!从简单的界面开始,逐步尝试构建更复杂的应用。

结论

SwiftUI 是 Apple 平台 UI 开发的未来。它通过声明式编程范式、跨平台能力和强大的工具支持,显著提高了开发效率和代码质量。虽然入门需要理解一些新的概念,但一旦掌握了状态管理和视图组合的理念,你会发现 SwiftUI 能够让你以更直观、更愉快的方式构建精美的用户界面。

希望这篇全面的介绍能够帮助你顺利迈出 SwiftUI 学习的第一步。祝你在探索 SwiftUI 的旅程中一切顺利!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部