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

Swift SwiftUI生命周期管理

2022-07-292.6k 阅读

视图的初始化

在 SwiftUI 中,视图的生命周期从初始化开始。每个视图都是一个结构体,当我们创建一个视图实例时,就触发了它的初始化过程。

简单视图初始化

比如创建一个简单的文本视图:

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
    }
}

这里 ContentView 结构体通过 Text 视图来构建其 bodyText 视图在初始化时接收一个字符串参数,这个字符串就是显示在屏幕上的文本内容。

带有参数的视图初始化

我们可以创建一个自定义视图并传递参数:

struct CustomTextView: View {
    let text: String
    let fontSize: CGFloat
    
    var body: some View {
        Text(text)
          .font(.system(size: fontSize))
    }
}

然后在其他视图中使用这个自定义视图:

struct MainView: View {
    var body: some View {
        CustomTextView(text: "Custom Text", fontSize: 24)
    }
}

CustomTextView 初始化时,textfontSize 参数被传入,用于定制视图的外观。这些参数在视图的生命周期中保持不变,除非整个视图被重新创建。

视图的更新

视图更新是 SwiftUI 生命周期中非常重要的一部分。当视图的状态或者环境发生变化时,SwiftUI 会决定是否需要更新视图。

状态驱动的更新

  1. @State 修饰符 我们可以使用 @State 修饰符来定义一个可变状态,当这个状态变化时,视图会自动更新。
    struct CounterView: View {
        @State private var count = 0
        
        var body: some View {
            VStack {
                Text("Count: \(count)")
                Button("Increment") {
                    count += 1
                }
            }
        }
    }
    
    这里 @State 修饰的 count 变量是一个可变状态。当用户点击 Increment 按钮时,count 增加,Text 视图会自动更新显示新的计数。
  2. @Binding 传递状态 如果我们想在父视图和子视图之间共享状态,可以使用 @Binding
    struct ChildView: View {
        @Binding var value: Int
        
        var body: some View {
            Button("Increment in Child") {
                value += 1
            }
        }
    }
    
    struct ParentView: View {
        @State private var number = 0
        
        var body: some View {
            VStack {
                Text("Number: \(number)")
                ChildView(value: $number)
            }
        }
    }
    
    ParentView 中,@State 定义了 number 状态。通过 $number 语法将 number 的绑定传递给 ChildView。当在 ChildView 中点击按钮增加 value 时,ParentView 中的 number 也会改变,从而导致 Text 视图更新。

环境变化导致的更新

  1. @Environment 修饰符 @Environment 用于从父视图的环境中获取值。当环境值发生变化时,视图会更新。
    struct ThemeableView: View {
        @Environment(\.colorScheme) var colorScheme
        
        var body: some View {
            VStack {
                if colorScheme == .dark {
                    Text("Dark Theme")
                 } else {
                    Text("Light Theme")
                 }
            }
        }
    }
    
    这里 @Environment(\.colorScheme) 获取当前的颜色模式(亮色或暗色)。当用户切换系统的颜色模式时,ThemeableView 会自动更新以显示相应的文本。
  2. 环境对象(@EnvironmentObject) 环境对象是一种在视图层次结构中共享数据的方式。 首先定义一个环境对象:
    class UserSettings: ObservableObject {
        @Published var theme: String = "light"
    }
    
    然后在视图中使用它:
    struct SettingsView: View {
        @EnvironmentObject var userSettings: UserSettings
        
        var body: some View {
            VStack {
                Text("Current Theme: \(userSettings.theme)")
                Button("Switch Theme") {
                    if userSettings.theme == "light" {
                        userSettings.theme = "dark"
                    } else {
                        userSettings.theme = "light"
                    }
                }
            }
        }
    }
    
    SettingsView 中,@EnvironmentObject 引入了 UserSettings 实例。当 theme 属性通过按钮点击发生变化时,SettingsView 会更新显示新的主题信息。

