阅读视图

发现新文章,点击刷新页面。

第4章:基础布局系统

Snip20260416_1.png

示例代码都放这里啦,有需要的可以下载学习。swiftUIDemo

4.1 垂直布局:VStack

VStack 介绍

VStack 是 SwiftUI 中用于垂直堆叠视图的容器,它会将子视图按垂直方向排列。VStack 是构建垂直布局的基础组件,适用于需要从上到下排列的界面元素。

基本用法

// 基本垂直栈
VStack {
    Text("第一行")
    Text("第二行")
    Text("第三行")
}

// 带间距和对齐的垂直栈
VStack(alignment: .leading, spacing: 20) {
    Text("左对齐")
    Text("第二行")
    Text("第三行")
}
.padding()

对齐方式

VStack 提供了三种主要的对齐方式:

  • .leading:左对齐
  • .center:居中对齐(默认)
  • .trailing:右对齐
  • .top.bottom:在嵌套布局中使用
// 不同对齐方式
VStack(alignment: .leading) {
    Text("左对齐")
    Text("这是一行更长的文本")
    Text("短文本")
}
.padding()

VStack(alignment: .center) {
    Text("居中对齐")
    Text("这是一行更长的文本")
    Text("短文本")
}
.padding()

VStack(alignment: .trailing) {
    Text("右对齐")
    Text("这是一行更长的文本")
    Text("短文本")
}
.padding()

嵌套 VStack

VStack 可以嵌套使用,创建更复杂的布局结构。

// 嵌套垂直栈
VStack(spacing: 10) {
    Text("标题")
        .font(.headline)
    
    VStack(alignment: .leading, spacing: 8) {
        Text("项目 1")
        Text("项目 2")
        Text("项目 3")
    }
    .padding()
    .background(Color.gray.opacity(0.1))
    .cornerRadius(8)
    
    Button("确认") {}
}
.padding()

适用场景

  • 表单布局:从上到下排列的输入字段
  • 列表项:垂直排列的内容块
  • 页面结构:标题、内容、按钮的垂直布局
  • 卡片式布局:垂直堆叠的信息卡片

性能考虑

  • VStack 会根据子视图的大小自动调整高度
  • 对于大量子视图,考虑使用 LazyVStack 来提高性能
  • 避免过深的嵌套,可能会影响渲染性能

4.2 水平布局:HStack

HStack 介绍

HStack 是 SwiftUI 中用于水平堆叠视图的容器,它会将子视图按水平方向排列。HStack 是构建水平布局的基础组件,适用于需要从左到右排列的界面元素。

基本用法

// 基本水平栈
HStack {
    Text("左侧")
    Text("中间")
    Text("右侧")
}

// 带间距和对齐的水平栈
HStack(alignment: .top, spacing: 20) {
    Text("顶部对齐")
    Text("这是一行\n多行文本")
    Text("短文本")
}
.padding()

对齐方式

HStack 提供了三种主要的对齐方式:

  • .top:顶部对齐
  • .center:居中对齐(默认)
  • .bottom:底部对齐
  • .leading.trailing:在嵌套布局中使用
// 不同对齐方式
HStack(alignment: .top) {
    Text("顶部对齐")
    Text("这是一行\n多行文本")
    Text("短文本")
}
.padding()

HStack(alignment: .center) {
    Text("居中对齐")
    Text("这是一行\n多行文本")
    Text("短文本")
}
.padding()

HStack(alignment: .bottom) {
    Text("底部对齐")
    Text("这是一行\n多行文本")
    Text("短文本")
}
.padding()

空间分配

HStack 可以使用 Spacer 来分配空间,实现更灵活的布局。

// 空间分配
HStack {
    Text("左侧")
    Spacer()  // 占据剩余空间
    Text("右侧")
}
.padding()

// 带比例的空间分配
HStack {
    Text("1/4 宽度")
        .frame(maxWidth: .infinity)
    Spacer()
    Text("1/4 宽度")
        .frame(maxWidth: .infinity)
    Spacer()
    Text("1/4 宽度")
        .frame(maxWidth: .infinity)
    Spacer()
    Text("1/4 宽度")
        .frame(maxWidth: .infinity)
}
.padding()

适用场景

  • 工具栏:水平排列的操作按钮
  • 列表项内容:左侧图标、中间文本、右侧箭头
  • 表单行:标签和输入框的水平排列
  • 导航栏:左侧返回按钮、中间标题、右侧操作按钮

性能考虑

  • HStack 会根据子视图的大小自动调整宽度
  • 对于大量子视图,考虑使用 LazyHStack 来提高性能
  • 注意水平空间不足时的布局行为,可能需要使用 ScrollView

4.3 层叠布局:ZStack

ZStack 介绍

ZStack 是 SwiftUI 中用于层叠视图的容器,它会将子视图按层叠方式排列,后面的视图会覆盖前面的视图。ZStack 是构建叠加效果的基础组件,适用于需要层级关系的界面元素。

基本用法

// 基本层叠
ZStack {
    Color.blue  // 背景
    Text("前景文本")
        .foregroundStyle(.white)
        .font(.largeTitle)
}
.frame(height: 200)

// 多层叠
ZStack {
    // 底层
    Rectangle()
        .fill(Color.yellow)
        .frame(width: 200, height: 200)
    
    // 中层
    Circle()
        .fill(Color.green)
        .frame(width: 150, height: 150)
    
    // 顶层
    Text("ZStack")
        .font(.headline)
}

