活动公告

系统通知
06-22 18:10
系统通知
06-14 00:00
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

SwiftUI UI设计实战教程轻松掌握现代苹果应用界面开发技巧

SunJu_FaceMall

3万

主题

3142

科技点

3万

积分

执行版主

碾压王

积分
32876

塔罗立华奏

执行版主 发表于 2025-9-10 23:20:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

SwiftUI是苹果在2019年推出的现代化UI框架,它彻底改变了iOS、macOS、watchOS和tvOS应用的开发方式。与传统的UIKit相比,SwiftUI采用了声明式编程范式,使开发者能够用更少的代码创建更美观、更响应式的用户界面。本教程将带您从零开始,逐步掌握SwiftUI的核心概念和实用技巧,帮助您轻松构建现代化的苹果应用界面。

SwiftUI的主要优势包括:

• 代码简洁:比UIKit减少约50%-70%的代码量
• 跨平台:同一套代码可运行在多个苹果平台上
• 实时预览:Xcode提供实时界面预览功能
• 声明式语法:更直观、更易理解和维护
• 自动适应:界面自动适应不同屏幕尺寸和方向

SwiftUI基础

基本语法结构

SwiftUI的基本语法结构非常简洁。一个最简单的SwiftUI视图如下所示:
  1. import SwiftUI
  2. struct ContentView: View {
  3.     var body: some View {
  4.         Text("Hello, SwiftUI!")
  5.     }
  6. }
  7. struct ContentView_Previews: PreviewProvider {
  8.     static var previews: some View {
  9.         ContentView()
  10.     }
  11. }
复制代码

在这个例子中:

• ContentView是一个遵循View协议的结构体
• body属性返回一个描述视图内容的视图层次结构
• ContentView_Previews提供Xcode预览功能

视图修饰器

视图修饰器是SwiftUI中用于修改视图外观和行为的函数。它们可以通过链式调用来组合多个修饰效果:
  1. Text("Hello, SwiftUI!")
  2.     .font(.title)
  3.     .fontWeight(.bold)
  4.     .foregroundColor(.blue)
  5.     .padding()
  6.     .background(Color.yellow)
  7.     .cornerRadius(10)
复制代码

这个例子中,我们应用了多个修饰器来改变文本的字体、粗细、颜色、内边距、背景和圆角。

常用UI组件

文本显示

SwiftUI中的Text视图用于显示静态或动态文本:
  1. // 静态文本
  2. Text("Hello, World!")
  3. // 动态文本
  4. let name = "John"
  5. Text("Hello, \(name)!")
  6. // 多行文本
  7. Text("This is a long text that will wrap across multiple lines automatically when it reaches the edge of the screen.")
  8.     .lineLimit(nil) // 无限行数
  9.     .multilineTextAlignment(.center) // 居中对齐
复制代码

图像显示

Image视图用于显示图片:
  1. // 显示系统图标
  2. Image(systemName: "star.fill")
  3.     .font(.largeTitle)
  4.     .foregroundColor(.yellow)
  5. // 显示应用中的图片
  6. Image("myImage")
  7.     .resizable() // 使图像可调整大小
  8.     .aspectRatio(contentMode: .fit) // 保持宽高比
  9.     .frame(width: 200, height: 200) // 设置框架大小
  10. // 从网络加载图片(需要URLImage库或AsyncImage)
  11. AsyncImage(url: URL(string: "https://example.com/image.jpg"))
  12.     .frame(width: 200, height: 200)
  13.     .clipShape(Circle()) // 裁剪为圆形
复制代码

按钮交互

Button视图用于创建可交互的按钮:
  1. // 基本按钮
  2. Button(action: {
  3.     print("Button tapped!")
  4. }) {
  5.     Text("Click Me")
  6.         .padding()
  7.         .background(Color.blue)
  8.         .foregroundColor(.white)
  9.         .cornerRadius(10)
  10. }
  11. // 按钮样式
  12. Button(action: {
  13.     // 按钮动作
  14. }) {
  15.     Label("Start", systemImage: "play.fill")
  16. }
  17. .buttonStyle(.borderedProminent)
  18. .controlSize(.large)
复制代码

输入控件

SwiftUI提供了多种输入控件,如文本框、滑块、开关等:
  1. // 文本框
  2. @State private var username: String = ""
  3. TextField("Enter your name", text: $username)
  4.     .textFieldStyle(RoundedBorderTextFieldStyle())
  5.     .padding()
  6. // 安全文本框(密码输入)
  7. @State private var password: String = ""
  8. SecureField("Enter password", text: $password)
  9.     .textFieldStyle(RoundedBorderTextFieldStyle())
  10.     .padding()
  11. // 滑块
  12. @State private var sliderValue: Double = 0.5
  13. Slider(value: $sliderValue, in: 0...1) {
  14.     Text("Value")
  15. }
  16. .padding()
  17. // 开关
  18. @State private var isNotificationEnabled: Bool = true
  19. Toggle(isOn: $isNotificationEnabled) {
  20.     Text("Enable Notifications")
  21. }
  22. .padding()
  23. // 选择器
  24. @State private var selectedColor = "Red"
  25. Picker("Select a color", selection: $selectedColor) {
  26.     Text("Red").tag("Red")
  27.     Text("Green").tag("Green")
  28.     Text("Blue").tag("Blue")
  29. }
  30. .pickerStyle(.segmented)
  31. .padding()
