SwiftUI 详细教程

SwiftUI 是苹果公司推出的声明式 UI 框架,用于构建 Apple 平台(iOS、macOS、watchOS 和 tvOS)的用户界面。以下是 SwiftUI 的全面教程:

1. SwiftUI 简介

主要特点

  • 声明式语法:描述 UI 应该做什么,而不是如何做
  • 实时预览:Xcode 提供实时交互式预览
  • 跨平台:一套代码适配所有 Apple 平台
  • 数据驱动:自动响应数据变化更新 UI
  • 原生性能:直接映射到原生视图控件

系统要求

  • macOS 10.15 或更高版本
  • Xcode 11 或更高版本
  • iOS 13/watchOS 6/tvOS 13 或更高版本(部署目标)

2. 创建第一个 SwiftUI 项目

  1. 打开 Xcode,选择 "Create a new Xcode project"
  2. 选择 "App" 模板(iOS/macOS/watchOS/tvOS)
  3. 在界面技术中选择 "SwiftUI"
  4. 完成项目创建

项目结构

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
    }
}

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

3. 基本视图和布局

文本视图

Text("Hello, SwiftUI!")
    .font(.title)           // 字体
    .foregroundColor(.blue) // 颜色
    .bold()                // 加粗
    .italic()              // 斜体
    .underline()           // 下划线

图片视图

Image(systemName: "star.fill") // SF Symbols
    .resizable()              // 可调整大小
    .frame(width: 100, height: 100) // 尺寸
    .foregroundColor(.yellow) // 颜色

Image("custom-image")        // 项目资源中的图片
    .resizable()
    .aspectRatio(contentMode: .fit)

按钮

Button(action: {
    print("Button tapped")
}) {
    Text("Tap Me")
        .padding()
        .background(Color.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
}

// 使用系统样式
Button("Delete", action: delete)
    .buttonStyle(.bordered)
    .tint(.red)

布局容器

VStack (垂直栈)

VStack {
    Text("First")
    Text("Second")
    Text("Third")
}
.spacing(20) // 子视图间距

HStack (水平栈)

HStack {
    Image(systemName: "star")
    Text("Favorite")
    Spacer() // 填充剩余空间
    Text("4.8")
}

ZStack (层叠栈)

ZStack {
    Circle()
        .fill(Color.blue)
        .frame(width: 100, height: 100)
    Text("Center")
        .foregroundColor(.white)
}

LazyVStack/LazyHStack (惰性加载栈)

ScrollView {
    LazyVStack {
        ForEach(1...1000, id: \.self) { item in
            Text("Row \(item)")
        }
    }
}

间距和填充

VStack(spacing: 20) { // 子视图间距
    Text("Hello")
    Text("World")
}
.padding()          // 四周内边距
.padding(.horizontal, 10) // 水平内边距
.padding(EdgeInsets(top: 10, leading: 15, bottom: 20, trailing: 25))

滚动视图

ScrollView {
    VStack {
        ForEach(0..<100) { index in
            Text("Item \(index)")
                .frame(maxWidth: .infinity)
                .padding()
                .background(index % 2 == 0 ? Color.gray.opacity(0.2) : Color.white)
        }
    }
}

// 水平滚动
ScrollView(.horizontal) {
    HStack {
        ForEach(0..<50) { index in
            Circle()
                .fill(Color.blue)
                .frame(width: 50, height: 50)
                .overlay(Text("\(index)"))
        }
    }
}

4. 状态管理

@State

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
                .font(.largeTitle)

            Button("Increment") {
                count += 1
            }
        }
    }
}

@Binding

struct ChildView: View {
    @Binding var value: Int

    var body: some View {
        Button("Increment in Child") {
            value += 1
        }
    }
}

struct ParentView: View {
    @State private var counter = 0

    var body: some View {
        VStack {
            Text("Parent value: \(counter)")
            ChildView(value: $counter)
        }
    }
}

@ObservedObject

class UserSettings: ObservableObject {
    @Published var score = 0
}

struct SettingsView: View {
    @ObservedObject var settings: UserSettings