视图的销毁

在 SwiftUI 中,视图的销毁不像在一些命令式编程框架中那样需要手动管理。SwiftUI 基于视图的状态和环境变化来自动处理视图的销毁。

从视图层次结构中移除

当一个视图不再是视图层次结构的一部分时,它会被销毁。例如,使用 NavigationView 时,当我们从一个屏幕导航离开,相关的视图可能会被销毁。

struct DetailView: View {
    var body: some View {
        Text("This is the detail view")
    }
}

struct MasterView: View {
    @State private var isShowingDetail = false
    
    var body: some View {
        NavigationView {
            VStack {
                Button("Show Detail") {
                    isShowingDetail = true
                }
                NavigationLink(destination: DetailView(), isActive: $isShowingDetail) {
                    EmptyView()
                }
            }
          .navigationTitle("Master")
        }
    }
}

当用户从 DetailView 导航回 MasterView 时,DetailView 会从视图层次结构中移除,SwiftUI 会自动处理其销毁过程,释放相关资源。

条件渲染导致的视图销毁

当一个视图由于条件渲染而不再显示时,也会被销毁。

struct ConditionalView: View {
    @State private var showView = true
    
    var body: some View {
        VStack {
            Button("Toggle View") {
                showView.toggle()
            }
            if showView {
                Text("This view may be destroyed")
            }
        }
    }
}

当用户点击 Toggle View 按钮,showView 变为 falseText 视图不再显示,SwiftUI 会处理该 Text 视图的销毁。

视图生命周期中的事件处理

除了初始化、更新和销毁,视图在其生命周期中还可以处理各种事件。

手势事件

  1. 点击手势 我们可以为视图添加点击手势。
    struct TappableView: View {
        @State private var tapCount = 0
        
        var body: some View {
            Text("Tapped \(tapCount) times")
              .onTapGesture {
                    tapCount += 1
                }
        }
    }
    
    onTapGesture 修饰符为 Text 视图添加了点击事件处理。每次点击,tapCount 增加,视图更新显示新的点击次数。
  2. 长按手势 长按手势可以通过 onLongPressGesture 实现。
    struct LongPressableView: View {
        @State private var longPressDetected = false
        
        var body: some View {
            Text(longPressDetected ? "Long press detected" : "Press and hold")
              .onLongPressGesture {
                    longPressDetected = true
                }
        }
    }
    
    当用户长按 Text 视图时,longPressDetected 变为 true,视图文本更新。

视图出现和消失事件

  1. 视图出现事件(onAppear) onAppear 可以用于在视图出现时执行一些代码。
    struct AppearView: View {
        @State private var log = ""
        
        var body: some View {
            VStack {
                Text(log)
            }
           .onAppear {
                log = "View appeared"
            }
        }
    }
    
    AppearView 出现在屏幕上时,onAppear 闭包中的代码会执行,更新 log 并显示相应信息。
  2. 视图消失事件(onDisappear) onDisappear 在视图消失时执行代码。
    struct DisappearView: View {
        @State private var log = ""
        
        var body: some View {
            VStack {
                Text(log)
            }
           .onDisappear {
                log = "View disappeared"
            }
        }
    }
    
    DisappearView 从屏幕上消失时,onDisappear 闭包中的代码会执行,更新 log 以显示视图消失信息。

高级生命周期管理

在复杂的 SwiftUI 应用中,可能需要更高级的生命周期管理技巧。

视图的惰性加载

有时我们可能不希望视图一开始就被创建,而是在需要时才加载。这可以通过 LazyVStackLazyHStack 来实现。

struct LazyContentView: View {
    let items = Array(1...100)
    
    var body: some View {
        LazyVStack {
            ForEach(items, id: \.self) { item in
                Text("Item \(item)")
            }
        }
    }
}

LazyContentView 中,LazyVStack 只会在需要显示视图时才创建 Text 视图,而不是一开始就创建所有 100 个视图,这对于性能优化很有帮助,尤其是在处理大量数据时。

