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 = "初始化"
               }
           }
       }
}

相关推荐
HarderCoder18 小时前
SwiftUI 可复用视图设计解剖:从语义到 API 的完整实践
swiftui·swift
东坡肘子18 小时前
当灵感跑在了结果前面 -- 肘子的 Swift 周报 #145
人工智能·swiftui·swift
霍霍哈嗨6 天前
swiftUI框架基础
ios·swiftui·swift
东坡肘子8 天前
当每一次写入都有了新价格 -- 肘子的 Swift 周报 #144
人工智能·swiftui·swift
CHB14 天前
uni-app x 蒸汽模式 iOS版 性能测试基准报告 Benchmark
ios·uni-app·swiftui
大熊猫侯佩14 天前
SwiftUI 侦探笔记:iPhone 14 失踪的“刘海”与侯佩的像素诡计
swiftui·swift·apple
大熊猫侯佩14 天前
WWDC26 新 @ContentBuilder 修饰符:SwiftUI 终于有了户口本
swiftui·swift·wwdc
东坡肘子15 天前
当 Linux 成为“空气”:容器、Agent 与不再重要的“桌面之争” -- 肘子的 Swift 周报 #143
人工智能·swiftui·swift
东坡肘子22 天前
SPI 加入 Apple,Swift 迈向自举 -- 肘子的 Swift 周报 #142
人工智能·swiftui·swift
神奇的程序员1 个月前
开发了一个进阶版Apple健康
swiftui·apple·apple watch