    var body: some View {
        VStack {
            Text("Score: \(settings.score)")
            Button("Increase Score") {
                settings.score += 1
            }
        }
    }
}

struct ContentView: View {
    @StateObject var settings = UserSettings()

    var body: some View {
        SettingsView(settings: settings)
    }
}

@StateObject

class DataModel: ObservableObject {
    @Published var items = [String]()

    init() {
        // 初始化数据
        loadData()
    }

    func loadData() {
        // 加载数据
        items = ["Apple", "Banana", "Orange"]
    }
}

struct ItemsView: View {
    @StateObject private var model = DataModel()

    var body: some View {
        List(model.items, id: \.self) { item in
            Text(item)
        }
    }
}

@EnvironmentObject

class AppSettings: ObservableObject {
    @Published var isDarkMode = false
}

struct ContentView: View {
    var body: some View {
        MainView()
            .environmentObject(AppSettings())
    }
}

struct MainView: View {
    @EnvironmentObject var settings: AppSettings

    var body: some View {
        Toggle("Dark Mode", isOn: $settings.isDarkMode)
    }
}

@AppStorage (UserDefaults)

struct SettingsView: View {
    @AppStorage("username") var username = "Anonymous"
    @AppStorage("isDarkMode") var isDarkMode = false

    var body: some View {
        Form {
            TextField("Username", text: $username)
            Toggle("Dark Mode", isOn: $isDarkMode)
        }
    }
}

5. 列表和导航

基本列表

List {
    Text("Item 1")
    Text("Item 2")
    Text("Item 3")
}

// 动态列表
let items = ["Apple", "Banana", "Orange"]
List(items, id: \.self) { item in
    Text(item)
}

// 带分区的列表
List {
    Section(header: Text("Fruits")) {
        Text("Apple")
        Text("Banana")
    }

    Section(header: Text("Vegetables")) {
        Text("Carrot")
        Text("Broccoli")
    }
}
.listStyle(.grouped) // 分组样式

导航视图

struct ContentView: View {
    let fruits = ["Apple", "Banana", "Orange"]

    var body: some View {
        NavigationView {
            List(fruits, id: \.self) { fruit in
                NavigationLink(destination: DetailView(fruit: fruit)) {
                    Text(fruit)
                }
            }
            .navigationTitle("Fruits")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Add") {
                        print("Add tapped")
                    }
                }
            }
        }
    }
}

struct DetailView: View {
    let fruit: String

    var body: some View {
        Text("Selected: \(fruit)")
            .navigationTitle(fruit)
    }
}

编辑列表

struct EditableListView: View {
    @State private var items = ["Item 1", "Item 2", "Item 3"]

    var body: some View {
        NavigationView {
            List {
                ForEach(items, id: \.self) { item in
                    Text(item)
                }
                .onDelete(perform: deleteItems)
                .onMove(perform: moveItems)
            }
            .navigationTitle("Edit Items")
            .toolbar {
                EditButton()

                Button("Add") {
                    addItem()
                }
            }
        }
    }

    func deleteItems(at offsets: IndexSet) {
        items.remove(atOffsets: offsets)
    }

    func moveItems(from source: IndexSet, to destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
    }

    func addItem() {
        items.append("Item \(items.count + 1)")
    }
}

6. 表单和用户输入

表单基础

Form {
    Section(header: Text("Personal Information")) {
        TextField("Name", text: $name)
        TextField("Email", text: $email)
            .keyboardType(.emailAddress)
            .textContentType(.emailAddress)
        DatePicker("Birthday", selection: $birthday, displayedComponents: .date)
    }

    Section(header: Text("Preferences")) {
        Toggle("Notifications", isOn: $notificationsEnabled)
        Picker("Theme", selection: $theme) {
            Text("Light").tag(0)
            Text("Dark").tag(1)
            Text("System").tag(2)
        }
        Stepper("Age: \(age)", value: $age, in: 0...120)
    }

    Section {
        Button("Save") {
            saveSettings()
        }
    }
}

文本输入

struct TextInputView: View {
    @State private var username = ""
    @State private var password = ""
    @State private var bio = ""