视图的缓存

对于一些频繁创建和销毁的视图,我们可以考虑缓存机制。虽然 SwiftUI 本身在一定程度上优化了视图的创建和销毁,但在某些场景下,手动缓存可能更合适。 假设我们有一个复杂的自定义视图 ComplexView

struct ComplexView: View {
    let data: String
    // 复杂的视图构建逻辑
    var body: some View {
        Text(data)
          .padding()
          .background(Color.blue)
    }
}

我们可以使用一个缓存字典来缓存视图实例:

class ViewCache {
    static var cache = [String: ComplexView]()
    
    static func getView(for data: String) -> ComplexView {
        if let cachedView = cache[data] {
            return cachedView
        } else {
            let newView = ComplexView(data: data)
            cache[data] = newView
            return newView
        }
    }
}

然后在其他视图中使用缓存的视图:

struct CachingView: View {
    let data1 = "Data 1"
    let data2 = "Data 2"
    
    var body: some View {
        VStack {
            ViewCache.getView(for: data1)
            ViewCache.getView(for: data2)
        }
    }
}

这样,对于相同数据的 ComplexView,不会重复创建,提高了性能。

视图生命周期与动画

动画在 SwiftUI 中与视图生命周期紧密相关。我们可以在视图的初始化、更新和销毁过程中添加动画效果。

  1. 初始化动画 我们可以在视图初始化时添加动画。
    struct FadeInView: View {
        @State private var isVisible = false
        
        var body: some View {
            Text("Fade In Text")
              .opacity(isVisible ? 1 : 0)
              .animation(.easeIn(duration: 1), value: isVisible)
              .onAppear {
                    isVisible = true
                }
        }
    }
    
    FadeInView 出现时,Text 视图从透明(opacity 为 0)逐渐淡入(opacity 变为 1),动画持续 1 秒。
  2. 更新动画 当视图状态更新时添加动画。
    struct ScaleView: View {
        @State private var scale = 1.0
        
        var body: some View {
            Button("Scale") {
                scale = scale == 1.0 ? 2.0 : 1.0
            }
           .scaleEffect(scale)
           .animation(.spring(), value: scale)
        }
    }
    
    当点击 Scale 按钮时,按钮的大小会根据 scale 的变化以弹簧动画的效果进行缩放。
  3. 销毁动画 我们也可以在视图销毁时添加动画。
    struct SlideOutView: View {
        @State private var isVisible = true
        
        var body: some View {
            if isVisible {
                Text("Slide Out Text")
                   .offset(x: isVisible ? 0 : UIScreen.main.bounds.width)
                   .animation(.easeOut(duration: 1), value: isVisible)
                   .onDisappear {
                        isVisible = false
                    }
            }
            Button("Hide") {
                isVisible = false
            }
        }
    }
    
    当点击 Hide 按钮时,Text 视图会从当前位置滑出屏幕,动画持续 1 秒。

总结

SwiftUI 的生命周期管理涵盖了视图的初始化、更新、销毁以及事件处理等多个方面。通过合理利用 @State@Binding@Environment@EnvironmentObject 等修饰符,以及各种手势和视图生命周期相关的方法,开发者可以构建出高效、响应式的用户界面。同时,在复杂应用中,利用视图的惰性加载、缓存以及与动画的结合,可以进一步优化性能和用户体验。掌握这些生命周期管理技巧对于深入理解和开发优秀的 SwiftUI 应用至关重要。无论是简单的文本视图还是复杂的自定义视图,都可以通过这些技术来实现精准的控制和优化。在实际开发中,根据应用的需求和场景,灵活运用这些知识,能够让我们的 SwiftUI 应用在性能和用户体验上达到更高的水平。不断探索和实践这些技术,有助于开发者在 SwiftUI 开发领域不断进步,打造出更加出色的应用程序。