SwiftUI Tips:如何实现viewDidLoad只执行一次的效果

很多时候我们需要在 View 初始化的时候设置一些状态,只要设置一次。和UIKit 中的 viewDidLoad 相似。然而因为 SwiftUI 的设计理念不同,SwiftUI View 中的生命周期只提供了 onAppear,没有类似 onFirstAppear 的方法。

当前提供的 onAppear 并不能满足我们在一些场景的需求,因为 onAppear 会在页面每次重新出现的时候都执行,不适合只要在页面初始化第一次的时候赋值的场景。

比如下面的例子:

swift 复制代码
struct ContentView: View {
    @State private var greeting: String = "Initial"
       
       var body: some View {
           NavigationStack {
               VStack {
                   Text(greeting)
                       .onTapGesture {
                           greeting = "点击"
                       }
                   NavigationLink(destination: Text("Pushed Text")) {
                       Text("Push a View")
                   }
                   .padding()
               }
               .onAppear {
                   greeting = "初始化"
               }
           }
       }
}

有一个标签在页面刚出现的时候为"初始化",如果用户点击了之后,标签的文案改为"点击"。在目前的生命周期事件里,只能把设置初始化值的代码写在 onAppear 里。

但是这样会引发什么问题呢?如果我们点击 NavigationLink 跳转到下一个页面,然后返回到当前页面,标签的值被重置为"初始化"了。其实我们是希望它保持"点击"的状态了。但是如果使用 onAppear 就会不可避免产生这样的问题。

那能不能在 View 的初始化方法中赋值呢?比如这样:

swift 复制代码
struct FirstAppearView: View {
    @State private var greeting: String = "Initial"

    init(greeting: String) {
        self.greeting = greeting
    }
}

如果 FirstAppearView 整个应用生命周期只被使用一次,以上的代码是成立的。如果这个页面可能被多次使用,这个方式就不行了。因为 SwfitUI 底层优化了性能,等于会自动缓存一些 View。使用 FirstAppearView 如果是 reuse 来的,那么 init 方法中的赋值就不会被执行了。总结来说,init 方法关联的是应用级别的初始化。但是我们想要的 onFirstAppear 其实是页面级别的初始化。

所以我们必须自定义一个方法,然后绑定到一个自身的状态值来标记。

我们可以通过下面的代码来自定义 ViewModifier 来达到这个目的:

swift 复制代码
public extension View {
    func onFirstAppear(_ action: @escaping () -> ()) -> some View {
        modifier(FirstAppear(action: action))
    }
}

private struct FirstAppear: ViewModifier {
    let action: () -> ()
    
    // Use this to only fire your block one time
    @State private var hasAppeared = false
    
    func body(content: Content) -> some View {
        // And then, track it here
        content.onAppear {
            guard !hasAppeared else { return }
            hasAppeared = true
            action()
        }
    }
}

使用的方法和 onAppear 是一样的,使用我们的自定义的 onFirstAppear 替换 onAppear 就可以达到我们的目的了。

swift 复制代码
struct ContentView: View {
    @State private var greeting: String = "Initial"
       
       var body: some View {
           NavigationStack {
               VStack {
                   Text(greeting)
                       .onTapGesture {
                           greeting = "点击"
                       }
                   NavigationLink(destination: Text("Pushed Text")) {
                       Text("Push a View")
                   }
                   .padding()
               }
               .onFirstAppear {
                   greeting = "初始化"
               }
           }
       }
}

相关推荐
长沙火山17 天前
SwiftUI 8.List介绍和使用
ios·list·swiftui
东坡肘子18 天前
Chrome 会成为 OpenAI 的下一个目标?| 肘子的 Swift 周报 #081
人工智能·swiftui·swift
littleplayer21 天前
iOS 中的 @MainActor 详解
前端·swiftui·swift
林晨月23 天前
SwiftUI Color(一)
ios·swiftui
东坡肘子24 天前
更短的有效期和更长的保质期 | 肘子的 Swift 周报 #080
swiftui·swift·wwdc
躺平每一天25 天前
SwiftUI 的列表组件 - List (Trae 提升效率)
swiftui·trae
ZRD11121 个月前
SwiftUI 表达式
swiftui·swift
东坡肘子1 个月前
微软收紧插件、谷歌发力云端,Xcode 何去何从? | 肘子的 Swift 周报 #079
人工智能·swiftui·swift
YungFan1 个月前
SwiftUI-国际化(二)
swiftui·swift