对齐方式

ZStack 提供了多种对齐方式,可以精确控制子视图的位置:

  • .topLeading.top.topTrailing
  • .leading.center.trailing
  • .bottomLeading.bottom.bottomTrailing
// 不同对齐方式
ZStack(alignment: .topLeading) {
    Rectangle()
        .fill(Color.gray.opacity(0.2))
        .frame(width: 300, height: 200)
    
    Text("左上角")
        .padding(10)
}

ZStack(alignment: .center) {
    Rectangle()
        .fill(Color.gray.opacity(0.2))
        .frame(width: 300, height: 200)
    
    Text("居中")
}

ZStack(alignment: .bottomTrailing) {
    Rectangle()
        .fill(Color.gray.opacity(0.2))
        .frame(width: 300, height: 200)
    
    Text("右下角")
        .padding(10)
}

实际应用

// 带徽章的图标
ZStack(alignment: .topTrailing) {
    Image(systemName: "bell")
        .font(.system(size: 24))
    
    Circle()
        .fill(Color.red)
        .frame(width: 16, height: 16)
        .overlay {
            Text("3")
                .font(.system(size: 10))
                .foregroundStyle(.white)
        }
        .offset(x: 4, y: -4)
}

// 卡片覆盖效果
ZStack {
    RoundedRectangle(cornerRadius: 12)
        .fill(Color.white)
        .shadow(radius: 4)
        .frame(width: 300, height: 200)
    
    VStack {
        Text("卡片标题")
            .font(.headline)
        Text("卡片内容")
            .foregroundStyle(.secondary)
    }
    .padding()
    
    // 右上角标签
    Text("NEW")
        .font(.caption)
        .foregroundStyle(.white)
        .padding(4)
        .background(Color.blue)
        .cornerRadius(4)
        .offset(x: 45, y: -10)
}

适用场景

  • 带背景的文本:文本叠加在背景之上
  • 徽章效果:通知图标上的数字徽章
  • 卡片布局:带有覆盖元素的信息卡片
  • 复杂 UI 组件:需要多层叠加的自定义控件
  • 模态视图:半透明覆盖层

性能考虑

  • ZStack 会按照添加顺序渲染视图,后面的视图会覆盖前面的
  • 对于复杂的叠加效果,注意渲染性能
  • 考虑使用 offset 修饰符来微调子视图位置

4.4 间距与对齐

间距设置

间距是布局中的重要因素,它决定了视图之间的关系和视觉舒适度。

// VStack 间距
VStack(spacing: 16) {
    Text("项目 1")
    Text("项目 2")
    Text("项目 3")
}

// HStack 间距
HStack(spacing: 20) {
    Text("左")
    Text("中")
    Text("右")
}

// 嵌套栈的间距
VStack(spacing: 20) {
    Text("标题")
    
    HStack(spacing: 10) {
        Button("按钮 1") {}
        Button("按钮 2") {}
    }
    
    Text("底部文本")
}

对齐设置

对齐决定了视图在容器中的位置,影响整体布局的一致性。

// 垂直对齐
VStack(alignment: .leading) {
    Text("左对齐")
    Text("这是一行更长的文本")
}

// 水平对齐
HStack(alignment: .center) {
    Text("顶部")
        .font(.largeTitle)
    Text("底部")
        .font(.footnote)
}

// 层叠对齐
ZStack(alignment: .bottom) {
    Image(systemName: "photo")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(height: 200)
    
    Text("图片标题")
        .padding()
        .background(Color.black.opacity(0.5))
        .foregroundStyle(.white)
        .frame(maxWidth: .infinity, alignment: .center)
}

内边距与外边距

内边距(padding)和外边距是控制视图与其他元素之间空间的重要工具。

// 内边距
VStack {
    Text("带内边距的文本")
        .padding()  // 四周内边距
    
    Text("自定义内边距")
        .padding(.horizontal, 20)  // 水平内边距
        .padding(.vertical, 10)    // 垂直内边距
}

// 外边距
VStack {
    Text("带外边距的文本")
}
.padding()  // 给整个 VStack 添加内边距

// 组合使用
Text("文本")
    .padding(10)  // 内边距
    .background(Color.yellow)
    .padding(10)  // 外边距(看起来像内边距)
    .background(Color.blue)

适用场景

  • 表单设计:通过间距和对齐创建整齐的表单
  • 卡片布局:使用内边距和外边距创建视觉层次感
  • 响应式设计:根据不同屏幕尺寸调整间距
  • 可访问性:适当的间距提高内容的可读性

最佳实践

  • 保持一致的间距系统:使用统一的间距值(如 8、16、24 等)
  • 考虑内容的重要性:重要内容之间应有更大的间距
  • 响应式调整:在不同屏幕尺寸上调整间距
  • 测试不同设备:确保在各种设备上布局都美观

4.5 垫片:Spacer

Spacer 介绍

Spacer 是 SwiftUI 中用于占据剩余空间的视图,它会自动扩展以填充可用空间。Spacer 是实现灵活布局的重要工具,特别适用于需要将元素推到容器边缘的场景。

基本用法

// 水平布局中的 Spacer
HStack {
    Text("左侧")
    Spacer()  // 占据中间的所有空间
    Text("右侧")
}
.padding()

