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,只有当当前视图出现时才会发生。
希望这对其他人有帮助!
更新: 添加了更多的代码示例,以便提供一个最小的工作示例。
在我的应用程序的主屏幕上,用户可以 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,只有当当前视图出现时才会发生。
希望这对其他人有帮助!