MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift UI框架快速上手

2024-05-115.7k 阅读

1. SwiftUI 框架基础

1.1 什么是 SwiftUI

SwiftUI 是苹果公司在 2019 年 WWDC 上推出的用于构建用户界面的全新框架。它基于 Swift 语言,采用声明式的编程方式,大大简化了界面开发流程。与传统的 UIKit 相比,SwiftUI 具有更简洁的语法、更高效的开发效率以及更好的跨平台适应性。

声明式编程意味着开发者只需描述界面“是什么样子”,而无需像命令式编程那样详细描述“如何去创建”。例如,在 SwiftUI 中创建一个简单的文本视图,只需这样写:

Text("Hello, SwiftUI!")

而在 UIKit 中,则需要创建 UILabel 对象,设置其文本属性、字体、位置等一系列操作:

let label = UILabel()
label.text = "Hello, UIKit!"
label.font = UIFont.systemFont(ofSize: 17)
label.frame = CGRect(x: 100, y: 100, width: 200, height: 30)
view.addSubview(label)

1.2 视图、视图修饰符和容器视图

1.2.1 视图

视图是 SwiftUI 中构建界面的基本元素。像前面提到的 Text 就是一种视图,用于显示文本。还有 Image 视图用于展示图片,Button 视图用于创建可点击的按钮等。 例如,显示一张图片:

Image("myImage")

这里假设项目中已经添加了名为“myImage”的图片资源。

1.2.2 视图修饰符

视图修饰符用于对视图进行外观、行为等方面的修改。它们以方法调用的形式链式添加到视图后面。比如改变 Text 视图的字体大小和颜色:

Text("Hello, SwiftUI!")
   .font(.largeTitle)
   .foregroundColor(.red)

.font(.largeTitle) 是将字体设置为大标题样式,.foregroundColor(.red) 则是将文本颜色设为红色。

1.2.3 容器视图

容器视图可以包含多个子视图,从而实现更复杂的界面布局。常见的容器视图有 HStack(水平排列子视图)、VStack(垂直排列子视图)和 ZStack(重叠排列子视图)。 例如,使用 HStack 水平排列一个文本和一个按钮:

HStack {
    Text("Click the button:")
    Button("Tap me") {
        // 按钮点击的逻辑代码
        print("Button tapped!")
    }
}

1.3 构建简单界面示例

下面我们通过一个完整的示例来展示如何使用 SwiftUI 构建一个简单的界面。假设我们要创建一个包含标题、图片和描述文本的界面。

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("My SwiftUI App")
               .font(.largeTitle)
               .foregroundColor(.blue)
            Image("myImage")
               .resizable()
               .aspectRatio(contentMode:.fit)
            Text("This is a simple description of my app.")
               .font(.body)
               .padding()
        }
    }
}

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

在这个示例中,ContentView 结构体遵循 View 协议,body 属性返回整个界面的视图结构。VStack 垂直排列了一个标题文本、一张图片和一段描述文本。图片使用 .resizable() 使其可缩放,并通过 .aspectRatio(contentMode:.fit) 保持图片的纵横比并适应容器大小。Text 视图通过不同的修饰符设置字体、颜色和内边距。ContentView_Previews 用于在 Xcode 的预览窗口中实时查看界面效果。

2. 布局和约束

2.1 间距和对齐方式

2.1.1 间距

在容器视图中,可以通过 .spacing 修饰符来设置子视图之间的间距。例如,在 VStack 中增加子视图之间的间距:

VStack(spacing: 20) {
    Text("First item")
    Text("Second item")
    Text("Third item")
}

这里 spacing: 20 表示子视图之间的垂直间距为 20 个点。

2.1.2 对齐方式

容器视图有多种对齐方式。对于 HStack,默认是垂直居中对齐,可通过 .alignment 修饰符改变,比如使其顶部对齐:

HStack(alignment:.top) {
    Text("Short text")
    Text("A longer text that should be aligned at the top with the short one.")
}

对于 VStack,默认是水平居中对齐,同样可以修改,例如使其左对齐:

VStack(alignment:.leading) {
    Text("First line")
    Text("Second line")
}

