Swift 从入门到精通-第四篇

第13章:SwiftUI 基础 - 声明式 UI

13.1 SwiftUI 是什么?

SwiftUI 是 Apple 在 2019 年推出的声明式 UI 框架,用来替代 UIKit。它的核心理念是:

**告诉 SwiftUI "你想要什么界面",而不是 "怎么创建界面"。 **

对比:

  • **UIKit(命令式) **:创建视图 → 添加到父视图 → 设置约束 → 手动更新

  • **SwiftUI(声明式) **:描述界面状态,SwiftUI 自动处理更新

13.2 你的第一个 SwiftUI 视图

在 Xcode 中创建 SwiftUI 项目,替换 ContentView.swift:

swift 复制代码
import SwiftUI

  


struct ContentView: View {

    var body: some View {

        Text("Hello, World!")

            .font(.largeTitle)

            .foregroundColor(.blue)

            .padding()

    }

}

  


#Preview {

    ContentView()

}

**关键概念: **

  • View 协议:所有 UI 元素都遵循这个协议

  • body:计算属性,返回视图层级

  • 修饰符(modifier):用 . 链式调用,修改视图样式

13.3 状态管理 - @State

SwiftUI 中,视图是函数:输入相同的状态,输出相同的界面。

swift 复制代码
struct CounterView: View {

    // @State 标记可变状态

    @State private var count = 0

    

    var body: some View {

        VStack(spacing: 20) {

            Text("Count: \(count)")

                .font(.largeTitle)

            

            Button("Increment") {

                count += 1  // 修改状态,自动刷新界面

            }

            .buttonStyle(.borderedProminent)

        }

    }

}

** @State 的特点: **

  • 用于视图内部状态

  • SwiftUI 自动管理存储

  • 值改变时自动重绘视图

13.4 布局系统 - Stacks

swift 复制代码
struct LayoutDemoView: View {

    var body: some View {

        VStack(spacing: 20) {      // 垂直堆栈

            Text("上方")

            

            HStack(spacing: 20) {  // 水平堆栈

                Text("左")

                    .frame(maxWidth: .infinity)

                    .background(Color.red.opacity(0.3))

                Text("中")

                    .frame(maxWidth: .infinity)

                    .background(Color.green.opacity(0.3))

                Text("右")

                    .frame(maxWidth: .infinity)

                    .background(Color.blue.opacity(0.3))

            }

            

            ZStack {                // 重叠堆栈

                Circle()

                    .fill(Color.yellow)

                    .frame(width: 100, height: 100)

                Text("Z")

                    .font(.largeTitle)

            }

            

            Text("下方")

        }

        .padding()

    }

}

**三种 Stack: **

  • VStack - 垂直排列

  • HStack - 水平排列

  • ZStack - 前后叠加

13.5 常用控件

swift 复制代码
struct ControlsView: View {

    @State private var text = ""

    @State private var isOn = false

    @State private var sliderValue = 50.0

    @State private var selectedDate = Date()

    @State private var selectedColor = Color.red

    

    var body: some View {

        Form {  // 表单,自动处理滚动和布局

            Section("输入") {

                TextField("请输入", text: $text)

                SecureField("密码", text: $text)  // 密码输入

                TextEditor(text: $text)  // 多行文本

            }

            

            Section("选择") {

                Toggle("开关", isOn: $isOn)

                

                Slider(value: $sliderValue, in: 0...100) {

                    Text("滑动条")

                }

                

                DatePicker("日期", selection: $selectedDate)

                

                ColorPicker("颜色", selection: $selectedColor)

            }

            

            Section("按钮") {

                Button("普通按钮") {}

                

                Button(action: {}) {

                    Label("带图标的按钮", systemImage: "star.fill")

                }

                

                Button("主要按钮") {}

                    .buttonStyle(.borderedProminent)

                

                Button("胶囊按钮") {}

                    .buttonStyle(.bordered)

                    .controlSize(.large)

                    .tint(.green)

            }

        }

    }

}

注意 **$** 符号: $texttext 的绑定(Binding),双向数据流。

13.6 列表与导航

swift 复制代码
// 数据模型

struct Restaurant: Identifiable {

