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

相关推荐
袁代码7 天前
SwiftUI开发教程系列 - 第十二章:本地化与多语言支持
开发语言·前端·ios·swiftui·swift·ios开发
袁代码14 天前
SwiftUI开发教程系列 - 第1章:简介与环境配置
开发语言·ios·swiftui·swift·ios开发
今天也想MK代码17 天前
在Swift开发中简化应用程序发布与权限管理的解决方案——SparkleEasy
前端·javascript·chrome·macos·electron·swiftui
東三城23 天前
【ios】---SwiftUI开发从入门到放弃
ios·swiftui·swift·1024程序员节
今天也想MK代码25 天前
基于swiftui 实现3D loading 动画效果
ios·swiftui·swift
胖虎11 个月前
SwiftUI(五)- ForEach循环创建视图&尺寸类&安全区域
ios·swiftui·swift·foreach·安全区域
zyosasa1 个月前
SwiftUI 精通之路 11: 栅格布局
前端·swiftui·swift
小溪彼岸1 个月前
【iOS小组件实战】灵动岛实时进度通知
swiftui·swift
提笔忘字的帝国1 个月前
【ios】SwiftUI 混用 UIKit 的 Bug 解决:UITableView 无法滚动到底部
swiftui·bug·xcode
zyosasa1 个月前
SwiftUI 精通之路 09:ForEach 视图构造器的基础应用
swiftui·swift