复制代码

布局系统

垂直和水平布局

SwiftUI提供了三种基本的布局容器:VStack(垂直堆栈)、HStack(水平堆栈)和ZStack(层叠堆栈)。
  1. // 垂直布局
  2. VStack(alignment: .leading, spacing: 20) {
  3.     Text("Title")
  4.         .font(.title)
  5.     Text("Subtitle")
  6.         .font(.subheadline)
  7.         .foregroundColor(.gray)
  8.     Text("This is the main content of the view. It can be longer and will wrap to multiple lines.")
  9. }
  10. .padding()
  11. // 水平布局
  12. HStack(spacing: 15) {
  13.     Image(systemName: "person.circle.fill")
  14.         .font(.largeTitle)
  15.         .foregroundColor(.blue)
  16.    
  17.     VStack(alignment: .leading) {
  18.         Text("John Doe")
  19.             .font(.headline)
  20.         Text("Software Engineer")
  21.             .font(.subheadline)
  22.             .foregroundColor(.secondary)
  23.     }
  24.    
  25.     Spacer() // 占据可用空间,将内容推到左侧
  26. }
  27. .padding()
  28. // 层叠布局
  29. ZStack {
  30.     // 背景图片
  31.     Image("background")
  32.         .resizable()
  33.         .aspectRatio(contentMode: .fill)
  34.    
  35.     // 半透明遮罩
  36.     Color.black
  37.         .opacity(0.4)
  38.    
  39.     // 前景内容
  40.     VStack {
  41.         Text("Welcome")
  42.             .font(.largeTitle)
  43.             .fontWeight(.bold)
  44.             .foregroundColor(.white)
  45.         
  46.         Text("Please sign in to continue")
  47.             .font(.subheadline)
  48.             .foregroundColor(.white)
  49.     }
  50. }
  51. .frame(height: 200)
复制代码

灵活布局

SwiftUI提供了Spacer和Divider等视图来帮助创建灵活的布局:
  1. HStack {
  2.     Text("Left")
  3.    
  4.     Spacer() // 占据中间的可用空间
  5.    
  6.     Text("Right")
  7. }
  8. .frame(height: 50)
  9. VStack {
  10.     Text("Top")
  11.    
  12.     Divider() // 水平分隔线
  13.    
  14.     Text("Bottom")
  15. }
  16. .frame(height: 200)
复制代码

条件布局

使用if语句和Group可以根据条件显示不同的视图:
  1. @State private var isLoggedIn = false
  2. VStack {
  3.     if isLoggedIn {
  4.         Text("Welcome back!")
  5.         Button("Log Out") {
  6.             isLoggedIn = false
  7.         }
  8.     } else {
  9.         Text("Please log in")
  10.         Button("Log In") {
  11.             isLoggedIn = true
  12.         }
  13.     }
  14. }
  15. .padding()
复制代码

自定义布局容器

SwiftUI允许您创建自定义的布局容器:
  1. struct GridStack<Content: View>: View {
  2.     let rows: Int
  3.     let columns: Int
  4.     let content: (Int, Int) -> Content
  5.    
  6.     var body: some View {
  7.         VStack {
  8.             ForEach(0..<rows, id: \.self) { row in
  9.                 HStack {
  10.                     ForEach(0..<columns, id: \.self) { column in
  11.                         content(row, column)
  12.                     }
  13.                 }
  14.             }
  15.         }
  16.     }
  17. }
  18. // 使用自定义网格布局
  19. GridStack(rows: 3, columns: 3) { row, col in
  20.     Text("R\(row)C\(col)")
  21.         .frame(width: 50, height: 50)
  22.         .background(Color.secondary)
  23.         .cornerRadius(8)
  24. }
复制代码

状态管理

@State属性包装器

@State用于管理视图的本地状态:
  1. struct CounterView: View {
  2.     @State private var count = 0
  3.    
  4.     var body: some View {
  5.         VStack {
  6.             Text("Count: \(count)")
  7.                 .font(.title)
  8.             
  9.             Button("Increment") {
  10.                 count += 1
  11.             }
  12.             .padding()
  13.             .background(Color.blue)
  14.             .foregroundColor(.white)
  15.             .cornerRadius(10)
  16.         }
  17.     }
  18. }
复制代码

@Binding属性包装器

