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
LazyVStack
和 LazyHStack
与普通堆栈类似,但会在需要时才加载内容,适合处理大量数据。
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
LazyVGrid
和 LazyHGrid
提供了网格布局,通常用于展示多列内容。
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()
动态视图生成
使用 ForEach
和 Identifiable
协议可以动态生成视图。
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 允许你创建自定义动画,通过 withAnimation
和 Animation
类型来实现。
@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/await
和 URLSession
可以方便地进行网络请求。
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. 复杂表单和数据验证
复杂表单
使用 Form
和 Section
可以创建复杂的表单,并结合 @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. 复杂导航和多窗口管理
复杂导航
使用 NavigationView
和 NavigationLink
可以实现复杂的导航结构,结合 @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 支持多窗口管理,可以使用 WindowGroup
和 DocumentGroup
创建和管理多个窗口。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandMenu("窗口") {
Button("新建窗口") {
NSApp.sendAction(#selector(NSWindow.makeKeyAndOrderFront(_:)), to: nil, from: nil)
}
}
}
}
}
版权声明:本文为原创文章,版权归 全栈开发技术博客 所有。
本文链接:https://www.lvtao.net/dev/learn-swiftui.html
转载时须注明出处及本声明