SwiftUI 从入门到放弃:全方位讲解组件、视图、高级技巧附实战示例

最近准备开发一些macOS上的小工具,于是乎整理一下丢了好久的Swift开发

SwiftUI 是苹果推出的一种声明式 UI 框架,旨在简化 iOS、macOS、watchOS 和 tvOS 应用程序的开发。通过 SwiftUI,你可以用非常少的代码创建灵活、现代的用户界面,并且与 Swift 语言无缝集成。本文将详细介绍 SwiftUI 的基本组件、布局方式,并最终构建一个实际的应用来帮助新手理解 SwiftUI 的使用。

SwiftUI 基础

1. Hello World! 示例

在 SwiftUI 中,所有的 UI 组件都被称为“视图 (View)”,并且通过 body 属性来描述界面的外观。下面是一个简单的 “Hello World!” 示例:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
            .font(.largeTitle) // 设置字体大小
            .foregroundColor(.blue) // 设置字体颜色
            .padding() // 添加内边距
    }
}

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView() // 设置主界面
        }
    }
}

2. 基本视图组件

Text 文本

Text 是 SwiftUI 中最基本的视图之一,用于显示静态文本。它支持设置字体、颜色、间距等属性。

Text("这是一个文本")
    .font(.headline) // 设置为标题样式
    .foregroundColor(.red) // 设置文本颜色为红色
Label 标签

Label 视图允许你同时显示一个图标和文本。

Label("设置", systemImage: "gearshape.fill") // 使用系统图标
    .font(.title)
TextField 文本输入框

TextField 是一个单行输入框,通常与绑定变量配合使用。

@State private var username: String = ""

TextField("请输入用户名", text: $username)
    .textFieldStyle(RoundedBorderTextFieldStyle()) // 设置圆角样式
    .padding()
TextEditor 多行文本输入框

TextEditor 用于多行文本输入。

@State private var description: String = ""

TextEditor(text: $description)
    .frame(height: 150) // 设置高度
    .border(Color.gray, width: 1)
SecureField 安全文本框

SecureField 用于输入密码等安全文本。

@State private var password: String = ""

SecureField("请输入密码", text: $password)
    .textFieldStyle(RoundedBorderTextFieldStyle())
    .padding()
Image 图片

Image 用于显示图片。你可以从系统资源中加载图像,也可以加载本地图片。

Image(systemName: "star.fill") // 使用系统图标
    .resizable() // 使图像可调整大小
    .frame(width: 50, height: 50) // 设置宽高
    .foregroundColor(.yellow) // 设置图像颜色
Button 按钮

按钮是 SwiftUI 中的交互组件,点击按钮可以执行操作。