    var body: some View {
        Form {
            TextField("Username", text: $username)
                .textContentType(.username)
                .autocapitalization(.none)
                .disableAutocorrection(true)

            SecureField("Password", text: $password)
                .textContentType(.password)

            TextEditor(text: $bio)
                .frame(height: 100)
                .border(Color.gray, width: 1)
        }
    }
}

选择器

struct PickerView: View {
    let colors = ["Red", "Green", "Blue"]
    @State private var selectedColor = "Red"
    @State private var selectedNumber = 1

    var body: some View {
        Form {
            Picker("Color", selection: $selectedColor) {
                ForEach(colors, id: \.self) {
                    Text($0)
                }
            }
            .pickerStyle(.menu) // 默认样式

            Picker("Number", selection: $selectedNumber) {
                ForEach(1..<100) { number in
                    Text("\(number)")
                }
            }
            .pickerStyle(.wheel) // 滚轮样式
        }
    }
}

滑块和步进器

struct ControlsView: View {
    @State private var volume = 50.0
    @State private var brightness = 0.5
    @State private var quantity = 1

    var body: some View {
        Form {
            Slider(value: $volume, in: 0...100, step: 1) {
                Text("Volume")
            } minimumValueLabel: {
                Text("0")
            } maximumValueLabel: {
                Text("100")
            }
            Text("\(Int(volume))")

            Slider(value: $brightness)
            Text(String(format: "%.2f", brightness))

            Stepper("Quantity: \(quantity)", value: $quantity, in: 1...10)
        }
    }
}

7. 动画和过渡

隐式动画

struct ImplicitAnimationView: View {
    @State private var scale: CGFloat = 1.0

    var body: some View {
        Button("Tap Me") {
            scale += 0.5
        }
        .scaleEffect(scale)
        .animation(.default, value: scale) // 隐式动画
    }
}

显式动画

struct ExplicitAnimationView: View {
    @State private var isRotated = false

    var body: some View {
        Button(action: {
            withAnimation(.easeInOut(duration: 1.0)) {
                isRotated.toggle()
            }
        }) {
            Text("Rotate")
                .rotationEffect(.degrees(isRotated ? 180 : 0))
                .animation(.spring(), value: isRotated)
        }
    }
}

过渡动画

struct TransitionView: View {
    @State private var showDetails = false

    var body: some View {
        VStack {
            Button("Toggle") {
                withAnimation {
                    showDetails.toggle()
                }
            }

            if showDetails {
                Text("Details go here")
                    .transition(.asymmetric(
                        insertion: .move(edge: .leading),
                        removal: .opacity
                    ))
            }

            Spacer()
        }
    }
}

复杂动画

struct AdvancedAnimationView: View {
    @State private var progress: CGFloat = 0.0

    var body: some View {
        VStack {
            Circle()
                .trim(from: 0, to: progress)
                .stroke(Color.blue, lineWidth: 5)
                .frame(width: 100, height: 100)
                .rotationEffect(.degrees(-90))
                .animation(
                    .easeInOut(duration: 1.0)
                    .repeatForever(autoreverses: true),
                    value: progress
                )

            Button("Animate") {
                progress = progress == 0.0 ? 1.0 : 0.0
            }
        }
    }
}

8. 绘图和自定义视图

基本形状

struct ShapesView: View {
    var body: some View {
        VStack(spacing: 20) {
            Rectangle()
                .fill(Color.red)
                .frame(width: 100, height: 50)

            RoundedRectangle(cornerRadius: 10)
                .fill(Color.blue)
                .frame(width: 100, height: 50)

            Circle()
                .fill(Color.green)
                .frame(width: 50, height: 50)

            Capsule()
                .fill(Color.orange)
                .frame(width: 100, height: 50)

            Ellipse()
                .fill(Color.purple)
                .frame(width: 100, height: 50)
        }
    }
}

路径绘制

struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        path.move(to: CGPoint(x: rect.midX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))

        return path
    }
}

