Swift UI 基础一

71 阅读4分钟

一、 View 协议

  • 定义:SwiftUI 中所有视图都必须遵循的协议
  • 要求:必须实现 body 计算属性
  • 目的:为 SwiftUI 提供统一的视图接口
@MainActor @preconcurrency public protocol View {

    associatedtype Body : View

    /// The content and behavior of the view.
    ///
    /// When you implement a custom view, you must implement a computed
    /// `body` property to provide the content for your view. Return a view
    /// that's composed of built-in views that SwiftUI provides, plus other
    /// composite views that you've already defined:
    ///
    ///     struct MyView: View {
    ///         var body: some View {
    ///             Text("Hello, World!")
    ///         }
    ///     }
    ///
    /// For more information about composing views and a view hierarchy,
    /// see <doc:Declaring-a-Custom-View>.
    @ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
}

struct MyView: View {
    var body: some View {
        Text("Hello, World!")
    }
}
1、 var body: some View
语法组成
  • var body::计算属性,View 协议要求
  • some View:不透明返回类型(Opaque Return Type)
为什么使用 some View
//  传统写法 - 类型声明复杂
var body: VStack<TupleView<(Text, Button<Text>)>> {
    VStack {
        Text("Hello")
        Button("Click") {}
    }
}

//  使用 some View - 简洁
var body: some View {
    VStack {
        Text("Hello")
        Button("Click") {}
    }
}
some View 中可以包含的内容
  • ✅ 单个视图:Text("Hello")
  • ✅ 布局容器:VStack { ... }
  • ✅ 条件视图:if condition { ... }
  • ✅ 循环视图:ForEach { ... }
  • ✅ 修饰器链:.padding().background()
非常重要的限制
// ❌ 不能直接写多个并列视图
var body: some View {
    Text("第一个")
    Text("第二个")  // 编译错误!
}

// ✅ 必须用容器包装
var body: some View {
    VStack {
        Text("第一个")
        Text("第二个")
    }
}

逻辑代码的处理
// ❌ 错误:混合语句破坏隐式返回
var body: some View {
    let x = 5
    Text("Hello")  // 编译错误!
}

// ✅ 方案1:显式返回
var body: some View {
    let x = 5
    return Text("Hello \(x)")
}

// ✅ 方案2:提取到计算属性
private var greeting: String {
    let x = 5
    return "Hello \(x)"
}

var body: some View {
    Text(greeting)
}

并列视图限制

什么是并列视图

在同一层级直接放置多个视图,没有容器包装。

为什么不能并列
  • 单一返回值原则body 只能返回一个视图
  • 类似函数返回值:不能同时返回多个值
解决方案
// 垂直排列
VStack {
    Text("第一行")
    Text("第二行")
    Button("按钮") {}
}

// 水平排列
HStack {
    Image("icon")
    Text("标题")
    Spacer()
}

// 层叠排列
ZStack {
    Color.blue
    Text("前景文字")
}

二、 容器视图

1、布局容器
VStack(alignment: .leading, spacing: 16) { }   // 垂直堆叠
HStack(alignment: .top, spacing: 8) { }        // 水平堆叠
ZStack(alignment: .center) { }                 // 层叠布局
LazyVStack { }                                 // 延迟垂直堆叠
LazyHStack { }                                 // 延迟水平堆叠

2、网格容器
LazyVGrid(columns: columns, spacing: 16) { }   // 垂直网格
LazyHGrid(rows: rows, spacing: 16) { }         // 水平网格

3、滚动容器
ScrollView(.vertical) { }                      // 垂直滚动
ScrollView(.horizontal) { }                    // 水平滚动
List { }                                       // 列表

4、导航容器
NavigationView { }                             // 导航视图
NavigationStack { }                            // 新导航栈 (iOS 16+)
TabView { }                                    // 标签页

5、表单容器
Form { }                                       // 表单
Section("标题") { }                            // 分组
Group { }                                      // 分组(无样式)