Button(action: {
    print("按钮被点击")
}) {
    Text("点击我")
        .padding()
        .background(Color.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
}
Link 链接

Link 视图用于打开一个外部链接。

Link("访问全栈博客", destination: URL(string: "https://www.lvtao.net")!)
    .padding()
NavigationLink 导航链接

NavigationLink 用于在应用内不同视图间导航。

NavigationLink(destination: Text("详情页面")) {
    Text("点击进入详情")
}
ToolbarItem 工具栏项目

ToolbarItem 通常用于在导航栏上添加按钮或其他功能。

.toolbar {
    ToolbarItem(placement: .navigationBarTrailing) {
        Button("完成") {
            print("完成按钮被点击")
        }
    }
}
Toggle 开关

Toggle 是一个布尔型开关,用于二选一的交互。

@State private var isOn: Bool = true

Toggle("开关", isOn: $isOn)
    .padding()
Map 地图

SwiftUI 提供了 Map 组件用于显示地图(需要导入 MapKit)。

import MapKit

@State private var region = MKCoordinateRegion(
    center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
    span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)

Map(coordinateRegion: $region)
    .frame(height: 300)
Picker 选择器

Picker 用于让用户从多个选项中进行选择。

@State private var selectedColor = "红色"
let colors = ["红色", "绿色", "蓝色"]

Picker("请选择颜色", selection: $selectedColor) {
    ForEach(colors, id: \.self) {
        Text($0)
    }
}
.pickerStyle(SegmentedPickerStyle()) // 设置分段样式
DatePicker 日期选择器

DatePicker 用于选择日期和时间。

@State private var selectedDate = Date()

DatePicker("选择日期", selection: $selectedDate, displayedComponents: .date)
    .datePickerStyle(GraphicalDatePickerStyle())
ProgressView 进度视图

ProgressView 用于展示进度。

ProgressView(value: 0.7) // 进度70%
    .progressViewStyle(LinearProgressViewStyle())
Slider 滑动条

Slider 用于选择一个范围值。

@State private var value: Double = 50

Slider(value: $value, in: 0...100)
    .padding()
Stepper 步进器

Stepper 用于增加或减少数值。

@State private var stepValue: Int = 1

Stepper("数量: \(stepValue)", value: $stepValue, in: 1...10)

3. 布局容器

SwiftUI 提供了多种布局容器来组织视图。

HStack 水平堆栈

HStack 会将视图水平排列。

HStack {
    Text("左边")
    Text("右边")
}
VStack 垂直堆栈

VStack 会将视图垂直排列。

VStack {
    Text("上面")
    Text("下面")
}
ZStack 层叠堆栈

ZStack 会将视图堆叠在一起。

ZStack {
    Text("底层")
    Text("顶层")
}
LazyVStack 和 LazyHStack

LazyVStackLazyHStack 与普通堆栈类似,但会在需要时才加载内容,适合处理大量数据。

LazyVStack {
    ForEach(0..<100) { index in
        Text("项目 \(index)")
    }
}
List 列表

List 用于展示可滚动的垂直列表。

List {
    Text("项目 1")
    Text("项目 2")
}
ScrollView 滚动视图

ScrollView 用于创建可滚动的视图。

ScrollView {
    VStack {
        ForEach(0..<50) { index in
            Text("项目 \(index)")
                .padding()
        }
    }
}
LazyVGrid 和 LazyHGrid

LazyVGridLazyHGrid 提供了网格布局,通常用于展示多列内容。

let columns = [
    GridItem(.flexible()),
    GridItem(.flexible())
]

LazyVGrid(columns: columns) {
    ForEach(0..<10) { index in
        Text("项目 \(index)")
    }
}
Form 表单

Form 通常用于输入表单数据。

Form {
    TextField("姓名", text: $username)
    SecureField("密码", text: $password)
}

4. 高级功能

NavigationView 导航视图

NavigationView 提供了一个导航容器,允许你在多个视图之间切换。

NavigationView {
    List {
        NavigationLink(destination: Text("详情页")) {
            Text("项目 1")
        }
    }
    .navigationTitle("导航示例")
}
TabView 标签视图

TabView 用于创建多个页面并通过标签栏进行切换。

TabView {
    Text("首页")
        .tabItem {
            Image(systemName: "house.fill")
            Text("首页")
        }

    Text("设置")
        .tabItem {
            Image(systemName: "gearshape.fill")
            Text("设置")
        }
}
Alert 警告框

Alert 用于展示弹出式警告。

@State private var showAlert = false

Button("显示警告") {
    showAlert = true
}
.alert(isPresented: $showAlert) {
    Alert(title: Text("提示"), message: Text("这是一个警告框"), dismissButton: .default(Text("确定")))
}
Modal 模态弹窗

模态视图允许显示一个临时弹窗,遮盖当前视图。

@State private var showModal = false

Button("显示模态") {
    showModal = true
}
.sheet(isPresented: $showModal) {
    Text("这是一个模态视图")
}
ActionSheet 操作表单

ActionSheet 提供了一种弹出式的选项表。

@State private var showActionSheet = false

Button("显示操作表") {
    showActionSheet = true
}
.actionSheet(isPresented: $showActionSheet) {
    ActionSheet(
        title: Text("操作表"),
        buttons: [
            .default(Text("选项一")),
            .destructive(Text("删除")),
            .cancel()
        ]
    )
}

5. 高级使用技巧

自定义视图修饰符

SwiftUI 允许你创建自定义的视图修饰符,以便在多个视图中复用相同的样式或行为。

struct BorderedViewModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

extension View {
    func bordered() -> some View {
        self.modifier(BorderedViewModifier())
    }
}

Text("自定义修饰符")
    .bordered()
动态视图生成

使用 ForEachIdentifiable 协议可以动态生成视图。

struct Item: Identifiable {
    let id = UUID()
    let name: String
}

let items = [Item(name: "项目 1"), Item(name: "项目 2")]

List {
    ForEach(items) { item in
        Text(item.name)
    }
}
状态管理

SwiftUI 使用 @State@Binding@ObservedObject 等属性包装器来管理状态。

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

    var body: some View {
        VStack {
            Text("计数: \(count)")
            Button("增加") {
                count += 1
            }
        }
    }
}
环境对象