// 垂直布局中的 Spacer
VStack {
    Text("顶部")
    Spacer()  // 占据中间的所有空间
    Text("底部")
}
.frame(height: 200)
.padding()

灵活使用

// 顶部对齐
VStack {
    Text("标题")
    Spacer()
}
.frame(height: 200)
.padding()

// 底部对齐
VStack {
    Spacer()
    Text("底部文本")
}
.frame(height: 200)
.padding()

// 两端对齐
HStack {
    Text("左侧")
    Spacer()
    Text("中间")
    Spacer()
    Text("右侧")
}
.padding()

实际应用

// 工具栏布局
HStack {
    Button("返回") {
        print("返回")
    }
    
    Spacer()
    
    Text("页面标题")
    
    Spacer()
    
    Button("更多") {
        print("更多")
    }
}
.padding()
.background(Color(.systemBackground))

// 表单底部按钮
VStack {
    // 表单内容
    ForEach(0..<3) {
        Text("表单项 \($0 + 1)")
            .padding()
            .background(Color.gray.opacity(0.1))
            .cornerRadius(8)
            .padding(.horizontal)
    }
    
    Spacer()
    
    // 底部按钮
    Button("提交") {
        print("提交")
    }
    .buttonStyle(.borderedProminent)
    .padding()
}

适用场景

  • 工具栏:将标题居中,按钮放在两侧
  • 表单:将提交按钮固定在底部
  • 卡片:将内容推到顶部,操作按钮放在底部
  • 导航栏:创建平衡的布局

性能考虑

  • Spacer 是轻量级视图,对性能影响很小
  • 合理使用 Spacer 可以减少不必要的几何计算
  • 避免在不需要的地方使用 Spacer,可能会导致意外的布局行为

4.6 布局修饰符

框架修饰符

frame 修饰符用于控制视图的大小和对齐方式。

// 设置固定大小
Text("固定大小")
    .frame(width: 200, height: 100)
    .background(Color.yellow)

// 设置最大和最小大小
Text("灵活大小")
    .frame(minWidth: 100, maxWidth: 300, minHeight: 50, maxHeight: 150)
    .background(Color.blue)

// 填充父容器
Text("填充")
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(Color.green)

// 带对齐的框架
Text("右对齐")
    .frame(width: 200, alignment: .trailing)
    .background(Color.gray.opacity(0.2))

位置修饰符

positionoffset 修饰符用于调整视图的位置。

// 绝对位置
Text("绝对位置")
    .position(x: 100, y: 100)

// 相对偏移
Text("相对偏移")
    .offset(x: 50, y: 20)

// 组合使用
ZStack {
    Text("基础位置")
        .background(Color.yellow)
    
    Text("偏移位置")
        .offset(x: 50, y: 30)
        .background(Color.red)
}

布局优先级

layoutPriority 修饰符用于设置视图的布局优先级。

HStack {
    Text("短文本")
        .layoutPriority(1)  // 高优先级
        .background(Color.yellow)
    
    Text("这是一段非常长的文本,会被截断")
        .background(Color.blue)
}
.frame(width: 200)

适用场景

  • 响应式设计:根据屏幕尺寸调整视图大小
  • 自定义布局:精确控制视图位置
  • 复杂界面:处理不同优先级的内容
  • 动态布局:根据内容自动调整

4.7 容器布局

List

List 是用于显示滚动列表的容器,自动处理单元格布局。

// 基本列表
List {
    Text("项目 1")
    Text("项目 2")
    Text("项目 3")
}

// 带分组的列表
List {
    Section(header: Text("分组 1")) {
        Text("项目 1")
        Text("项目 2")
    }
    
    Section(header: Text("分组 2")) {
        Text("项目 3")
        Text("项目 4")
    }
}

ScrollView

ScrollView 用于创建可滚动的内容区域。

// 垂直滚动
ScrollView {
    VStack(spacing: 20) {
        ForEach(0..<20) {
            Text("项目 \($0 + 1)")
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(8)
        }
    }
    .padding()
}

// 水平滚动
ScrollView(.horizontal) {
    HStack(spacing: 20) {
        ForEach(0..<10) {
            Text("项目 \($0 + 1)")
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(8)
        }
    }
    .padding()
}

LazyVStack 和 LazyHStack

LazyVStackLazyHStack 是延迟加载的栈容器,适用于大量数据。

// 延迟加载的垂直栈
ScrollView {
    LazyVStack(spacing: 20) {
        ForEach(0..<1000) {
            Text("项目 \($0 + 1)")
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(8)
        }
    }
    .padding()
}

适用场景

  • List:显示结构化数据列表
  • ScrollView:显示超出屏幕的内容
  • LazyVStack/LazyHStack:处理大量数据,提高性能

实战:创建一个登录页面

需求分析

创建一个包含以下元素的登录页面:

  1. 应用图标和标题
  2. 用户名输入框
  3. 密码输入框(带可见性切换)
  4. 登录按钮
  5. 忘记密码链接
  6. 注册链接

代码实现

import SwiftUI

struct LoginView: View {
    // 状态变量
    @State private var username = ""
    @State private var password = ""
    @State private var showPassword = false
    