@Binding用于创建对状态的引用,允许子视图修改父视图的状态:
  1. struct ToggleView: View {
  2.     @Binding var isOn: Bool
  3.    
  4.     var body: some View {
  5.         Button(action: {
  6.             isOn.toggle()
  7.         }) {
  8.             Text(isOn ? "On" : "Off")
  9.                 .padding()
  10.                 .background(isOn ? Color.green : Color.red)
  11.                 .foregroundColor(.white)
  12.                 .cornerRadius(10)
  13.         }
  14.     }
  15. }
  16. struct ParentView: View {
  17.     @State private var switchState = false
  18.    
  19.     var body: some View {
  20.         VStack {
  21.             Text("Switch is \(switchState ? "On" : "Off")")
  22.             
  23.             ToggleView(isOn: $switchState)
  24.         }
  25.     }
  26. }
复制代码

@ObservedObject和@StateObject

@ObservedObject和@StateObject用于观察符合ObservableObject协议的对象:
  1. class TimerModel: ObservableObject {
  2.     @Published var counter = 0
  3.     var timer: Timer?
  4.    
  5.     init() {
  6.         timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
  7.             self.counter += 1
  8.         }
  9.     }
  10.    
  11.     deinit {
  12.         timer?.invalidate()
  13.     }
  14. }
  15. struct TimerView: View {
  16.     @StateObject private var timer = TimerModel()
  17.    
  18.     var body: some View {
  19.         VStack {
  20.             Text("Timer: \(timer.counter)")
  21.                 .font(.title)
  22.             
  23.             Button("Reset") {
  24.                 timer.counter = 0
  25.             }
  26.             .padding()
  27.             .background(Color.blue)
  28.             .foregroundColor(.white)
  29.             .cornerRadius(10)
  30.         }
  31.     }
  32. }
复制代码

@EnvironmentObject

@EnvironmentObject用于在整个视图层次结构中共享数据:
  1. class UserSettings: ObservableObject {
  2.     @Published var username = "Anonymous"
  3.     @Published var notificationEnabled = true
  4. }
  5. struct ProfileView: View {
  6.     @EnvironmentObject var settings: UserSettings
  7.    
  8.     var body: some View {
  9.         VStack {
  10.             Text("Username: \(settings.username)")
  11.             
  12.             Toggle("Notifications", isOn: $settings.notificationEnabled)
  13.         }
  14.         .padding()
  15.     }
  16. }
  17. struct ContentView: View {
  18.     @StateObject private var settings = UserSettings()
  19.    
  20.     var body: some View {
  21.         NavigationView {
  22.             VStack {
  23.                 TextField("Username", text: $settings.username)
  24.                     .textFieldStyle(RoundedBorderTextFieldStyle())
  25.                     .padding()
  26.                
  27.                 NavigationLink("View Profile", destination: ProfileView())
  28.             }
  29.             .navigationTitle("Settings")
  30.         }
  31.         .environmentObject(settings)
  32.     }
  33. }
复制代码

列表和导航

列表视图

List视图用于显示可滚动的数据列表:
  1. struct SimpleListView: View {
  2.     let fruits = ["Apple", "Banana", "Orange", "Mango", "Pineapple"]
  3.    
  4.     var body: some View {
  5.         List(fruits, id: \.self) { fruit in
  6.             Text(fruit)
  7.         }
  8.     }
  9. }
复制代码

可识别数据

使用符合Identifiable协议的数据模型:
  1. struct TodoItem: Identifiable {
  2.     let id = UUID()
  3.     let title: String
  4.     var isCompleted: Bool
  5. }
  6. struct TodoListView: View {
  7.     @State private var todos = [
  8.         TodoItem(title: "Buy groceries", isCompleted: false),
  9.         TodoItem(title: "Finish project", isCompleted: true),
  10.         TodoItem(title: "Call mom", isCompleted: false)
  11.     ]
  12.    
  13.     var body: some View {
  14.         List {
  15.             ForEach($todos) { $todo in
  16.                 HStack {
  17.                     Text(todo.title)
  18.                     Spacer()
  19.                     if todo.isCompleted {
  20.                         Image(systemName: "checkmark")
  21.                             .foregroundColor(.green)
  22.                     }
  23.                 }
  24.                 .onTapGesture {
  25.                     todo.isCompleted.toggle()
  26.                 }
  27.             }
  28.             .onDelete(perform: deleteTodos)
  29.         }
  30.     }
  31.    
  32.     func deleteTodos(at offsets: IndexSet) {
  33.         todos.remove(atOffsets: offsets)
  34.     }
  35. }
复制代码

分区列表

创建带有分区的列表:
  1. struct SectionedListView: View {
  2.     let sections = [
  3.         Section(title: "Fruits", items: ["Apple", "Banana", "Orange"]),
  4.         Section(title: "Vegetables", items: ["Carrot", "Broccoli", "Spinach"])
  5.     ]
  6.    
  7.     var body: some View {
  8.         List {
  9.             ForEach(sections, id: \.title) { section in
  10.                 Section(header: Text(section.title)) {
  11.                     ForEach(section.items, id: \.self) { item in
  12.                         Text(item)
  13.                     }
  14.                 }
  15.             }
  16.         }
  17.     }
  18. }
  19. struct Section: Identifiable {
  20.     let id = UUID()
  21.     let title: String
  22.     let items: [String]
  23. }
