SwiftUI自定义onFirstAppear View Modifier重复触发问题求助
问题原因
你的问题出在视图实例的生命周期上:当你从PdfPrinterView切换到其他详情视图时,SwiftUI会销毁当前的PdfPrinterView实例;再次返回时,会重新创建一个全新的PdfPrinterView实例。而你在FirstAppear修饰器里用的@State private var isFirstAppear = true是绑定到视图实例的状态,视图销毁后这个状态会被清除,新实例创建时isFirstAppear又会变回true,所以每次返回都会触发首次加载逻辑。
解决方法
方法一:将状态移至ViewModel(推荐)
如果PdfPrinterView有对应的ViewModel,把首次出现的状态放在ViewModel中,用@StateObject保证ViewModel实例不被销毁:
// 定义ViewModel class PdfPrinterViewModel: ObservableObject { @Published var hasAppearedOnce = false } // 修改PdfPrinterView struct PdfPrinterView: View { @StateObject private var vm = PdfPrinterViewModel() var body: some View { VStack { HeaderView() PdfScrollView() } .onAppear { if !vm.hasAppearedOnce { vm.hasAppearedOnce = true action() } } } func action() { print("On first appear") } }
方法二:使用全局状态容器(适合无ViewModel的简单场景)
创建单例类跟踪视图的首次出现状态,确保跨视图实例状态不丢失:
class AppAppearanceTracker { static let shared = AppAppearanceTracker() private var appearedViews = Set<String>() func markAsAppeared(_ viewId: String) { appearedViews.insert(viewId) } func hasAppeared(_ viewId: String) -> Bool { appearedViews.contains(viewId) } } // 修改FirstAppear修饰器 private struct FirstAppear: ViewModifier { let action: () -> Void let viewId: String func body(content: Content) -> some View { content.onAppear { let tracker = AppAppearanceTracker.shared if !tracker.hasAppeared(viewId) { tracker.markAsAppeared(viewId) print("First") action() } else { print("Second") } } } } // 修改View扩展 extension View { func onFirstAppear(viewId: String, _ action: @escaping () -> Void) -> some View { modifier(FirstAppear(action: action, viewId: viewId)) } } // 使用示例 struct PdfPrinterView: View { var body: some View { VStack { HeaderView() PdfScrollView() } .onFirstAppear(viewId: "PdfPrinterView") { action() } } func action() { print("On first appear") } }
方法三:使用持久化存储(适合全局永久仅执行一次的场景)
如果希望即使APP重启后也只执行一次,用@AppStorage将状态持久化到本地:
private struct FirstAppear: ViewModifier { let action: () -> Void let storageKey: String @AppStorage private var hasAppeared: Bool = false init(action: @escaping () -> Void, storageKey: String) { self.action = action self.storageKey = storageKey _hasAppeared = AppStorage(wrappedValue: false, storageKey) } func body(content: Content) -> some View { content.onAppear { if !hasAppeared { hasAppeared = true print("First") action() } else { print("Second") } } } } extension View { func onFirstAppear(storageKey: String, _ action: @escaping () -> Void) -> some View { modifier(FirstAppear(action: action, storageKey: storageKey)) } } // 使用示例 struct PdfPrinterView: View { var body: some View { VStack { HeaderView() PdfScrollView() } .onFirstAppear(storageKey: "PdfPrinterView_FirstAppear") { action() } } func action() { print("On first appear") } }
内容的提问来源于stack exchange,提问作者Trey6
相关产品推荐
相关产品推荐