    var body: some View {
        ZStack {
            // 背景
            LinearGradient(
                colors: [.blue.opacity(0.1), .purple.opacity(0.1)],
                startPoint: .top,
                endPoint: .bottom
            )
            .ignoresSafeArea()
            
            VStack(spacing: 24) {
                // 应用图标和标题
                VStack(spacing: 12) {
                    Image(systemName: "lock.fill")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 80, height: 80)
                        .foregroundStyle(.blue)
                    
                    Text("欢迎回来")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                    
                    Text("请登录以继续")
                        .foregroundStyle(.secondary)
                }
                
                // 输入区域
                VStack(spacing: 16) {
                    // 用户名输入框
                    TextField(
                        "用户名",
                        text: $username,
                        prompt: Text("请输入用户名")
                    )
                    .textFieldStyle(.roundedBorder)
                    .padding(.horizontal)
                    
                    // 密码输入框
                    ZStack(alignment: .trailing) {
                        if showPassword {
                            TextField(
                                "密码",
                                text: $password,
                                prompt: Text("请输入密码")
                            )
                        } else {
                            SecureField(
                                "密码",
                                text: $password,
                                prompt: Text("请输入密码")
                            )
                        }
                        
                        Button(action: {
                            showPassword.toggle()
                        }) {
                            Image(systemName: showPassword ? "eye.slash.fill" : "eye.fill")
                                .foregroundStyle(.secondary)
                                .padding(.trailing, 16)
                        }
                    }
                    .textFieldStyle(.roundedBorder)
                    .padding(.horizontal)
                    
                    // 忘记密码
                    HStack {
                        Spacer()
                        Button("忘记密码?") {
                            print("忘记密码")
                        }
                        .foregroundStyle(.blue)
                        .padding(.trailing)
                    }
                }
                
                // 登录按钮
                Button("登录") {
                    print("登录")
                }
                .buttonStyle(.borderedProminent)
                .tint(.blue)
                .padding(.horizontal)
                .frame(maxWidth: .infinity)
                
                // 注册链接
                HStack {
                    Text("还没有账号?")
                    Button("立即注册") {
                        print("注册")
                    }
                    .foregroundStyle(.blue)
                }
                
                Spacer()
            }
            .padding(.top, 60)
        }
    }
}

#Preview {
    LoginView()
}

代码解析

  1. ZStack:用于层叠背景和内容,创建深度感
  2. VStack:用于垂直排列各个部分,保持页面结构清晰
  3. HStack:用于水平排列忘记密码链接和注册链接
  4. Spacer:用于底部填充空间,将内容推到顶部
  5. TextField 和 SecureField:用于用户输入
  6. Button:用于操作按钮
  7. LinearGradient:用于创建美观的背景渐变
  8. @State:用于管理视图状态

实战:创建一个产品详情页

需求分析

创建一个产品详情页,包含以下元素:

  1. 产品图片
  2. 产品标题和价格
  3. 产品描述
  4. 规格选择
  5. 购买按钮

代码实现

import SwiftUI

struct ProductDetailView: View {
    // 状态变量
    @State private var selectedColor = "红色"
    @State private var selectedSize = "M"
    @State private var quantity = 1
    
    // 产品数据
    let productName = "SwiftUI 高级教程"
    let productPrice = "¥99.00"
    let productDescription = "本教程涵盖了 SwiftUI 的高级特性,包括动画、手势、布局和性能优化等内容。通过实际项目案例,帮助你掌握 SwiftUI 的核心概念和最佳实践。"
    let colors = ["红色", "蓝色", "黑色"]
    let sizes = ["S", "M", "L", "XL"]
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // 产品图片
                ZStack {
                    Color.gray.opacity(0.1)
                        .frame(height: 300)
                    
                    Image(systemName: "book.fill")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 150, height: 150)
                        .foregroundStyle(.blue)
                }
                
                // 产品信息
                VStack(alignment: .leading, spacing: 12) {
                    HStack {
                        Text(productName)
                            .font(.title)
                            .fontWeight(.bold)
                        Spacer()
                        Text(productPrice)
                            .font(.title)
                            .fontWeight(.bold)
                            .foregroundStyle(.red)
                    }
                    
                    // 产品描述
                    Text(productDescription)
                        .foregroundStyle(.secondary)
                        .lineLimit(nil)
                    
                    // 颜色选择
                    Text("颜色")
                        .font(.headline)
                    HStack(spacing: 10) {
                        ForEach(colors, id: \.self) {
                            color in
                            Button(action: {
                                selectedColor = color
                            }) {
                                Text(color)
                                    .padding(8)
                                    .background(selectedColor == color ? Color.blue : Color.gray.opacity(0.1))
                                    .foregroundStyle(selectedColor == color ? .white : .primary)
                                    .cornerRadius(4)
                            }
                        }
                    }
                    
                    // 尺寸选择
                    Text("尺寸")
                        .font(.headline)
                    HStack(spacing: 10) {
                        ForEach(sizes, id: \.self) {
                            size in
                            Button(action: {
                                selectedSize = size
                            }) {
                                Text(size)
                                    .padding(8)
                                    .background(selectedSize == size ? Color.blue : Color.gray.opacity(0.1))
                                    .foregroundStyle(selectedSize == size ? .white : .primary)
                                    .cornerRadius(4)
                            }
                        }
                    }
                    
                    // 数量选择
                    Text("数量")
                        .font(.headline)
                    HStack {
                        Button(action: {
                            if quantity > 1 {
                                quantity -= 1
                            }
                        }) {
                            Image(systemName: "minus.circle")
                                .font(.system(size: 24))
                        }
                        
                        Text("\(quantity)")
                            .font(.headline)
                            .padding(.horizontal, 20)
                        
                        Button(action: {
                            quantity += 1
                        }) {
                            Image(systemName: "plus.circle")
                                .font(.system(size: 24))
                        }
                    }
                }
                .padding()
                
                // 购买按钮
                Button("加入购物车") {
                    print("加入购物车")
                }
                .buttonStyle(.borderedProminent)
                .tint(.blue)
                .padding(.horizontal)
                .frame(maxWidth: .infinity)
                .padding(.bottom, 30)
            }
        }
        .navigationTitle("产品详情")
        .navigationBarTitleDisplayMode(.inline)
    }
}