复制代码

导航视图

使用NavigationView和NavigationLink创建导航结构:
  1. struct MasterDetailView: View {
  2.     let items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
  3.    
  4.     var body: some View {
  5.         NavigationView {
  6.             List(items, id: \.self) { item in
  7.                 NavigationLink(destination: DetailView(item: item)) {
  8.                     Text(item)
  9.                 }
  10.             }
  11.             .navigationTitle("Items")
  12.         }
  13.     }
  14. }
  15. struct DetailView: View {
  16.     let item: String
  17.    
  18.     var body: some View {
  19.         VStack {
  20.             Text("Detail for \(item)")
  21.                 .font(.title)
  22.                 .padding()
  23.             
  24.             Spacer()
  25.         }
  26.         .navigationTitle(item)
  27.         .navigationBarTitleDisplayMode(.inline)
  28.     }
  29. }
复制代码

标签栏视图

使用TabView创建标签栏界面:
  1. struct TabBarView: View {
  2.     var body: some View {
  3.         TabView {
  4.             HomeView()
  5.                 .tabItem {
  6.                     Image(systemName: "house.fill")
  7.                     Text("Home")
  8.                 }
  9.             
  10.             SearchView()
  11.                 .tabItem {
  12.                     Image(systemName: "magnifyingglass")
  13.                     Text("Search")
  14.                 }
  15.             
  16.             ProfileView()
  17.                 .tabItem {
  18.                     Image(systemName: "person.fill")
  19.                     Text("Profile")
  20.                 }
  21.         }
  22.     }
  23. }
  24. struct HomeView: View {
  25.     var body: some View {
  26.         Text("Home Screen")
  27.             .font(.title)
  28.     }
  29. }
  30. struct SearchView: View {
  31.     var body: some View {
  32.         Text("Search Screen")
  33.             .font(.title)
  34.     }
  35. }
  36. struct ProfileView: View {
  37.     var body: some View {
  38.         Text("Profile Screen")
  39.             .font(.title)
  40.     }
  41. }
复制代码

动画和过渡

基本动画

SwiftUI提供了简单的方法来为视图变化添加动画:
  1. struct BasicAnimationView: View {
  2.     @State private var isAnimating = false
  3.    
  4.     var body: some View {
  5.         VStack {
  6.             Circle()
  7.                 .fill(Color.blue)
  8.                 .frame(width: 100, height: 100)
  9.                 .scaleEffect(isAnimating ? 1.5 : 1.0)
  10.                 .animation(.easeInOut(duration: 1.0), value: isAnimating)
  11.             
  12.             Button("Animate") {
  13.                 isAnimating.toggle()
  14.             }
  15.             .padding()
  16.             .background(Color.blue)
  17.             .foregroundColor(.white)
  18.             .cornerRadius(10)
  19.         }
  20.     }
  21. }
复制代码

隐式动画

使用.animation()修饰器为所有可动画的变化添加动画:
  1. struct ImplicitAnimationView: View {
  2.     @State private var animationAmount: CGFloat = 1
  3.    
  4.     var body: some View {
  5.         VStack {
  6.             Button("Tap Me") {
  7.                 animationAmount += 1
  8.             }
  9.             .padding(40)
  10.             .background(Color.red)
  11.             .foregroundColor(.white)
  12.             .clipShape(Circle())
  13.             .scaleEffect(animationAmount)
  14.             .blur(radius: (animationAmount - 1) * 3)
  15.             .animation(.default, value: animationAmount)
  16.         }
  17.     }
  18. }
复制代码

显式动画

使用withAnimation函数明确指定哪些变化应该有动画:
  1. struct ExplicitAnimationView: View {
  2.     @State private var isShowingRed = false
  3.    
  4.     var body: some View {
  5.         VStack {
  6.             Button("Toggle") {
  7.                 withAnimation {
  8.                     isShowingRed.toggle()
  9.                 }
  10.             }
  11.             
  12.             if isShowingRed {
  13.                 Rectangle()
  14.                     .fill(Color.red)
  15.                     .frame(width: 200, height: 200)
  16.                     .transition(.asymmetric(
  17.                         insertion: .scale,
  18.                         removal: .opacity
  19.                     ))
  20.             }
  21.         }
  22.     }
  23. }
复制代码

自定义动画

创建自定义动画曲线和效果:
  1. struct CustomAnimationView: View {
  2.     @State private var animationAmount = 0.0
  3.    
  4.     var body: some View {
  5.         VStack {
  6.             // 自定义弹簧动画
  7.             Button("Spring Animation") {
  8.                 animationAmount += 1
  9.             }
  10.             .padding(50)
  11.             .background(Color.green)
  12.             .foregroundColor(.white)
  13.             .clipShape(Circle())
  14.             .overlay(
  15.                 Circle()
  16.                     .stroke(Color.green)
  17.                     .scaleEffect(animationAmount)
  18.                     .opacity(Double(2 - animationAmount))
  19.                     .animation(
  20.                         Animation.spring(response: 1, dampingFraction: 0.5, blendDuration: 0)
  21.                             .repeatForever(autoreverses: false),
  22.                         value: animationAmount
  23.                     )
  24.             )
  25.             
  26.             // 自定义定时动画
  27.             Button("Custom Timing") {
  28.                 withAnimation(
  29.                     Animation.timingCurve(0.2, 0.8, 0.2, 1, duration: 1.5)
  30.                 ) {
  31.                     animationAmount += 1
  32.                 }
  33.             }
  34.             .padding()
  35.             .background(Color.purple)
  36.             .foregroundColor(.white)
  37.             .cornerRadius(10)
  38.         }
  39.     }
  40. }