常见组合模式
1、经典页面结构
NavigationView {
    ScrollView {
        VStack(spacing: 20) {
            HeaderView()
            ContentView()
            FooterView()
        }
        .padding()
    }
    .navigationTitle("页面标题")
}

2、标签页应用
TabView {
    NavigationView {
        ScrollView {
            VStack {
                Text("内容")
            }
        }
        .navigationTitle("标题")
    }
    .tabItem {
        Image(systemName: "house")
        Text("首页")
    }
}

3、网格布局
ScrollView {
    LazyVGrid(columns: columns, spacing: 16) {
        ForEach(items) { item in
            ItemView(item: item)
        }
    }
    .padding()
}

错误的嵌套示例
// ❌ 错误:导航栏会跟着滚动
ScrollView {
    NavigationView {
        VStack {
            Text("内容")
        }
    }
}

// ✅ 正确:导航栏固定,内容滚动
NavigationView {
    ScrollView {
        VStack {
            Text("内容")
        }
    }
}

三、 ForEach详解

ForEach 的本质
  • 不是 Swift 语法:是 SwiftUI 框架的结构体
  • 遵循 View 协议:用于在声明式 UI 中创建视图
  • 与 Swift for 循环不同:用于视图创建,不是逻辑处理
// Swift 原生 for 循环:用于逻辑处理
for item in items {
    print(item)
    processItem(item)
}

// SwiftUI ForEach:用于创建视图
ForEach(items, id: \.id) { item in
    Text(item.name)
}

ForEach 基本使用
// 基本用法
ForEach(items, id: \.id) { item in
    Text(item.name)
}

// 数字范围
ForEach(1...10, id: \.self) { number in
    Text("第 \(number) 项")
}

// 枚举索引
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
    Text("\(index): \(item.name)")
}

四、视图修饰

通用修饰器(大部分 View 都支持)
Text("Hello")
    .padding()                    // 内边距
    .background(Color.blue)       // 背景色
    .foregroundColor(.white)      // 前景色
    .cornerRadius(8)              // 圆角
    .shadow(radius: 2)            // 阴影
    .opacity(0.8)                 // 透明度
    .frame(width: 200, height: 50) // 尺寸
    .hidden()                     // 隐藏
    .disabled(false)              // 禁用状态

特定 View 的专用修饰器

Text

Text("Hello")
    .font(.title)                 // 字体大小
    .fontWeight(.bold)            // 字体粗细
    .multilineTextAlignment(.center) // 多行文本对齐
    .lineLimit(3)                 // 行数限制
    .lineSpacing(5)               // 行间距
    .truncationMode(.tail)        // 截断模式

Button

Button("Click") {}
    .buttonStyle(.borderedProminent)  // 按钮样式
    .controlSize(.large)              // 控件大小
    .keyboardShortcut(.space)         // 键盘快捷键

Image

Image("logo")
    .resizable()                  // 可调整大小
    .scaledToFit()               // 缩放模式
    .aspectRatio(contentMode: .fill) // 宽高比
    .clipShape(Circle())          // 裁剪形状

TextField

TextField("Enter text", text: $text)
    .textFieldStyle(.roundedBorder)   // 输入框样式
    .keyboardType(.emailAddress)      // 键盘类型
    .autocapitalization(.none)        // 自动大写
    .disableAutocorrection(true)      // 禁用自动纠正

List

List {
    Text("Item 1")
}
.listStyle(.plain)                // 列表样式
.listRowSeparator(.hidden)        // 行分割线
.listRowBackground(Color.gray)    // 行背景

行高设置不同的方式

// 文本行高
Text("多行文本")
    .lineSpacing(10)              // 行间距

// 列表行高
List {
    Text("Item")
        .frame(height: 60)        // 设置行高
}

// VStack 间距
VStack(spacing: 20) {             // 元素间距
    Text("第一行")
    Text("第二行")
}