#Preview {
    ProductDetailView()
}

代码解析

  1. ScrollView:用于滚动显示产品详情
  2. ZStack:用于显示产品图片和背景
  3. VStack:用于垂直排列产品信息
  4. HStack:用于水平排列价格、颜色选择、尺寸选择和数量控制
  5. Button:用于选择颜色、尺寸和调整数量
  6. @State:用于管理用户选择的状态

小结

本章详细介绍了 SwiftUI 中的基础布局系统,包括:

  • VStack:垂直堆叠视图,适用于从上到下的布局
  • HStack:水平堆叠视图,适用于从左到右的布局
  • ZStack:层叠视图,适用于需要层级关系的布局
  • 间距与对齐:控制视图之间的空间和位置关系
  • Spacer:占据剩余空间,实现灵活布局
  • 布局修饰符:控制视图的大小、位置和优先级
  • 容器布局:List、ScrollView、LazyVStack 等高级容器
  • 实战案例:登录页面和产品详情页的完整实现

布局最佳实践

  1. 保持简洁:使用最少的容器实现所需布局
  2. 嵌套合理:避免过深的布局嵌套
  3. 响应式设计:考虑不同屏幕尺寸的布局适配
  4. 性能优化:对于大量数据使用 Lazy 容器
  5. 一致性:保持间距和对齐的一致性
  6. 可访问性:确保布局对所有用户都友好

通过本章的学习,你已经掌握了 SwiftUI 中最基本的布局技巧,能够创建各种常见的布局结构。在实际开发中,你可以根据具体需求选择合适的布局容器和技术,创建美观、响应式的用户界面。


参考资料


本内容为《SwiftUI 基础教程》第四章,欢迎关注后续更新。

群核科技香港公开发售获1591倍超额认购,暗盘暴涨170%

4月16日晚,“杭州六小龙”之一的Manycore Tech(简称“群核科技”)公布配售结果,群核科技香港公开发售获1591倍认购,国际发售获14.46倍认购。值得注意的是,4月16日,群核科技富途暗盘交易收涨170%,报20.52港元,市值近350亿港元。该股将于4月17日(周五)正式登陆港交所,成为“全球空间智能第一股”。

第3章:基础视图组件

Snip20260416_1.png

示例代码都放这里啦,有需要的可以下载学习。swiftUIDemo

3.1 文本显示:Text

Text 组件介绍

Text 是 SwiftUI 中最基本的视图组件,用于显示文本内容。它支持富文本、字体样式、颜色等多种属性。

基本用法

// 基本文本
Text("Hello, SwiftUI!")

// 带样式的文本
Text("Hello, SwiftUI!")
    .font(.largeTitle)         // 设置字体大小
    .fontWeight(.bold)         // 设置字重
    .foregroundStyle(.blue)    // 设置文本颜色
    .italic()                  // 斜体
    .underline()               // 下划线
    .strikethrough()           // 删除线

富文本

// 富文本
Text("Hello, \(Text("SwiftUI").foregroundStyle(.blue).bold())!")

// 多行文本
Text("这是一段多行文本,\n可以通过反斜杠 n 来换行,\n或者直接在字符串中换行。")
    .multilineTextAlignment(.center)  // 多行文本对齐方式
    .lineLimit(3)                     // 限制行数
    .truncationMode(.tail)            // 截断方式

本地化

// 本地化文本
Text("welcome_message")  // 从 Localizable.strings 文件中读取

// 带参数的本地化
Text("greeting", comment: "欢迎语")

// 格式化文本
let name = "张三"
Text("欢迎 %@", name)

日期和数字格式化

// 日期格式化
let date = Date()
Text(date, style: .date)           // 仅日期
Text(date, style: .time)           // 仅时间
Text(date, style: .relative)       // 相对时间
Text(date, style: .offset)         // 时间偏移
Text(date, style: .timer)          // 计时器

// 数字格式化
let number = 123456.789
Text(number, format: .number)
Text(number, format: .currency(code: "CNY"))
Text(number, format: .percent)

3.2 图片显示:Image

Image 组件介绍

Image 用于显示图片,可以从系统图标、资源文件或网络加载图片。

基本用法

// 系统图标
Image(systemName: "star.fill")

// 资源文件图片
Image("avatar")

// 网络图片 (iOS 15+)
AsyncImage(url: URL(string: "https://example.com/image.jpg")) { phase in
    switch phase {
    case .empty:
        ProgressView()  // 加载中
    case .success(let image):
        image
            .resizable()
            .aspectRatio(contentMode: .fill)
    case .failure:
        Image(systemName: "photo")  // 加载失败
    @unknown default:
        EmptyView()
    }
}

