SwiftUI Pop To Root DeInit Class

SwiftUI Pop To Root DeInit Class

更新: 添加了更多的代码示例,以便提供一个最小的工作示例。


在我的应用程序的主屏幕上,用户可以 select 从几个不同的选项。其中之一是创建一个新的用户帐户,这是一个使用 NavigationView 和各种 NavigationLinks 的多页表单。

在表单的第一页上,一个可观察对象 class 被初始化,表单的其余页面观察到 class.

如果我浏览表单,然后点击返回箭头离开表单,class 会按预期取消初始化。但是,我将超时警报设置为在 x 秒后关闭,并使用确定按钮将它们带回应用程序的主屏幕(即脱离表单和导航视图)。

所有这一切都很好,除了在警报上点击确定然后被发送回主屏幕时,class 没有被取消初始化,因此内存泄漏。

这也导致了另一个主要问题。

如果我在表格的第一页并等待提醒,没问题。但是,如果我在表单的任何连续页面上出现警报,我会收到控制台错误:

[Presentation] Attempt to present <SwiftUI.PlatformAlertController: 0x7f850606ba00> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x7f84f680a410> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_24NavigationColumnModifier__: 0x7f84f690e800>) which is already presenting <SwiftUI.PlatformAlertController: 0x7f8515829a00>

这让我相信警报会显示在表单的每一页上,而不仅仅是触发警报时用户所在的页面。

此外,当点击“确定”返回主屏幕时,再次显示警告并显示另一个错误:

[Presentation] Presenting view controller <SwiftUI.PlatformAlertController: 0x7f84f5881200> from detached view controller <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_24NavigationColumnModifier__: 0x7f84f69236a0> is discouraged.

我已经研究这些问题一段时间了,但似乎无法找到解决这些问题的明确答案。

注册已过期Class:

import Foundation

class RegExpired: ObservableObject {
    @Published var navToHome: Bool = false
}

计时器Class:

import Combine
import Foundation

class TimerClass: ObservableObject {
    
// Timeout Timer
    private var receive = [AnyCancellable]()
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    init() {
        timeoutTimer()
        print("Initialized")
    }
    
    deinit {
        timerCancel()
        print("DeInitialized")
    }
    
// Storing User Activity Time
    @Published var userActivity = Date.now
    @Published var userActivityAlert = false
    @Published var userTimeout = false
    
// CreateUser Timer
    func timeoutTimer() {
        timer
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.warning()
                self?.timeout()
            }.store(in: &receive)
    }
    
// Cancel Timer
    func timerCancel() {
        timer.upstream.connect().cancel()
        userActivityAlert = false
        userTimeout = false
    }
    
// Timeout Warning
    func warning() {
        if Date.now >= userActivity.addingTimeInterval(10*1) {
            userActivityAlert = true
        }
    }
    
// Timeout Expired
    func timeout() {
        if Date.now >= userActivity.addingTimeInterval(15*1) {
            userActivityAlert = false
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.userTimeout = true
            }
        }
    }
}

超时警报:

import SwiftUI

struct TimeoutAlertView: View {
    
    @EnvironmentObject var regExpired: RegExpired
    @ObservedObject var timerClass: TimerClass
    
    var body: some View {
        if timerClass.userActivityAlert == true {
            VStack {
                EmptyView()
            }
            .alert("Warning", isPresented: $timerClass.userActivityAlert, actions: {
                Button("I'm Here") { timerClass.userActivity = Date.now }}, message: { Text("Are you still there?") })
        } else if timerClass.userTimeout == true {
            VStack {
                EmptyView()
            }
            .alert("Expired", isPresented: $timerClass.userTimeout, actions: {
                Button("OK") {
                    self.regExpired.navToHome = true
                }}, message: { Text("Your session has expired.") })
        }
        
    }
}

应用程序:

import SwiftUI

@main
struct MyApp: App {
    @StateObject var regExpired: RegExpired
    
    init() {
        let regExpired = RegExpired()
        _regExpired = StateObject(wrappedValue: regExpired)
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(regExpired)
        }
    }
}

内容视图:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var regExpired: RegExpired
    @State var isMainViewActive: Bool = false
    
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: WelcomeView(), isActive: $isMainViewActive) {
                    Text("Reg Form")
                }
                .isDetailLink(false)
            }
            .onReceive(self.regExpired.$navToHome) { navToHome in
                if navToHome {
                    self.isMainViewActive = false
                    self.regExpired.navToHome = false
                }
            }
        } // End NavigationView
        .navigationViewStyle(.stack)
    }
}

欢迎和下一个观看次数:

import SwiftUI

struct WelcomeView: View {

    @StateObject var timerClass = TimerClass()
    @State private var btnHover = false
    @State private var isBtnActive = false
    
    var body: some View {

        VStack {                
            List {
                Section {
                    Text("Welcome")
                }               

                Section {
                    Spacer()
                    HStack {
                        Spacer()
                        Image(systemName: btnHover == true ? "chevron.right.circle.fill" : "chevron.forward.circle")
                            .pressAction {
                                btnHover = true
                            } onRelease: {
                                btnHover = false
                                isBtnActive = true
                            }
                            .background(
                                NavigationLink(destination: NextView(timerClass: timerClass), isActive: $isBtnActive) {}
                                    .opacity(0)
                            )
                            Spacer()
                    }
                }
            }
    }
    .navigationBarTitle("", displayMode: .inline)
    TimeoutAlertView(timerClass: timerClass)
    }
}

struct NextView: View {

    @ObservedObject var timerClass: TimerClass
    @State private var btnHover = false
    @State private var isBtnActive = false
    
    var body: some View {

        VStack {                
            List {
                Section {
                    Text("Welcome")
                }               

                Section {
                    Spacer()
                    HStack {
                        Spacer()
                        Image(systemName: btnHover == true ? "chevron.right.circle.fill" : "chevron.forward.circle")
                            .pressAction {
                                btnHover = true
                            } onRelease: {
                                btnHover = false
                                isBtnActive = true
                            }
                            .background(
                                NavigationLink(destination: AnotherView(timerClass: timerClass), isActive: $isBtnActive) {}
                                    .opacity(0)
                            )
                            Spacer()
                    }
                }
            }
    }
    .navigationBarTitle("", displayMode: .inline)
    TimeoutAlertView(timerClass: timerClass)
    }
}

WelcomeView 是定时器Class 通过@StateObject 初始化的地方。所有其他表单页面都使用@ObservedObject 来共享 class。但是,在警报上点击“确定”后,用户将返回到 ContentView 并且 class 保持初始化状态,并且会出现上述所有错误。

非常感谢!谢谢。

好的,我解决了

老实说,我不需要我发布的所有代码。您需要检测当前视图。我在表单的每一页中添加了:

@State private var isViewDisplayed = false

然后:

.onAppear() {
    self.isViewDisplayed = true
}

.onDisappear() {
    self.isViewDisplayed = false
}

然后调用 TimeoutAlertView

if self.isViewDisplayed {
    TimeoutAlertView(timerClass: timerClass)
}

现在,如果 isViewDisplayed 为真,则每个页面只会 运行 TimeoutAlertView,只有当当前视图出现时才会发生。

希望这对其他人有帮助!