    let id = UUID()

    let name: String

    let cuisine: String

    let rating: Double

}

  


struct RestaurantRow: View {

    let restaurant: Restaurant

    

    var body: some View {

        HStack {

            VStack(alignment: .leading) {

                Text(restaurant.name)

                    .font(.headline)

                Text(restaurant.cuisine)

                    .font(.subheadline)

                    .foregroundColor(.secondary)

            }

            

            Spacer()

            

            HStack {

                Image(systemName: "star.fill")

                    .foregroundColor(.yellow)

                Text(String(format: "%.1f", restaurant.rating))

            }

        }

    }

}

  


struct RestaurantListView: View {

    let restaurants = [

        Restaurant(name: "川味轩", cuisine: "川菜", rating: 4.5),

        Restaurant(name: "金鼎轩", cuisine: "粤菜", rating: 4.2),

        Restaurant(name: "日料屋", cuisine: "日料", rating: 4.8)

    ]

    

    var body: some View {

        NavigationView {

            List(restaurants) { restaurant in

                NavigationLink(destination: RestaurantDetailView(restaurant: restaurant)) {

                    RestaurantRow(restaurant: restaurant)

                }

            }

            .navigationTitle("餐厅列表")

        }

    }

}

  


struct RestaurantDetailView: View {

    let restaurant: Restaurant

    

    var body: some View {

        VStack(spacing: 20) {

            Image(systemName: "fork.knife.circle.fill")

                .resizable()

                .frame(width: 100, height: 100)

                .foregroundColor(.orange)

            

            Text(restaurant.name)

                .font(.largeTitle)

            

            Text(restaurant.cuisine)

                .font(.title2)

                .foregroundColor(.secondary)

            

            HStack {

                ForEach(0..<Int(restaurant.rating), id: \.self) { _ in

                    Image(systemName: "star.fill")

                        .foregroundColor(.yellow)

                }

            }

            

            Spacer()

        }

        .padding()

        .navigationTitle("详情")

    }

}

**关键概念: **

  • Identifiable 协议:让数据可以被列表唯一标识

  • List:自动处理行、分隔线、滑动删除

  • NavigationView + NavigationLink:页面跳转

13.7 数据绑定 - @Binding

当子视图需要修改父视图的状态时:

swift 复制代码
// 子视图

struct ToggleView: View {

    @Binding var isOn: Bool  // 绑定,不是自己的状态

    

    var body: some View {

        Toggle("开关", isOn: $isOn)

    }

}

  


// 父视图

struct ParentView: View {

    @State private var lightOn = false

    

    var body: some View {

        ToggleView(isOn: $lightOn)  // 传递绑定

    }

}

13.8 可观察对象 - @StateObject 和 @ObservedObject

对于复杂数据模型,使用 ObservableObject:

swift 复制代码
import Combine

  


class TaskStore: ObservableObject {

    @Published var tasks: [Task] = []

    @Published var filter: TaskFilter = .all

    

    var filteredTasks: [Task] {

        switch filter {

        case .all: return tasks

        case .active: return tasks.filter { !$0.isCompleted }

        case .completed: return tasks.filter { $0.isCompleted }

        }

    }

    

    func addTask(_ title: String) {

        tasks.append(Task(title: title))

    }

    

    func toggleTask(_ task: Task) {

        if let index = tasks.firstIndex(where: { $0.id == task.id }) {

            tasks[index].isCompleted.toggle()

        }

    }

}

  


struct TaskListView: View {

    @StateObject private var store = TaskStore()  // 创建可观察对象

    

    var body: some View {

        List {

            ForEach(store.filteredTasks) { task in

                TaskRow(task: task) {

                    store.toggleTask(task)

                }

            }

        }

    }

}

** @StateObject vs @ObservedObject: **

  • @StateObject:创建并持有对象(用这个视图创建的数据)

  • @ObservedObject:引用外部传入的对象

13.9 环境值 - @Environment

swift 复制代码
struct ContentView: View {

    @Environment(\.colorScheme) var colorScheme

    @Environment(\.dismiss) var dismiss

    