图片修饰符

Image("avatar")
    .resizable()                 // 可调整大小
    .aspectRatio(contentMode: .fit)  // 内容模式
    .frame(width: 100, height: 100)  // 设置大小
    .clipShape(Circle())         // 裁剪形状
    .overlay(                    // 叠加内容
        Circle()
            .stroke(Color.blue, lineWidth: 2)
    )
    .shadow(radius: 5)           // 阴影
    .opacity(0.8)                // 透明度

系统图标

// 系统图标
Image(systemName: "heart.fill")
    .foregroundStyle(.red)
    .font(.system(size: 24))

// 多色图标 (iOS 15+)
Image(systemName: "person.fill.badge.plus")
    .symbolRenderingMode(.multicolor)

// 可变颜色图标
Image(systemName: "star")
    .foregroundStyle(.yellow)

3.3 按钮交互:Button

Button 组件介绍

Button 用于创建可点击的按钮,支持多种样式和交互方式。

基本用法

// 基本按钮
Button("点击我") {
    print("按钮被点击了")
}

// 带图标的按钮
Button {
    print("按钮被点击了")
} label: {
    HStack {
        Image(systemName: "star.fill")
        Text("喜欢")
    }
}

// 带角色的按钮
Button("删除", role: .destructive) {
    print("删除操作")
}

按钮样式

// 边框按钮
Button("边框按钮") {
    // 操作
}
.buttonStyle(.bordered)

// 突出显示的按钮
Button("突出按钮") {
    // 操作
}
.buttonStyle(.borderedProminent)
.tint(.blue)  // 按钮颜色

// 胶囊按钮
Button("胶囊按钮") {
    // 操作
}
.buttonStyle(.borderedProminent)
.tint(.green)
.cornerRadius(20)

// 文本按钮
Button("文本按钮") {
    // 操作
}
.buttonStyle(.plain)

禁用状态

@State private var isEnabled = false

Button("禁用按钮") {
    // 操作
}
.disabled(!isEnabled)
.opacity(isEnabled ? 1.0 : 0.5)

3.4 输入控件:TextField、SecureField、TextEditor

TextField 文本输入框

@State private var text = ""

TextField("请输入文本", text: $text)
    .textFieldStyle(.roundedBorder)  // 边框样式
    .padding()                      // 内边距
    .keyboardType(.default)         // 键盘类型
    .autocapitalization(.sentences) // 自动大写
    .autocorrectionDisabled(true)   // 禁用自动纠正

// 带提示的 TextField
TextField(
    "请输入用户名",
    text: $text,
    prompt: Text("用户名不能为空")
        .foregroundStyle(.secondary)
)
.textFieldStyle(.roundedBorder)

SecureField 安全输入框

@State private var password = ""

SecureField("请输入密码", text: $password)
    .textFieldStyle(.roundedBorder)
    .padding()

// 带可见性切换的密码输入
@State private var showPassword = false

ZStack(alignment: .trailing) {
    if showPassword {
        TextField("请输入密码", text: $password)
    } else {
        SecureField("请输入密码", text: $password)
    }
    Button(action: {
        showPassword.toggle()
    }) {
        Image(systemName: showPassword ? "eye.slash.fill" : "eye.fill")
            .foregroundStyle(.secondary)
            .padding(.trailing, 8)
    }
}
.textFieldStyle(.roundedBorder)
.padding()

TextEditor 多行文本编辑器

@State private var message = ""

TextEditor(text: $message)
    .frame(height: 150)           // 设置高度
    .border(Color.gray.opacity(0.3), width: 1)  // 边框
    .cornerRadius(8)              // 圆角
    .padding()
    .foregroundStyle(.primary)    // 文本颜色

// 带占位符的 TextEditor
ZStack(alignment: .topLeading) {
    TextEditor(text: $message)
        .frame(height: 150)
        .padding(8)
    
    if message.isEmpty {
        Text("请输入消息...")
            .foregroundStyle(.secondary)
            .padding(10)
            .allowsHitTesting(false)  // 允许点击穿透
    }
}
.border(Color.gray.opacity(0.3), width: 1)
.cornerRadius(8)
.padding()

3.5 开关与选择:Toggle、Picker、Slider、Stepper

Toggle 开关

@State private var isEnabled = false

Toggle("启用功能", isOn: $isEnabled)
    .toggleStyle(.switch)  // 开关样式
    .padding()

// 带图标的 Toggle
Toggle(isOn: $isEnabled) {
    HStack {
        Image(systemName: "bell.fill")
        Text("接收通知")
    }
}
.toggleStyle(.switch)
.padding()

Picker 选择器

@State private var selectedOption = "选项1"
let options = ["选项1", "选项2", "选项3"]

// 分段控件样式
Picker("选择", selection: $selectedOption) {
    ForEach(options, id: \.self) {
        Text($0)
    }
}
.pickerStyle(.segmented)
.padding()

// 菜单样式
Picker("选择", selection: $selectedOption) {
    ForEach(options, id: \.self) {
        Text($0)
    }
}
.pickerStyle(.menu)
.padding()

// 轮盘样式(iOS 14+)
@State private var selectedColor = Color.red
let colors: [Color] = [.red, .green, .blue, .yellow]