struct Star: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let points = 5
        let outerRadius = min(rect.width, rect.height) / 2
        let innerRadius = outerRadius * 0.4
        let center = CGPoint(x: rect.midX, y: rect.midY)

        for i in 0..<points * 2 {
            let angle = CGFloat(i) * .pi / CGFloat(points)
            let radius = i % 2 == 0 ? outerRadius : innerRadius

            let point = CGPoint(
                x: center.x + radius * sin(angle),
                y: center.y + radius * cos(angle)
            )

            if i == 0 {
                path.move(to: point)
            } else {
                path.addLine(to: point)
            }
        }

        path.closeSubpath()
        return path
    }
}

struct CustomShapesView: View {
    var body: some View {
        VStack(spacing: 20) {
            Triangle()
                .fill(Color.red)
                .frame(width: 100, height: 100)

            Star()
                .fill(Color.blue)
                .frame(width: 100, height: 100)
        }
    }
}

视图组合

struct Badge: View {
    var count: Int

    var body: some View {
        ZStack {
            Circle()
                .fill(Color.red)
                .frame(width: 20, height: 20)

            Text("\(count)")
                .foregroundColor(.white)
                .font(.caption)
        }
    }
}

struct NotificationIcon: View {
    var hasUnread: Bool
    var unreadCount: Int

    var body: some View {
        ZStack(alignment: .topTrailing) {
            Image(systemName: "bell.fill")
                .font(.title)

            if hasUnread {
                Badge(count: unreadCount)
                    .offset(x: 5, y: -5)
            }
        }
    }
}

struct CombinedView: View {
    var body: some View {
        HStack {
            NotificationIcon(hasUnread: true, unreadCount: 3)
            NotificationIcon(hasUnread: false, unreadCount: 0)
        }
    }
}

9. 网络请求和数据持久化

网络请求 (URLSession)

struct Post: Codable, Identifiable {
    let id: Int
    let title: String
    let body: String
}

class PostsViewModel: ObservableObject {
    @Published var posts = [Post]()
    @Published var isLoading = false
    @Published var error: Error?

    func fetchPosts() {
        isLoading = true
        error = nil

        guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
            return
        }

        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            DispatchQueue.main.async {
                self?.isLoading = false

                if let error = error {
                    self?.error = error
                    return
                }

                guard let data = data else { return }

                do {
                    self?.posts = try JSONDecoder().decode([Post].self, from: data)
                } catch {
                    self?.error = error
                }
            }
        }.resume()
    }
}

struct PostsView: View {
    @StateObject var viewModel = PostsViewModel()

    var body: some View {
        Group {
            if viewModel.isLoading {
                ProgressView("Loading...")
            } else if let error = viewModel.error {
                Text("Error: \(error.localizedDescription)")
            } else {
                List(viewModel.posts) { post in
                    VStack(alignment: .leading) {
                        Text(post.title)
                            .font(.headline)
                        Text(post.body)
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                    }
                }
            }
        }
        .navigationTitle("Posts")
        .onAppear {
            viewModel.fetchPosts()
        }
    }
}

Core Data 集成

// 1. 创建 Core Data 模型文件 (.xcdatamodeld)
// 2. 定义实体和属性

import CoreData

class DataController: ObservableObject {
    let container = NSPersistentContainer(name: "Model")

    init() {
        container.loadPersistentStores { description, error in
            if let error = error {
                print("Core Data failed to load: \(error.localizedDescription)")
            }
        }
    }
}

@main
struct CoreDataApp: App {
    @StateObject private var dataController = DataController()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, dataController.container.viewContext)
        }
    }
}

struct Task: Identifiable {
    let id: UUID
    let title: String
    let isCompleted: Bool
}