复制代码

实战案例:构建一个天气应用界面

让我们综合运用前面学到的知识,构建一个完整的天气应用界面。

数据模型

首先,定义天气数据模型:
  1. struct WeatherData: Identifiable {
  2.     let id = UUID()
  3.     let day: String
  4.     let date: String
  5.     let highTemperature: Int
  6.     let lowTemperature: Int
  7.     let weatherType: WeatherType
  8.     let precipitationChance: Int
  9. }
  10. enum WeatherType: String, CaseIterable {
  11.     case sunny = "sun.max.fill"
  12.     case cloudy = "cloud.fill"
  13.     case rainy = "cloud.rain.fill"
  14.     case snowy = "cloud.snow.fill"
  15.     case stormy = "cloud.bolt.rain.fill"
  16.    
  17.     var color: Color {
  18.         switch self {
  19.         case .sunny: return .yellow
  20.         case .cloudy: return .gray
  21.         case .rainy: return .blue
  22.         case .snowy: return .cyan
  23.         case .stormy: return .purple
  24.         }
  25.     }
  26. }
复制代码

主视图

创建主视图,显示当前天气和预报:
  1. struct WeatherAppView: View {
  2.     @State private var currentLocation = "New York, NY"
  3.     @State private var currentTemp = 72
  4.     @State private var feelsLike = 75
  5.     @State private var humidity = 65
  6.     @State private var windSpeed = 8
  7.     @State private var currentWeather = WeatherType.sunny
  8.    
  9.     private let forecastData = [
  10.         WeatherData(day: "Today", date: "Jun 12", highTemperature: 75, lowTemperature: 62, weatherType: .sunny, precipitationChance: 10),
  11.         WeatherData(day: "Wed", date: "Jun 13", highTemperature: 78, lowTemperature: 64, weatherType: .cloudy, precipitationChance: 20),
  12.         WeatherData(day: "Thu", date: "Jun 14", highTemperature: 82, lowTemperature: 68, weatherType: .sunny, precipitationChance: 5),
  13.         WeatherData(day: "Fri", date: "Jun 15", highTemperature: 80, lowTemperature: 67, weatherType: .rainy, precipitationChance: 80),
  14.         WeatherData(day: "Sat", date: "Jun 16", highTemperature: 76, lowTemperature: 63, weatherType: .stormy, precipitationChance: 90),
  15.         WeatherData(day: "Sun", date: "Jun 17", highTemperature: 74, lowTemperature: 61, weatherType: .rainy, precipitationChance: 60),
  16.         WeatherData(day: "Mon", date: "Jun 18", highTemperature: 77, lowTemperature: 64, weatherType: .cloudy, precipitationChance: 30)
  17.     ]
  18.    
  19.     var body: some View {
  20.         ZStack {
  21.             // 背景渐变
  22.             LinearGradient(gradient: Gradient(colors: [Color.blue.opacity(0.6), Color.blue.opacity(0.3)]), startPoint: .topLeading, endPoint: .bottomTrailing)
  23.                 .ignoresSafeArea()
  24.             
  25.             ScrollView {
  26.                 VStack(spacing: 20) {
  27.                     // 位置和当前温度
  28.                     currentWeatherView
  29.                     
  30.                     // 天气详情
  31.                     weatherDetailsView
  32.                     
  33.                     // 每小时预报
  34.                     hourlyForecastView
  35.                     
  36.                     // 7天预报
  37.                     sevenDayForecastView
  38.                 }
  39.                 .padding()
  40.             }
  41.         }
  42.     }
  43.    
  44.     private var currentWeatherView: some View {
  45.         VStack(spacing: 10) {
  46.             HStack {
  47.                 VStack(alignment: .leading) {
  48.                     Text(currentLocation)
  49.                         .font(.title)
  50.                         .fontWeight(.bold)
  51.                     
  52.                     Text("Today, \(Date().formatted(.dateTime.month().day()))")
  53.                         .font(.subheadline)
  54.                         .foregroundColor(.secondary)
  55.                 }
  56.                
  57.                 Spacer()
  58.                
  59.                 Button(action: {
  60.                     // 刷新天气数据
  61.                 }) {
  62.                     Image(systemName: "arrow.clockwise")
  63.                         .font(.title2)
  64.                         .padding(8)
  65.                         .background(Color.white.opacity(0.2))
  66.                         .clipShape(Circle())
  67.                 }
  68.             }
  69.             
  70.             HStack {
  71.                 VStack(alignment: .leading, spacing: 5) {
  72.                     Text("\(currentTemp)°")
  73.                         .font(.system(size: 70, weight: .bold))
  74.                     
  75.                     Text("Feels like \(feelsLike)°")
  76.                         .font(.subheadline)
  77.                         .foregroundColor(.secondary)
  78.                 }
  79.                
  80.                 Spacer()
  81.                
  82.                 Image(systemName: currentWeather.rawValue)
  83.                     .font(.system(size: 60))
  84.                     .foregroundColor(currentWeather.color)
  85.             }
  86.         }
  87.         .padding()
  88.         .background(Color.white.opacity(0.2))
  89.         .cornerRadius(20)
  90.     }
  91.    
  92.     private var weatherDetailsView: some View {
  93.         HStack(spacing: 20) {
  94.             WeatherDetailItem(icon: "humidity.fill", title: "Humidity", value: "\(humidity)%")
  95.             WeatherDetailItem(icon: "wind", title: "Wind", value: "\(windSpeed) mph")
  96.             WeatherDetailItem(icon: "umbrella.fill", title: "Precip", value: "10%")
  97.         }
  98.         .padding()
  99.         .background(Color.white.opacity(0.2))
  100.         .cornerRadius(20)
  101.     }
  102.    
  103.     private var hourlyForecastView: some View {
  104.         VStack(alignment: .leading, spacing: 10) {
  105.             Text("Hourly Forecast")
  106.                 .font(.headline)
  107.                 .fontWeight(.bold)
  108.             
  109.             ScrollView(.horizontal, showsIndicators: false) {
  110.                 HStack(spacing: 15) {
  111.                     ForEach(0..<24) { hour in
  112.                         HourlyWeatherView(
  113.                             time: hour == 0 ? "Now" : "\(hour % 12 == 0 ? 12 : hour % 12)\(hour < 12 || hour == 24 ? "AM" : "PM")",
  114.                             temp: currentTemp + Int.random(in: -5...5),
  115.                             icon: WeatherType.allCases.randomElement()!
  116.                         )
  117.                     }
  118.                 }
  119.                 .padding(.vertical, 5)
  120.             }
  121.         }
  122.         .padding()
  123.         .background(Color.white.opacity(0.2))
  124.         .cornerRadius(20)
  125.     }
  126.    
  127.     private var sevenDayForecastView: some View {
  128.         VStack(alignment: .leading, spacing: 10) {
  129.             Text("7-Day Forecast")
  130.                 .font(.headline)
  131.                 .fontWeight(.bold)
  132.             
  133.             ForEach(forecastData) { day in
  134.                 HStack {
  135.                     Text(day.day)
  136.                         .frame(width: 50, alignment: .leading)
  137.                     
  138.                     Text(day.date)
  139.                         .font(.caption)
  140.                         .foregroundColor(.secondary)
  141.                     
  142.                     Spacer()
  143.                     
  144.                     Image(systemName: day.weatherType.rawValue)
  145.                         .foregroundColor(day.weatherType.color)
  146.                     
  147.                     Text("\(day.precipitationChance)%")
  148.                         .font(.caption)
  149.                         .foregroundColor(.blue)
  150.                     
  151.                     Text("\(day.lowTemperature)°")
  152.                         .foregroundColor(.secondary)
  153.                     
  154.                     Text("\(day.highTemperature)°")
  155.                         .fontWeight(.bold)
  156.                 }
  157.                 .padding(.vertical, 5)
  158.             }
  159.         }
  160.         .padding()
  161.         .background(Color.white.opacity(0.2))
  162.         .cornerRadius(20)
  163.     }
  164. }
  165. struct WeatherDetailItem: View {
  166.     let icon: String
  167.     let title: String
  168.     let value: String
  169.    
  170.     var body: some View {
  171.         VStack(spacing: 5) {
  172.             Image(systemName: icon)
  173.                 .font(.title2)
  174.             
  175.             Text(title)
  176.                 .font(.caption)
  177.                 .foregroundColor(.secondary)
  178.             
  179.             Text(value)
  180.                 .font(.headline)
  181.                 .fontWeight(.bold)
  182.         }
  183.         .frame(maxWidth: .infinity)
  184.     }
  185. }
  186. struct HourlyWeatherView: View {
  187.     let time: String
  188.     let temp: Int
  189.     let icon: WeatherType
  190.    
  191.     var body: some View {
  192.         VStack(spacing: 5) {
  193.             Text(time)
  194.                 .font(.caption)
  195.             
  196.             Image(systemName: icon.rawValue)
  197.                 .font(.title3)
  198.                 .foregroundColor(icon.color)
  199.             
  200.             Text("\(temp)°")
  201.                 .font(.headline)
  202.         }
  203.         .padding(.horizontal, 10)
  204.         .padding(.vertical, 5)
  205.         .background(Color.white.opacity(0.1))
  206.         .cornerRadius(10)
  207.     }
  208. }