@EnvironmentObject 允许你在视图层次结构中共享数据。

class UserSettings: ObservableObject {
    @Published var username = "Guest"
}

struct ContentView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        Text("用户名: \(settings.username)")
    }
}

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(UserSettings())
        }
    }
}
动画

SwiftUI 提供了强大的动画支持,可以轻松创建平滑的过渡效果。

@State private var isAnimated = false

var body: some View {
    VStack {
        Button("动画") {
            withAnimation {
                isAnimated.toggle()
            }
        }
        Rectangle()
            .fill(isAnimated ? Color.red : Color.blue)
            .frame(width: isAnimated ? 100 : 200, height: isAnimated ? 100 : 200)
            .animation(.easeInOut(duration: 1.0))
    }
}

6. 实战:构建一个完整的应用

在这个部分,我们将使用上述的组件来构建一个简单的应用,该应用包括多窗口切换、导航和表单输入。

import SwiftUI

// 主界面
struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("欢迎使用我的应用")
                    .font(.largeTitle)
                    .padding()
                
                NavigationLink(destination: DetailView()) {
                    Text("进入详情页面")
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                }
            }
            .navigationTitle("首页")
        }
    }
}

// 详情页
struct DetailView: View {
    @State private var username: String = ""
    @State private var showAlert = false
    
    var body: some View {
        VStack {
            TextField("请输入用户名", text: $username)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()

            Button("提交") {
                showAlert = true
            }
            .alert(isPresented: $showAlert) {
                Alert(title: Text("提示"), message: Text("用户名: \(username)"), dismissButton: .default(Text("确定")))
            }
            .padding()
            
            Spacer()
        }
        .padding()
        .navigationTitle("详情页")
    }
}

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

奇门杂技

1. 自定义视图和组件

自定义视图

你可以通过组合现有的视图来创建自定义视图,以便在多个地方复用。

struct CustomButton: View {
    var title: String
    var action: () -> Void

    var body: some View {
        Button(action: action) {
            Text(title)
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
        }
    }
}

struct ContentView: View {
    var body: some View {
        CustomButton(title: "自定义按钮") {
            print("按钮被点击")
        }
    }
}

自定义布局

SwiftUI 允许你创建自定义布局容器,通过实现 Layout 协议来定义布局行为。

struct CustomLayout: Layout {
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        // 计算布局的大小
        return proposal.replacingUnspecifiedDimensions()
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        // 放置子视图
        for (index, subview) in subviews.enumerated() {
            let x = bounds.minX + CGFloat(index) * 100
            let y = bounds.minY
            subview.place(at: CGPoint(x: x, y: y), proposal: proposal)
        }
    }
}

struct ContentView: View {
    var body: some View {
        CustomLayout {
            Text("视图 1")
            Text("视图 2")
        }
    }
}

2. 数据流和状态管理

@StateObject 和 @ObservedObject

@StateObject@ObservedObject 用于管理视图中的复杂状态。@StateObject 适用于视图的初始化,而 @ObservedObject 适用于从外部传递的状态。

class UserSettings: ObservableObject {
    @Published var username = "Guest"
}

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

    var body: some View {
        VStack {
            Text("用户名: \(settings.username)")
            Button("更改用户名") {
                settings.username = "New User"
            }
        }
    }
}