2.2 灵活尺寸和优先级

2.2.1 灵活尺寸

SwiftUI 支持灵活的视图尺寸设置。例如,Spacer 视图可以占用剩余的空间。在 HStack 中使用 Spacer 使两个按钮分别靠左右两侧:

HStack {
    Button("Left Button") { }
    Spacer()
    Button("Right Button") { }
}

2.2.2 优先级

当多个视图对空间有不同需求时,可以通过设置优先级来决定如何分配空间。例如,有一个文本视图和一个按钮,希望文本视图尽量占用空间,但按钮也有最小尺寸:

struct PriorityExample: View {
    var body: some View {
        HStack {
            Text("This is a long text that should take as much space as possible.")
               .lineLimit(nil)
               .priority(.high)
            Button("Button") { }
               .frame(minWidth: 80)
        }
    }
}

这里 Text 视图通过 .priority(.high) 设置了高优先级,并且通过 .lineLimit(nil) 允许文本自动换行。按钮设置了最小宽度为 80 点。

2.3 安全区域和边距

2.3.1 安全区域

在 iOS 设备上,有些区域可能会被系统元素(如状态栏、导航栏、底部指示器等)遮挡。SwiftUI 提供了安全区域的概念,确保视图不会显示在这些被遮挡的区域。可以使用 safeAreaInsets 来获取安全区域的内边距。 例如,在一个视图中根据安全区域调整布局:

struct SafeAreaExample: View {
    var body: some View {
        VStack {
            Text("Top content")
            Spacer()
            Text("Bottom content")
        }
       .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top?? 0)
       .padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom?? 0)
    }
}

这里通过 UIApplication.shared.windows.first?.safeAreaInsets.top 获取顶部安全区域内边距,通过 UIApplication.shared.windows.first?.safeAreaInsets.bottom 获取底部安全区域内边距,并将其应用到视图的 padding 上。

2.3.2 边距

除了安全区域内边距,还可以使用 padding 修饰符为视图添加自定义边距。padding 可以接受不同的参数形式,如 padding() 表示四个方向都添加默认边距,padding(10) 表示四个方向都添加 10 点的边距,padding(.top, 20) 表示仅在顶部添加 20 点边距等。

Text("With padding")
   .padding(.all, 15)
   .background(Color.gray)

这里为 Text 视图在四个方向都添加了 15 点的边距,并设置了灰色的背景。

3. 数据绑定和响应式编程

3.1 @State 和 @Binding

3.1.1 @State

@State 是 SwiftUI 中用于表示视图内部可变状态的属性包装器。例如,创建一个按钮,点击按钮改变文本内容:

struct StateExample: View {
    @State private var text = "Initial text"

    var body: some View {
        VStack {
            Text(text)
            Button("Change text") {
                self.text = "New text"
            }
        }
    }
}

在这个例子中,@State 修饰的 text 属性用于存储文本内容,按钮点击时修改 text 的值,视图会自动重新渲染以显示新的文本。

3.1.2 @Binding

@Binding 用于在视图之间传递可变状态。当一个子视图需要修改父视图的状态时,可以使用 @Binding。例如,创建一个子视图用于增加计数器的值:

struct CounterView: View {
    @Binding var count: Int

    var body: some View {
        Button("Increment") {
            self.count += 1
        }
    }
}

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

    var body: some View {
        VStack {
            Text("Count: \(counter)")
            CounterView(count: $counter)
        }
    }
}

ParentView 中,@State 修饰的 counter 是父视图的状态。在 CounterView 中,@Binding 修饰的 count 接受来自父视图 counter 的绑定(通过 $counter 传递),子视图中的按钮点击可以修改父视图的 counter 值。

3.2 响应式编程基础

SwiftUI 基于响应式编程模型,视图会根据数据的变化自动更新。除了 @State@Binding,还可以使用 ObservableObject@ObservedObject 来实现更复杂的响应式逻辑。

3.2.1 ObservableObject

ObservableObject 是一个协议,遵循该协议的类用于表示可观察的对象,其属性的变化会通知到依赖它的视图。例如,创建一个计数器的可观察对象:

import Combine

class Counter: ObservableObject {
    @Published var count = 0

    func increment() {
        self.count += 1
    }
}

这里 Counter 类遵循 ObservableObject 协议,@Published 修饰的 count 属性表示当该属性值变化时会发布通知。increment 方法用于增加计数器的值。

3.2.2 @ObservedObject

@ObservedObject 用于在视图中观察 ObservableObject 的变化。例如,使用上面的 Counter 类在视图中显示计数器:

struct ObservedObjectExample: View {
    @ObservedObject var counter = Counter()

    var body: some View {
        VStack {
            Text("Count: \(counter.count)")
            Button("Increment") {
                self.counter.increment()
            }
        }
    }
}

ObservedObjectExample 视图中,@ObservedObject 修饰的 counter 观察 Counter 对象的变化,当 counter.count 值改变时,视图会自动更新。

4. 导航和页面管理

4.1 NavigationView

NavigationView 是 SwiftUI 中用于实现导航功能的容器视图。它通常包含一个 NavigationBar,用于显示标题和导航按钮。

例如,创建一个简单的导航界面,包含一个列表和详情页面:

struct ListView: View {
    var items = ["Item 1", "Item 2", "Item 3"]

    var body: some View {
        NavigationView {
            List(items, id: \.self) { item in
                NavigationLink(destination: DetailView(item: item)) {
                    Text(item)
                }
            }
           .navigationTitle("Items List")
        }
    }
}

struct DetailView: View {
    var item: String

    var body: some View {
        Text("Detail of \(item)")
           .navigationTitle(item)
    }
}

ListView 中,NavigationView 包裹了一个 ListList 中的每一项都是一个 NavigationLink,点击后导航到 DetailViewNavigationLinkdestination 属性指定目标视图,Text 作为导航项的显示内容。navigationTitle 用于设置导航栏的标题。

4.2 TabView

TabView 用于创建底部选项卡式的界面,用户可以通过点击选项卡在不同的视图之间切换。

例如,创建一个包含三个选项卡的界面:

struct TabViewExample: View {
    var body: some View {
        TabView {
            Text("First tab")
               .tabItem {
                    Image(systemName: "house")
                    Text("Home")
                }
            Text("Second tab")
               .tabItem {
                    Image(systemName: "person")
                    Text("Profile")
                }
            Text("Third tab")
               .tabItem {
                    Image(systemName: "gear")
                    Text("Settings")
                }
        }
    }
}

TabViewExample 中,每个子视图通过 .tabItem 修饰符设置选项卡的图标和标题。Image(systemName: "xxx") 用于显示系统提供的图标,Text("xxx") 是选项卡的文字标题。

4.3 PageView

虽然 SwiftUI 没有直接提供 PageView,但可以通过 TabView 结合 PageTabViewStyle 来实现类似分页视图的效果。

例如,创建一个简单的分页视图:

struct PageViewExample: View {
    var body: some View {
        TabView {
            Text("Page 1")
            Text("Page 2")
            Text("Page 3")
        }
       .tabViewStyle(PageTabViewStyle())
    }
}

这里通过 tabViewStyle(PageTabViewStyle())TabView 的样式设置为分页样式,用户可以通过左右滑动来切换页面。

5. 动画和过渡效果

5.1 基本动画

5.1.1 隐式动画

SwiftUI 支持隐式动画,即当视图的属性发生变化时,自动添加动画效果。例如,点击按钮改变文本颜色并添加动画:

struct ImplicitAnimationExample: View {
    @State private var isRed = false

    var body: some View {
        VStack {
            Text("Animated Text")
               .foregroundColor(isRed? Color.red : Color.blue)
               .animation(.easeInOut(duration: 1))
            Button("Change Color") {
                self.isRed.toggle()
            }
        }
    }
}

在这个例子中,Text 视图的 foregroundColor 根据 isRed 的值变化,.animation(.easeInOut(duration: 1)) 为颜色变化添加了一个时长为 1 秒,缓动效果为 easeInOut 的动画。

5.1.2 显式动画

显式动画通过 withAnimation 块来实现。例如,在按钮点击时同时改变文本颜色和字体大小:

struct ExplicitAnimationExample: View {
    @State private var isBig = false

    var body: some View {
        VStack {
            Text("Animated Text")
               .foregroundColor(isBig? Color.green : Color.orange)
               .font(isBig?.largeTitle :.body)
            Button("Animate") {
                withAnimation(.easeInOut(duration: 1.5)) {
                    self.isBig.toggle()
                }
            }
        }
    }
}

withAnimation 块中,isBig 的切换操作会触发 Text 视图颜色和字体大小的动画变化,动画时长为 1.5 秒,缓动效果为 easeInOut

5.2 过渡效果

5.2.1 视图过渡

SwiftUI 提供了多种视图过渡效果,如淡入淡出、滑动、缩放等。例如,在两个视图之间切换时添加淡入淡出过渡:

struct TransitionExample: View {
    @State private var showFirst = true

    var body: some View {
        VStack {
            if showFirst {
                Text("First View")
                   .transition(.opacity)
            } else {
                Text("Second View")
                   .transition(.opacity)
            }
            Button("Switch Views") {
                withAnimation {
                    self.showFirst.toggle()
                }
            }
        }
    }
}

这里通过 .transition(.opacity) 为视图切换添加了淡入淡出的过渡效果。

5.2.2 容器视图过渡

对于容器视图,也可以添加过渡效果。例如,在 VStack 中添加子视图时使用滑动过渡:

struct ContainerTransitionExample: View {
    @State private var showItem = false

    var body: some View {
        VStack {
            if showItem {
                Text("New Item")
                   .transition(.slide)
            }
            Button("Add Item") {
                withAnimation {
                    self.showItem.toggle()
                }
            }
        }
    }
}

showItemtrue 时,Text 视图以滑动的过渡效果出现。

6. 与 UIKit 和 AppKit 的集成

6.1 在 SwiftUI 中使用 UIKit 视图

有时候可能需要在 SwiftUI 项目中使用一些 UIKit 视图,SwiftUI 提供了 UIViewRepresentable 协议来实现这个目的。

例如,将 UIImageView 集成到 SwiftUI 中:

import UIKit
import SwiftUI

struct ImageViewRepresentable: UIViewRepresentable {
    let image: UIImage

    func makeUIView(context: Context) -> UIImageView {
        return UIImageView()
    }

    func updateUIView(_ uiView: UIImageView, context: Context) {
        uiView.image = image
    }
}

struct UIKitIntegrationExample: View {
    let myImage = UIImage(named: "myImage")

    var body: some View {
        VStack {
            ImageViewRepresentable(image: myImage!)
            Text("Image from UIKit")
        }
    }
}

在这个例子中,ImageViewRepresentable 结构体遵循 UIViewRepresentable 协议,makeUIView 方法创建 UIImageView 实例,updateUIView 方法设置图片。在 UIKitIntegrationExample 视图中使用 ImageViewRepresentable 来显示 UIKit 的图片视图。

6.2 在 UIKit 中使用 SwiftUI 视图

在 UIKit 项目中也可以嵌入 SwiftUI 视图,通过 UIHostingController 来实现。

例如,在 UIKit 的视图控制器中显示一个 SwiftUI 视图:

import UIKit
import SwiftUI

struct SwiftUIView: View {
    var body: some View {
        Text("SwiftUI in UIKit")
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let swiftUIView = SwiftUIView()
        let hostingController = UIHostingController(rootView: swiftUIView)
        addChild(hostingController)
        view.addSubview(hostingController.view)
        hostingController.view.frame = view.bounds
        hostingController.didMove(toParent: self)
    }
}

这里创建了一个简单的 SwiftUI 视图 SwiftUIView,在 UIKit 的 ViewController 中通过 UIHostingController 将其嵌入到视图中。

通过以上内容,相信你对 SwiftUI 框架已经有了较为深入的了解,可以快速上手进行开发,并根据实际需求构建出功能丰富、美观的用户界面。无论是从基础的视图构建,到复杂的布局、数据绑定、导航,还是动画效果和与其他框架的集成,SwiftUI 都提供了强大且便捷的工具和方法。