struct TaskListView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(
        entity: TaskEntity.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \TaskEntity.createdAt, ascending: true)]
    ) var tasks: FetchedResults<TaskEntity>

    @State private var newTaskTitle = ""

    var body: some View {
        NavigationView {
            List {
                ForEach(tasks) { task in
                    HStack {
                        Text(task.title ?? "Unknown")
                        Spacer()
                        Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                            .foregroundColor(task.isCompleted ? .green : .gray)
                    }
                }
                .onDelete(perform: deleteTasks)

                HStack {
                    TextField("New task", text: $newTaskTitle)
                    Button("Add") {
                        addTask()
                    }
                    .disabled(newTaskTitle.isEmpty)
                }
            }
            .navigationTitle("Tasks")
            .toolbar {
                EditButton()
            }
        }
    }

    func addTask() {
        let newTask = TaskEntity(context: moc)
        newTask.id = UUID()
        newTask.title = newTaskTitle
        newTask.isCompleted = false
        newTask.createdAt = Date()

        try? moc.save()
        newTaskTitle = ""
    }

    func deleteTasks(at offsets: IndexSet) {
        for index in offsets {
            let task = tasks[index]
            moc.delete(task)
        }

        try? moc.save()
    }
}

10. 高级主题

自定义视图修饰符

struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 2)
    }
}

extension View {
    func cardStyle() -> some View {
        self.modifier(CardModifier())
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello")
                .cardStyle()
            Text("World")
                .cardStyle()
        }
        .padding()
    }
}

自定义按钮样式

struct GradientButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding()
            .foregroundColor(.white)
            .background(
                LinearGradient(
                    gradient: Gradient(colors: [Color.blue, Color.purple]),
                    startPoint: .leading,
                    endPoint: .trailing
                )
            )
            .cornerRadius(10)
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
            .opacity(configuration.isPressed ? 0.9 : 1.0)
            .animation(.easeOut(duration: 0.2), value: configuration.isPressed)
    }
}

struct CustomButtonView: View {
    var body: some View {
        Button("Press Me") {
            print("Button pressed")
        }
        .buttonStyle(GradientButtonStyle())
    }
}

视图偏好键

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct SizeReportingView: View {
    var body: some View {
        GeometryReader { geometry in
            Color.clear
                .preference(key: SizePreferenceKey.self, value: geometry.size)
        }
    }
}

struct SizeReaderView: View {
    @State private var size: CGSize = .zero

    var body: some View {
        VStack {
            Text("Width: \(size.width, specifier: "%.1f")")
            Text("Height: \(size.height, specifier: "%.1f")")

            Rectangle()
                .fill(Color.blue)
                .frame(width: 200, height: 100)
                .background(SizeReportingView())
                .onPreferenceChange(SizePreferenceKey.self) { newSize in
                    size = newSize
                }
        }
    }
}

拖放支持

struct DragDropView: View {
    @State private var items = ["Apple", "Banana", "Orange", "Grapes"]
    @State private var draggedItem: String?

    var body: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 20) {
            ForEach(items, id: \.self) { item in
                Text(item)
                    .frame(width: 100, height: 100)
                    .background(Color.blue)
                    .cornerRadius(10)
                    .foregroundColor(.white)
                    .onDrag {
                        draggedItem = item
                        return NSItemProvider(object: item as NSString)
                    }
                    .onDrop(
                        of: [.text],
                        delegate: DropViewDelegate(
                            item: item,
                            items: $items,
                            draggedItem: $draggedItem
                        )
                    )
            }
        }
        .padding()
    }
}

struct DropViewDelegate: DropDelegate {
    let item: String
    @Binding var items: [String]
    @Binding var draggedItem: String?

    func performDrop(info: DropInfo) -> Bool {
        return true
    }

    func dropEntered(info: DropInfo) {
        guard let draggedItem = draggedItem else { return }

        let fromIndex = items.firstIndex(of: draggedItem)!
        let toIndex = items.firstIndex(of: item)!

        if fromIndex != toIndex {
            items.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: toIndex > fromIndex ? toIndex + 1 : toIndex)
        }
    }
}

11. 多平台适配

条件编译

struct ContentView: View {
    var body: some View {
        VStack {
            #if os(iOS)
            Text("Running on iOS")
            #elseif os(macOS)
            Text("Running on macOS")
            #elseif os(watchOS)
            Text("Running on watchOS")
            #elseif os(tvOS)
            Text("Running on tvOS")
            #endif
        }
    }
}

平台特定视图