复制代码

预览

添加预览代码:
  1. struct WeatherAppView_Previews: PreviewProvider {
  2.     static var previews: some View {
  3.         WeatherAppView()
  4.     }
  5. }
复制代码

这个天气应用界面展示了SwiftUI的多个关键特性:

• 使用VStack、HStack和ZStack进行布局
• 使用ScrollView创建可滚动内容
• 使用自定义视图组件(如WeatherDetailItem和HourlyWeatherView)
• 使用@State管理界面状态
• 使用渐变背景和透明度创建视觉效果
• 使用条件渲染和循环来显示动态内容

最佳实践和性能优化

视图组合与重用

将UI分解为小型、可重用的组件:
  1. // 可重用的卡片组件
  2. struct CardView<Content: View>: View {
  3.     let content: Content
  4.    
  5.     init(@ViewBuilder content: () -> Content) {
  6.         self.content = content()
  7.     }
  8.    
  9.     var body: some View {
  10.         content
  11.             .padding()
  12.             .background(Color.white)
  13.             .cornerRadius(10)
  14.             .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
  15.     }
  16. }
  17. // 使用卡片组件
  18. struct ContentView: View {
  19.     var body: some View {
  20.         VStack(spacing: 20) {
  21.             CardView {
  22.                 Text("This is a card")
  23.                     .font(.headline)
  24.                
  25.                 Text("Cards are a great way to group related content")
  26.                     .font(.subheadline)
  27.                     .foregroundColor(.secondary)
  28.             }
  29.             
  30.             CardView {
  31.                 HStack {
  32.                     Image(systemName: "star.fill")
  33.                         .foregroundColor(.yellow)
  34.                     
  35.                     Text("Another card with an icon")
  36.                         .fontWeight(.bold)
  37.                     
  38.                     Spacer()
  39.                 }
  40.             }
  41.         }
  42.         .padding()
  43.     }
  44. }
