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

相关推荐
小洋人最happy7 天前
SwiftUI基础组件之HStack、VStack、ZStack详解
swiftui·vstack·zstack·hstack·spacing
coooliang8 天前
【iOS】SwiftUI状态管理
ios·swiftui·swift
小洋人最happy8 天前
SwiftUI基础组件之List详解
list·swiftui·selection·列表组件·ondelete
struggle202510 天前
Ollmao (OH-luh-毛程序包及源码) 是一款原生 SwiftUI 应用程序,它与 Ollama 集成,可在 Mac 上本地运行强大的 AI 模型
ios·swiftui·swift
货拉拉技术1 个月前
货拉拉用户端SwiftUI踩坑之旅
ios·swiftui·swift
ZacJi2 个月前
巧用 allowsHitTesting 自定义 SignInWithAppleButton
ios·swiftui·swift
刘争Stanley2 个月前
SwiftUI 是如何改变 iOS 开发游戏规则的?
ios·swiftui·swift
1024小神2 个月前
在swiftui中使用Alamofire发送请求获取github仓库里的txt文件内容并解析
ios·github·swiftui
大熊猫侯佩2 个月前
SwiftUI 撸码常见错误 2 例漫谈
swiftui·xcode·tag·tabview·preview·coredata·fetchrequest
东坡肘子2 个月前
肘子的 Swift 周报 #063|异种肾脏移植取得突破
swiftui·swift·apple