Picker("颜色", selection: $selectedColor) {
    ForEach(colors, id: \.self) {
        ColorPickerView(color: $0)
    }
}
.pickerStyle(.wheel)
.frame(height: 200)
.padding()

// 辅助视图
struct ColorPickerView: View {
    let color: Color
    var body: some View {
        HStack {
            Rectangle()
                .fill(color)
                .frame(width: 20, height: 20)
                .cornerRadius(4)
            Text(String(describing: color))
        }
    }
}

Slider 滑块

@State private var value = 0.5

Slider(value: $value, in: 0...1)
    .padding()
    .tint(.blue)  // 滑块颜色

// 带标签的滑块
Slider(
    value: $value,
    in: 0...1,
    label: { Text("亮度") },
    minimumValueLabel: { Text("暗") },
    maximumValueLabel: { Text("亮") }
)
.padding()

// 整数滑块
@State private var intValue = 5

Slider(value: Binding(
    get: { Double(intValue) },
    set: { intValue = Int($0) }
), in: 0...10, step: 1)
.padding()
Text("值:\(intValue)")

Stepper 步进器

@State private var count = 0

Stepper("数量:\(count)", value: $count)
    .padding()

// 带范围的步进器
Stepper(
    "数量:\(count)",
    value: $count,
    in: 0...10,
    step: 2
)
.padding()

// 带标签的步进器
Stepper {
    Text("数量:\(count)")
} onIncrement: {
    count += 1
    print("增加到:\(count)")
} onDecrement: {
    count -= 1
    print("减少到:\(count)")
}
.padding()

3.6 进度指示:ProgressView

不确定进度

// 基本进度指示器
ProgressView()

// 带标签的进度指示器
ProgressView("加载中...")

// 带样式的进度指示器
ProgressView("处理中...")
    .progressViewStyle(.circular)
    .tint(.blue)
    .padding()

确定进度

@State private var progress = 0.0

ProgressView("下载进度", value: progress, total: 1.0)
    .padding()

// 带百分比的进度条
ProgressView(
    value: progress,
    total: 1.0,
    label: { Text("下载进度") },
    currentValueLabel: { Text("\(Int(progress * 100))%") }
)
.padding()

// 水平进度条样式
ProgressView(value: progress, total: 1.0)
    .progressViewStyle(.linear)
    .tint(.green)
    .frame(height: 10)
    .padding()

实战:创建一个用户设置页面

需求分析

创建一个包含以下元素的用户设置页面:

  1. 个人信息区域
  2. 通知设置(开关)
  3. 主题选择(选择器)
  4. 字体大小(滑块)
  5. 清除缓存按钮
  6. 退出登录按钮

代码实现

import SwiftUI

struct SettingsView: View {
    // 状态变量
    @State private var notificationsEnabled = true
    @State private var selectedTheme = "浅色"
    @State private var fontSize = 16.0
    @State private var cacheSize = "128 MB"
    
    // 主题选项
    let themes = ["浅色", "深色", "跟随系统"]
    
    var body: some View {
        NavigationStack {
            List {
                // 个人信息区域
                Section {
                    HStack {
                        Image(systemName: "person.circle.fill")
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: 60, height: 60)
                            .foregroundStyle(.blue)
                        
                        VStack(alignment: .leading, spacing: 4) {
                            Text("张三")
                                .font(.headline)
                            Text("zhangsan@example.com")
                                .font(.subheadline)
                                .foregroundStyle(.secondary)
                        }
                        Spacer()
                        Image(systemName: "chevron.right")
                            .foregroundStyle(.secondary)
                    }
                    .padding(.vertical, 8)
                }
                
                // 通知设置
                Section("通知设置") {
                    Toggle("接收推送通知", isOn: $notificationsEnabled)
                    Toggle("声音提醒", isOn: $notificationsEnabled)
                    Toggle("振动提醒", isOn: $notificationsEnabled)
                }
                
                // 外观设置
                Section("外观设置") {
                    Picker("主题", selection: $selectedTheme) {
                        ForEach(themes, id: \.self) {
                            Text($0)
                        }
                    }
                    .pickerStyle(.menu)
                    
                    VStack(alignment: .leading, spacing: 8) {
                        Text("字体大小:\(Int(fontSize))")
                        Slider(value: $fontSize, in: 12...24, step: 1)
                            .tint(.blue)
                    }
                }
                
                // 存储设置
                Section("存储设置") {
                    HStack {
                        Text("缓存大小")
                        Spacer()
                        Text(cacheSize)
                            .foregroundStyle(.secondary)
                    }
                    Button("清除缓存") {
                        // 清除缓存逻辑
                        print("清除缓存")
                    }
                    .foregroundStyle(.blue)
                }
                
                // 账户设置
                Section {
                    Button("关于我们") {
                        // 关于我们逻辑
                    }
                    Button("隐私政策") {
                        // 隐私政策逻辑
                    }
                    Button("退出登录", role: .destructive) {
                        // 退出登录逻辑
                    }
                }
            }
            .navigationTitle("设置")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

#Preview {
    SettingsView()
}

代码解析

  • List 和 Section:使用列表和分组组织设置项
  • NavigationStack:提供导航功能
  • Toggle:用于开关设置
  • Picker:用于主题选择
  • Slider:用于调整字体大小
  • Button:用于操作按钮
  • HStack 和 VStack:用于布局
  • @State:用于管理视图状态

小结

本章介绍了 SwiftUI 中的基础视图组件,包括:

