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

相关推荐
用户347475478332814 小时前
把SwiftUI View 转为图片
ios·swiftui
东坡肘子4 天前
高通收购 Arduino:历史的轮回 | 肘子的 Swift 周报 #0106
swiftui·arduino·swift
HarderCoder4 天前
Swift 基础语法全景(一):从变量到类型安全
swiftui·swift
QWQ___qwq6 天前
SwiftUI 的状态管理包装器(Property Wrapper)
ios·swiftui·swift
jh_cao8 天前
(4)SwiftUI 基础(第四篇)
ios·swiftui·swift
jh_cao9 天前
(3)SwiftUI 的状态之上:数据流与架构(MVVM in SwiftUI)
ios·架构·swiftui
jh_cao9 天前
(3)容器布局进阶:Spacer、Divider、Frame 与 Alignment
swiftui
东坡肘子9 天前
Sora 2:好模型,但未必是好生意 | 肘子的 Swift 周报 #0105
人工智能·swiftui·swift
jh_cao9 天前
(1)SwiftUI 的哲学:声明式 UI vs 命令式 UI
ui·swiftui·命令模式
HarderCoder9 天前
Swift 6 并发深渊:@unchecked Sendable 与“隐式 MainActor”如何合谋杀死你的 App
swiftui·swift