struct PlatformAdaptiveView: View {
    var body: some View {
        Group {
            #if os(iOS)
            iOSView()
            #elseif os(macOS)
            macOSView()
            #endif
        }
    }
}

struct iOSView: View {
    var body: some View {
        NavigationView {
            List {
                Text("iOS Specific UI")
            }
            .navigationTitle("iOS")
        }
    }
}

struct macOSView: View {
    var body: some View {
        NavigationView {
            List {
                Text("macOS Specific UI")
            }
            .frame(minWidth: 300)
            .navigationTitle("macOS")
        }
    }
}

设备方向检测

struct OrientationView: View {
    @State private var orientation = UIDeviceOrientation.unknown

    var body: some View {
        Group {
            if orientation.isPortrait {
                Text("Portrait")
            } else if orientation.isLandscape {
                Text("Landscape")
            } else {
                Text("Unknown")
            }
        }
        .onAppear {
            orientation = UIDevice.current.orientation
        }
        .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
            orientation = UIDevice.current.orientation
        }
    }
}

12. 测试和调试

预览调试

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice("iPhone 13")
                .previewDisplayName("iPhone 13")

            ContentView()
                .previewDevice("iPhone SE (2nd generation)")
                .previewDisplayName("iPhone SE")

            ContentView()
                .previewDevice("iPad Pro (11-inch) (3rd generation)")
                .previewDisplayName("iPad Pro")
        }
    }
}

调试修饰符

struct DebugView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
        .debugAction {
            print("Current count: \(count)")
        }
        .border(Color.red) // 可视化视图边界
    }
}

extension View {
    func debugAction(_ action: () -> Void) -> some View {
        #if DEBUG
        action()
        #endif
        return self
    }
}

单元测试

import XCTest
@testable import YourApp

class YourAppTests: XCTestCase {
    func testExample() throws {
        let viewModel = PostsViewModel()
        XCTAssertTrue(viewModel.posts.isEmpty)

        viewModel.fetchPosts()
        // 使用 XCTestExpectation 测试异步代码
    }
}

13. 性能优化

惰性加载

struct LazyLoadingView: View {
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(1...1000, id: \.self) { item in
                    RowView(item: item)
                        .onAppear {
                            print("Loading item \(item)")
                        }
                }
            }
        }
    }
}

struct RowView: View {
    let item: Int

    var body: some View {
        Text("Row \(item)")
            .padding()
    }
}

绘图性能

struct HighPerformanceDrawing: View {
    let colors: [Color] = [.red, .green, .blue, .orange, .purple]

    var body: some View {
        Canvas { context, size in
            for _ in 0..<1000 {
                let x = CGFloat.random(in: 0..<size.width)
                let y = CGFloat.random(in: 0..<size.height)
                let radius = CGFloat.random(in: 5..<20)
                let color = colors.randomElement()!

                let rect = CGRect(
                    x: x - radius,
                    y: y - radius,
                    width: radius * 2,
                    height: radius * 2
                )

                context.fill(
                    Path(ellipseIn: rect),
                    with: .color(color)
                )
            }
        }
    }
}

避免不必要的视图更新

struct OptimizedView: View {
    @StateObject private var model = DataModel()

    var body: some View {
        VStack {
            // 只有变化的子视图会重新计算
            Text("Static Title")

            // 使用 EquatableView 防止不必要的重绘
            EquatableView(content: ExpensiveView(data: model.expensiveData))

            Button("Update") {
                model.update()
            }
        }
    }
}

struct ExpensiveView: View, Equatable {
    let data: [String]

    var body: some View {
        List(data, id: \.self) { item in
            Text(item)
        }
    }

    static func == (lhs: ExpensiveView, rhs: ExpensiveView) -> Bool {
        lhs.data == rhs.data
    }
}

14. 资源与进阶学习

官方资源

学习资源

开源项目

社区

SwiftUI 是一个强大而现代的 UI 框架,本教程涵盖了 SwiftUI 的主要特性和概念。要精通 SwiftUI 需要不断实践,建议通过实际项目来加深理解。随着 SwiftUI 的不断发展,保持学习最新特性和最佳实践非常重要。









results matching ""

    No results matching ""