@EnvironmentObject

@EnvironmentObject 允许你在视图层次结构中共享数据。

class UserSettings: ObservableObject {
    @Published var username = "Guest"
}

struct ContentView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        VStack {
            Text("用户名: \(settings.username)")
            Button("更改用户名") {
                settings.username = "New User"
            }
        }
    }
}

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

3. 动画和过渡

自定义动画

SwiftUI 允许你创建自定义动画,通过 withAnimationAnimation 类型来实现。

@State private var isAnimated = false

var body: some View {
    VStack {
        Button("动画") {
            withAnimation(.easeInOut(duration: 1.0)) {
                isAnimated.toggle()
            }
        }
        Rectangle()
            .fill(isAnimated ? Color.red : Color.blue)
            .frame(width: isAnimated ? 100 : 200, height: isAnimated ? 100 : 200)
    }
}

过渡效果

使用 transition 修饰符可以为视图添加过渡效果。

@State private var showDetails = false

var body: some View {
    VStack {
        Button("显示详情") {
            withAnimation {
                showDetails.toggle()
            }
        }
        if showDetails {
            Text("详情内容")
                .transition(.slide)
        }
    }
}

4. 复杂布局和自适应设计

响应式布局

SwiftUI 支持响应式布局,可以根据设备尺寸自动调整视图。

struct ContentView: View {
    var body: some View {
        GeometryReader { geometry in
            HStack {
                Text("左边")
                    .frame(width: geometry.size.width * 0.5)
                Text("右边")
                    .frame(width: geometry.size.width * 0.5)
            }
        }
    }
}

自适应设计

使用 @Environment@EnvironmentObject 可以根据环境变量(如设备类型、方向等)调整视图。

struct ContentView: View {
    @Environment(\.horizontalSizeClass) var sizeClass

    var body: some View {
        if sizeClass == .compact {
            VStack {
                Text("紧凑布局")
            }
        } else {
            HStack {
                Text("常规布局")
            }
        }
    }
}

5. 数据绑定和 MVVM 模式

MVVM 模式

SwiftUI 非常适合使用 MVVM(Model-View-ViewModel)模式来组织代码。ViewModel 负责处理业务逻辑,而视图负责展示数据。

class UserViewModel: ObservableObject {
    @Published var username = "Guest"

    func updateUsername() {
        username = "New User"
    }
}

struct ContentView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        VStack {
            Text("用户名: \(viewModel.username)")
            Button("更改用户名") {
                viewModel.updateUsername()
            }
        }
    }
}

6. 网络请求和异步操作

异步网络请求

使用 async/awaitURLSession 可以方便地进行网络请求。

struct User: Codable {
    let userId: Int
    let userName: String
}

class UserViewModel: ObservableObject {
    @Published var user: User?

    func fetchUser() async {
        guard let url = URL(string: "https://tool.lvtao.net/scui/token") else { return }
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            user = try JSONDecoder().decode(User.self, from: data)
        } catch {
            print("请求失败: \(error)")
        }
    }
}

struct ContentView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        VStack {
            if let user = viewModel.user {
                Text("用户名: \(user.name)")
            } else {
                Text("加载中...")
            }
            Button("获取用户") {
                Task {
                    await viewModel.fetchUser()
                }
            }
        }
    }
}

7. 复杂交互和手势

手势识别

SwiftUI 支持多种手势识别,如点击、拖动、缩放等。

struct ContentView: View {
    @State private var offset = CGSize.zero

    var body: some View {
        Rectangle()
            .frame(width: 100, height: 100)
            .offset(offset)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        offset = value.translation
                    }
                    .onEnded { _ in
                        withAnimation {
                            offset = .zero
                        }
                    }
            )
    }
}

复杂交互

结合多个手势和状态管理,可以实现复杂的交互效果。

struct ContentView: View {
    @State private var isZoomed = false
    @State private var scale: CGFloat = 1.0