复制代码

惰性加载

对于大型列表或复杂视图,使用惰性加载提高性能:
  1. struct LazyLoadingExample: View {
  2.     let items = (1...1000).map { "Item \($0)" }
  3.    
  4.     var body: some View {
  5.         ScrollView {
  6.             // 惰性垂直堆栈
  7.             LazyVStack(spacing: 10) {
  8.                 ForEach(items, id: \.self) { item in
  9.                     Text(item)
  10.                         .padding()
  11.                         .background(Color.blue.opacity(0.2))
  12.                         .cornerRadius(8)
  13.                 }
  14.             }
  15.             .padding()
  16.         }
  17.         
  18.         // 惰性网格
  19.         ScrollView {
  20.             LazyVGrid(columns: [
  21.                 GridItem(.flexible()),
  22.                 GridItem(.flexible()),
  23.                 GridItem(.flexible())
  24.             ], spacing: 10) {
  25.                 ForEach(items, id: \.self) { item in
  26.                     Text(item)
  27.                         .padding()
  28.                         .background(Color.green.opacity(0.2))
  29.                         .cornerRadius(8)
  30.                 }
  31.             }
  32.             .padding()
  33.         }
  34.     }
  35. }
复制代码

避免不必要的重绘

使用.equatable()协议和@Equatable属性包装器减少不必要的视图更新:
  1. struct EquatableView: View, Equatable {
  2.     let title: String
  3.     let value: Int
  4.    
  5.     var body: some View {
  6.         HStack {
  7.             Text(title)
  8.             Spacer()
  9.             Text("\(value)")
  10.                 .fontWeight(.bold)
  11.         }
  12.         .padding()
  13.         .background(Color.blue.opacity(0.1))
  14.         .cornerRadius(8)
  15.     }
  16.    
  17.     // 实现相等性判断,只有当title和value都改变时才更新视图
  18.     static func == (lhs: EquatableView, rhs: EquatableView) -> Bool {
  19.         return lhs.title == rhs.title && lhs.value == rhs.value
  20.     }
  21. }
  22. struct ParentView: View {
  23.     @State private var counter = 0
  24.     @State private var title = "Counter"
  25.    
  26.     var body: some View {
  27.         VStack {
  28.             EquatableView(title: title, value: counter)
  29.             
  30.             Button("Increment") {
  31.                 counter += 1
  32.             }
  33.             .padding()
  34.             .background(Color.blue)
  35.             .foregroundColor(.white)
  36.             .cornerRadius(8)
  37.             
  38.             Button("Change Title") {
  39.                 title = "New Title"
  40.             }
  41.             .padding()
  42.             .background(Color.green)
  43.             .foregroundColor(.white)
  44.             .cornerRadius(8)
  45.         }
  46.         .padding()
  47.     }
  48. }
复制代码

合理使用状态管理