  • Text:文本显示,支持富文本、本地化和格式化
  • Image:图片显示,支持系统图标、资源文件和网络图片
  • Button:按钮交互,支持多种样式和角色
  • 输入控件:TextFieldSecureFieldTextEditor
  • 选择控件:TogglePickerSliderStepper
  • 进度指示:ProgressView
  • 一个完整的用户设置页面实战

通过本章的学习,你已经掌握了 SwiftUI 中最常用的基础组件,能够创建各种常见的用户界面元素。


参考资料


本内容为《SwiftUI 基础教程》第三章,欢迎关注后续更新。

3连板*ST观典:股票短期内涨幅较大,明起停牌核查

36氪获悉,*ST观典公告,公司股票短期内涨幅较大,公司就股票交易情况进行停牌核查。公司股价自2026年4月14日以来连续3个交易日涨停,鉴于公司存在重大退市风险,投资者较为关注,为维护投资者利益,公司将就股票交易情况进行核查。经向上海证券交易所申请,公司股票自2026年4月17日(星期五)开市起停牌,自披露核查公告后复牌,预计停牌时间不超过3个交易日。公司提醒广大投资者注意二级市场交易风险。

熊猫乳品:2025年归母净利润同比增长5.38%,拟10派6元

36氪获悉,熊猫乳品发布2025年业绩报告。报告显示,2025年实现营业收入8.11亿元,同比增长6.12%;归属于上市公司股东的净利润1.08亿元,同比增长5.38%;基本每股收益0.8711元。公司怕谁向全体股东每10股派发现金红利6元(含税),送红股0股(含税),以资本公积金向全体股东每10股转增0股。

Anthropico计划下周向英国银行业发布其Mythos模型

Anthropic PBC计划在未来一周内,向英国金融机构发布其备受关注的Mythos人工智能模型。这家科技公司在发现该模型是识别并可能利用网络安全漏洞的强大工具后,正逐步扩大“玻璃翼计划”。该项目旨在让特定机构提前获得这款人工智能的使用权限。Anthropic英国、爱尔兰和北欧业务负责人Pip White在接受媒体采访时表示:“这就在眼前了,会在下周内发生。正如你所预料的那样,过去一周我与英国首席执行官们的沟通非常密切。”(界面)

神火股份:股东普天工贸拟减持不超1.30%公司股份

36氪获悉,神火股份公告,持股5%以上股东商丘市普天工贸有限公司计划自本公告披露之日起15个交易日后的3个月内(即2026年5月14日至2026年8月13日),以集中竞价或大宗交易方式减持本公司股份不超过2,900万股,占公司总股本的1.30%。减持原因为自身资金安排及经营管理需要,股份来源为受让的国有股及公司实施权益分派所获得的股份。

佰维存储:股东孙静等拟合计减持不超2.00%股份

36氪获悉,佰维存储公告,股东孙静、深圳佰盛、深圳佰泰、深圳泰德盛、深圳方泰来计划减持公司股份。其中,孙静拟减持不超过470.84万股(占总股本1.00%);深圳佰盛拟减持不超过52.32万股(0.11%);深圳佰泰拟减持不超过209.26万股(0.44%);深圳泰德盛拟减持不超过73.24万股(0.16%——;深圳方泰来拟减持不超过136.02万股(0.29%)。减持方式为集中竞价和大宗交易,期间为2026年5月14日至2026年8月13日,原因均为资金需求。

合盛硅业:2025年净亏损29.91亿元,同比转亏

36氪获悉,合盛硅业发布2025年业绩报告。报告显示,期内实现营业收入204.99亿元,同比下降23.20%;归属于上市公司股东的净利润为-29.91亿元,较2024年净利润17.40亿元由盈转亏。公司2025年度拟不进行利润分配,也不进行资本公积金转增股本。

通富微电:2025年净利润12.19亿元,同比增长79.86%

36氪获悉,通富微电发布2025年业绩报告。报告显示,期内实现营业收入279.21亿元,同比增长16.92%;归属于上市公司股东的净利润为12.19亿元,同比增长79.86%。公司拟以总股本1,517,596,912股为基数,向全体股东每10股派发现金红利0.81元(含税),不送红股,不以公积金转增股本。

万科回应总裁空缺:已由董事长及高管团队分担相关职责

36氪获悉,万科A在深交所互动易平台回应了总裁职位空缺、泊寓业务、化债等市场关注。对总裁一职的空缺,万科表示,公司总裁职位目前处于空缺状态,公司已通过董事会授权及高管分工调整,由董事长及公司高管团队分担相关职责,确保了公司经营决策的正常开展和公司治理的有效运作。万科还提到,受多重因素影响,当前公司面临着多重风险挑战,整体经营形势依然十分严峻。在各方支持下,集团全力推进改革化险工作。

永辉超市:一季度净利润2.87亿元,同比增长94.40%

36氪获悉,永辉超市发布2026年第一季度业绩报告。报告显示,一季度营业收入133.67亿元,同比减少23.53%;净利润2.87亿元,同比增长94.40%;扣非后归母净利润为2.47亿元,同比增长79.55%。此外,截至一季度末,永辉超市全国累计调改门店达327家。
❌