    var body: some View {
        Rectangle()
            .frame(width: 100, height: 100)
            .scaleEffect(scale)
            .gesture(
                TapGesture()
                    .onEnded {
                        withAnimation {
                            isZoomed.toggle()
                            scale = isZoomed ? 2.0 : 1.0
                        }
                    }
            )
    }
}

8. 复杂动画和过渡

组合动画

SwiftUI 允许你组合多个动画效果,创建复杂的动画序列。

@State private var isAnimated = false

var body: some View {
    VStack {
        Button("动画") {
            withAnimation(.easeInOut(duration: 1.0)) {
                isAnimated.toggle()
            }
        }
        Rectangle()
            .fill(isAnimated ? Color.red : Color.blue)
            .frame(width: isAnimated ? 100 : 200, height: isAnimated ? 100 : 200)
            .rotationEffect(Angle(degrees: isAnimated ? 180 : 0))
            .animation(.easeInOut(duration: 1.0), value: isAnimated)
    }
}

过渡动画

使用 transition 修饰符可以为视图添加过渡效果,并结合动画实现复杂的过渡效果。

@State private var showDetails = false

var body: some View {
    VStack {
        Button("显示详情") {
            withAnimation {
                showDetails.toggle()
            }
        }
        if showDetails {
            Text("详情内容")
                .transition(.asymmetric(insertion: .slide, removal: .opacity))
        }
    }
}

9. 复杂表单和数据验证

复杂表单

使用 FormSection 可以创建复杂的表单,并结合 @State@Binding 管理表单数据。

struct ContentView: View {
    @State private var username = ""
    @State private var password = ""
    @State private var isFormValid = false

    var body: some View {
        Form {
            Section(header: Text("用户信息")) {
                TextField("用户名", text: $username)
                SecureField("密码", text: $password)
            }
            Section {
                Button("提交") {
                    isFormValid = !username.isEmpty && !password.isEmpty
                }
            }
        }
        .alert(isPresented: $isFormValid) {
            Alert(title: Text("提示"), message: Text("表单已提交"), dismissButton: .default(Text("确定")))
        }
    }
}

数据验证

结合 @State@Binding,可以实现表单数据的验证和错误提示。

struct ContentView: View {
    @State private var username = ""
    @State private var password = ""
    @State private var showError = false

    var body: some View {
        Form {
            Section(header: Text("用户信息")) {
                TextField("用户名", text: $username)
                    .onChange(of: username) { _ in
                        validateForm()
                    }
                SecureField("密码", text: $password)
                    .onChange(of: password) { _ in
                        validateForm()
                    }
            }
            Section {
                Button("提交") {
                    validateForm()
                }
            }
        }
        .alert(isPresented: $showError) {
            Alert(title: Text("错误"), message: Text("用户名或密码不能为空"), dismissButton: .default(Text("确定")))
        }
    }

    func validateForm() {
        showError = username.isEmpty || password.isEmpty
    }
}

10. 复杂导航和多窗口管理

复杂导航

使用 NavigationViewNavigationLink 可以实现复杂的导航结构,结合 @State@Binding 管理导航状态。

struct ContentView: View {
    @State private var isNavigating = false

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView(), isActive: $isNavigating) {
                    Text("进入详情页面")
                }
                Button("导航") {
                    isNavigating = true
                }
            }
            .navigationTitle("首页")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("详情页面")
            .navigationTitle("详情")
    }
}

多窗口管理

在 macOS 和 iPadOS 上,SwiftUI 支持多窗口管理,可以使用 WindowGroupDocumentGroup 创建和管理多个窗口。

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandMenu("窗口") {
                Button("新建窗口") {
                    NSApp.sendAction(#selector(NSWindow.makeKeyAndOrderFront(_:)), to: nil, from: nil)
                }
            }
        }
    }
}

标签: macOS, SwiftUI

相关文章

macOS下使用UTM安装Alpine Linux虚拟机

在macOS下使用UTM(Universal Turing Machine)安装Alpine Linux虚拟机是一个相对简单的过程,但需要一些基本的配置和步骤。以下是详细的指南,涵盖了从安装UT...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件