    var body: some View {

        VStack {

            Text(colorScheme == .dark ? "Dark Mode" : "Light Mode")

            

            Button("关闭") {

                dismiss()  // 关闭当前页面

            }

        }

    }

}

第14章:SwiftUI 进阶

14.1 动画

SwiftUI 动画非常简单,只需添加 .animation

swift 复制代码
struct AnimationView: View {

    @State private var isExpanded = false

    

    var body: some View {

        VStack {

            RoundedRectangle(cornerRadius: isExpanded ? 50 : 10)

                .fill(isExpanded ? Color.blue : Color.red)

                .frame(

                    width: isExpanded ? 300 : 100,

                    height: isExpanded ? 300 : 100

                )

                .animation(.spring(response: 0.3, dampingFraction: 0.5), value: isExpanded)

            

            Button("切换") {

                isExpanded.toggle()

            }

        }

    }

}

**动画类型: **

  • .default - 默认动画

  • .linear - 线性

  • .easeIn / .easeOut / .easeInOut - 缓动

  • .spring() - 弹性动画

  • .interactiveSpring() - 交互式弹性

14.2 手势

swift 复制代码
struct GestureView: View {

    @State private var offset: CGSize = .zero

    @State private var scale: CGFloat = 1.0

    @State private var rotation: Angle = .zero

    

    var body: some View {

        Image(systemName: "star.fill")

            .font(.system(size: 100))

            .foregroundColor(.yellow)

            .offset(offset)

            .scaleEffect(scale)

            .rotationEffect(rotation)

            .gesture(

                DragGesture()

                    .onChanged { gesture in

                        offset = gesture.translation

                    }

                    .onEnded { _ in

                        withAnimation {

                            offset = .zero

                        }

                    }

            )

            .gesture(

                MagnificationGesture()

                    .onChanged { scale = $0 }

            )

            .gesture(

                RotationGesture()

                    .onChanged { rotation = $0 }

            )

    }

}

14.3 数据持久化

swift 复制代码
import SwiftUI

  


struct TodoItem: Identifiable, Codable {

    let id: UUID

    var title: String

    var isCompleted: Bool

    var createdAt: Date

}

  


class TodoStore: ObservableObject {

    @Published var todos: [TodoItem] = []

    

    private let saveKey = "todos"

    

    init() {

        load()

    }

    

    func add(_ title: String) {

        let todo = TodoItem(

            id: UUID(),

            title: title,

            isCompleted: false,

            createdAt: Date()

        )

        todos.append(todo)

        save()

    }

    

    func toggle(_ todo: TodoItem) {

        if let index = todos.firstIndex(where: { $0.id == todo.id }) {

            todos[index].isCompleted.toggle()

            save()

        }

    }

    

    func delete(at offsets: IndexSet) {

        todos.remove(atOffsets: offsets)

        save()

    }

    

    // UserDefaults 存储

    private func save() {

        if let encoded = try? JSONEncoder().encode(todos) {

            UserDefaults.standard.set(encoded, forKey: saveKey)

        }

    }

    

    private func load() {

        if let data = UserDefaults.standard.data(forKey: saveKey),

           let decoded = try? JSONDecoder().decode([TodoItem].self, from: data) {

            todos = decoded

        }

    }

}

相关推荐
泉木2 小时前
Swift 从入门到精通-终篇
swift
2501_916007478 小时前
提高开发效率的尝试,用快蝎(kxapp)完成 iOS 项目的创建、调试与构建
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
泉木1 天前
Swift 从入门到精通-第三篇
swift
songgeb1 天前
Buildable Folder & Group & Folder Reference in Xcode
xcode·swift
songgeb2 天前
UITableView 在 width=0 时 reloadData 被"空转消费"导致 Cell 显示错乱
swift·cursor
小小码农Come on2 天前
VTK-8.2.0+QT5.14.2展示3D图像
qt·3d·swift
wjm0410062 天前
ios学习路线 -- Swift基础(1)
开发语言·ios·swift
2501_915909063 天前
iOS 开发编译与真机调试流程的新思路,用快蝎 IDE 构建应用
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915918413 天前
苹果应用开发编译流程,用快蝎(kxapp)工具完成 iOS 构建与调试
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程