根据场景选择合适的状态管理方式:
  1. // @State: 用于简单视图的本地状态
  2. struct StateExample: View {
  3.     @State private var isOn = false
  4.    
  5.     var body: some View {
  6.         Toggle("Switch", isOn: $isOn)
  7.             .padding()
  8.     }
  9. }
  10. // @ObservedObject: 用于观察外部对象,可能会被销毁和重建
  11. class Settings: ObservableObject {
  12.     @Published var fontSize: Double = 16.0
  13. }
  14. struct ObservedObjectExample: View {
  15.     @ObservedObject var settings = Settings()
  16.    
  17.     var body: some View {
  18.         VStack {
  19.             Slider(value: $settings.fontSize, in: 12...24)
  20.             Text("Font size: \(Int(settings.fontSize))")
  21.         }
  22.         .padding()
  23.     }
  24. }
  25. // @StateObject: 用于拥有并管理可观察对象的生命周期
  26. class Counter: ObservableObject {
  27.     @Published var value = 0
  28.    
  29.     func increment() {
  30.         value += 1
  31.     }
  32. }
  33. struct StateObjectExample: View {
  34.     @StateObject private var counter = Counter()
  35.    
  36.     var body: some View {
  37.         VStack {
  38.             Text("Count: \(counter.value)")
  39.             Button("Increment") {
  40.                 counter.increment()
  41.             }
  42.         }
  43.         .padding()
  44.     }
  45. }
  46. // @EnvironmentObject: 用于在视图层次结构中共享数据
  47. struct EnvironmentObjectExample: View {
  48.     var body: some View {
  49.         NavigationView {
  50.             ParentView()
  51.                 .environmentObject(Counter())
  52.         }
  53.     }
  54. }
  55. struct ParentView: View {
  56.     var body: some View {
  57.         NavigationLink("Go to Child", destination: ChildView())
  58.     }
  59. }
  60. struct ChildView: View {
  61.     @EnvironmentObject var counter: Counter
  62.    
  63.     var body: some View {
  64.         VStack {
  65.             Text("Count: \(counter.value)")
  66.             Button("Increment") {
  67.                 counter.increment()
  68.             }
  69.         }
  70.         .padding()
  71.     }
  72. }
复制代码

使用自定义修饰器

创建自定义修饰器来重用视图修饰逻辑:
  1. // 自定义标题样式修饰器
  2. struct TitleStyle: ViewModifier {
  3.     func body(content: Content) -> some View {
  4.         content
  5.             .font(.largeTitle)
  6.             .fontWeight(.bold)
  7.             .foregroundColor(.primary)
  8.             .padding(.bottom, 10)
  9.     }
  10. }
  11. // 自定义卡片样式修饰器
  12. struct CardStyle: ViewModifier {
  13.     func body(content: Content) -> some View {
  14.         content
  15.             .padding()
  16.             .background(Color.white)
  17.             .cornerRadius(10)
  18.             .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
  19.     }
  20. }
  21. // 扩展View以方便使用修饰器
  22. extension View {
  23.     func titleStyle() -> some View {
  24.         self.modifier(TitleStyle())
  25.     }
  26.    
  27.     func cardStyle() -> some View {
  28.         self.modifier(CardStyle())
  29.     }
  30. }
  31. // 使用自定义修饰器
  32. struct CustomModifierExample: View {
  33.     var body: some View {
  34.         VStack {
  35.             Text("Welcome to SwiftUI")
  36.                 .titleStyle()
  37.             
  38.             Text("Custom modifiers help reuse styling code")
  39.                 .cardStyle()
  40.         }
  41.         .padding()
  42.     }
  43. }
复制代码

总结和进阶学习资源

总结

SwiftUI是苹果推出的现代化UI框架,它通过声明式语法和强大的组合能力,极大地简化了iOS、macOS、watchOS和tvOS应用的开发过程。在本教程中,我们学习了:

1. SwiftUI基础知识和语法结构
2. 常用UI组件的使用方法
3. 布局系统和视图组合
4. 状态管理和数据流
5. 列表和导航的实现
6. 动画和过渡效果
7. 通过实战案例构建完整应用界面
8. 性能优化和最佳实践

掌握SwiftUI需要不断练习和探索。随着经验的积累,您将能够更加高效地构建美观、响应式的苹果应用界面。

进阶学习资源

要进一步提升SwiftUI技能,可以参考以下资源:

• Apple SwiftUI Documentation
• Apple SwiftUI Tutorials
• Swift by Sundell SwiftUI Collection

• “SwiftUI by Tutorials” by raywenderlich.com
• “SwiftUI for Masterminds” by J.D. Gauchat
• “Pro SwiftUI” by Steven F. Daniel

• Udemy - SwiftUI Masterclass
• Coursera - SwiftUI iOS Application Development
• Hacking with Swift - SwiftUI

• Awesome SwiftUI- 精选的SwiftUI资源列表
• SwiftUI-Examples- SwiftUI示例代码集合
• SwiftUIX- 扩展和增强SwiftUI功能的开源库

• SwiftUI Subreddit
• Apple Developer Forums
• Stack Overflow

通过结合理论学习和实践项目,您将能够逐步掌握SwiftUI的高级特性,并成为一名高效的苹果应用界面开发者。祝您学习愉快!
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则