【发布时间】:2022-02-18 07:47:07
【问题描述】:
更新:添加了更多代码示例,以便提供一个工作最小的示例。
在我的应用程序的主屏幕上,用户可以从几个不同的选项中进行选择。其中之一是创建一个新的用户帐户,这是一个利用 NavigationView 和各种 NavigationLinks 的多页表单。
在表单的第一页初始化一个可观察的类,然后表单的其余页面观察该类。
如果我浏览表单,然后从表单中完全点击后退箭头,则该类会按原样取消初始化。但是,我设置了超时警报,设置为在 x 秒后使用 OK 按钮将它们带回应用程序的主屏幕(即退出表单和导航视图)。
所有这一切都很好,除了在警报上点击确定然后被发送回主屏幕时,类没有被取消初始化,因此内存泄漏。
这也导致了另一个重大问题。
如果我在表单的第一页并等待警报,没问题。但是,如果我在表单的任何连续页面上并出现警报,我会收到控制台错误说明:
[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.
我一直在研究这些问题一段时间,但似乎无法找到关于如何解决这些问题的明确答案。
注册过期类:
import Foundation
class RegExpired: ObservableObject {
@Published var navToHome: Bool = false
}
定时器类:
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 是通过@StateObject 初始化 timerClass 的地方。所有其他表单页面都使用@ObservedObject 来共享该类。但是,在警报上点击 OK 后,用户将返回到 ContentView 并且类保持初始化状态,并且会发生上述所有错误。
我很感激!谢谢。
【问题讨论】:
-
很惊讶你的
MyApp可以编译。为什么不使用@StateObject var regExpired = RegExpired(),删除init()并使用.environmentObject(regExpired)而不是不存在的caRegExpired。 -
需要最小的可重现示例。
-
workingdog:你发现了一些错别字。我修好了它们。这是一个精简的代码示例。还有很多事情要做,这就是自定义初始化的原因。
-
我用更多代码示例更新了帖子。希望这将有助于建立一个最小的可复制示例来说明正在发生的事情。谢谢!
-
我刚刚测试了表单第一页上的 TimeoutAlertView(timerClass: timerClass) 是否会显示警报并证明我的理论。他们是这样!如果我只将它放在一个地方而没有表单的其他页面,它会将我带回 ContentView 并按应有的方式取消初始化该类。也不再有重复的警报。但是,“不鼓励从分离的视图控制器显示视图控制器”的错误仍然显示,因为警报是从分离的视图触发的。
标签: class swiftui alert swiftui-navigationview