I setup a timer to show two alerts. The first alert shows the user a warning that says "Hey, you've been inactive for a while, are you still here" and if they tap yes, then it resets the timer. If they don't tap the alert button, after x amount of time it shows the second alert notifying them their session has expired. They tap OK and it takes them out of the nav view and back to the main page.
This all works perfectly, however XCode is throwing an error of:
[Presentation] Attempt to present <SwiftUI.PlatformAlertController: 0x7fc2fa031800> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x7fc2e9f05530> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_24NavigationColumnModifier__: 0x7fc309f28e80>) which is already presenting <SwiftUI.PlatformAlertController: 0x7fc2ea842000>.
From what I understand, this is saying, there's already an alert being shown and even though I'm going to dismiss it and show your other alert, I'm going to complain about it, which tells me I'm doing something wrong. I would like to ensure I'm doing it correctly so I do not receive this error in XCode's console.
After creating the timer I am using onReceive to watch it:
.onReceive(timer) { time in
if userTimeout == true {
timer.upstream.connect().cancel()
} else {
caWarning()
caTimeout()
}
}
The two functions are really simplistic:
func caWarning() {
if Date.now >= userActivity.addingTimeInterval(5*1) {
userActivityAlert = true
}
}
func caTimeout() {
if Date.now >= userActivity.addingTimeInterval(10*1) {
userActivityAlert = false
userTimeout = true
}
}
Finally, my if statement for the alerts is:
if userActivityAlert == true {
VStack {
EmptyView()
}
.alert("Foo", isPresented: $userActivityAlert, actions: {
Button("I'm Here") {
userActivity = Date.now
}
}, message: { Text("Are you still there?") }
) // End show alert
} else if UserTimeout == true {
VStack {
EmptyView()
}
.alert("Foo", isPresented: $userTimeout, actions: {
Button("OK") {
self.caRegExpired.navToHome = true
}
}, message: {
Text("Your session has expired.\nTap OK, to start over.")
}) // End Alert
} // End If
I have tried everything I can think of. From @Environment (\.dismiss) var dismiss
and then calling dismiss()
(dismisses the underlying view and not the alert), to pouring through Apple's docs and old UIKit stuff (no help).
I'm fine with either doing it this way (i.e. two separate alerts) OR being able to modify the first alert into the second alert after a certain period of time (perhaps even showing a countdown until the user's session has expired).
Honestly, I'm wondering if it's just log noise because the first alert disappears and the second one displays exactly as it should. Literally this all works fine, but it's important that errors are addressed and I want to know if I'm doing something wrong.
This is for the latest SwiftUI 5+ and XCode for iOS 15+
@loremipsum's link has the answer: just wait a little between deactivating the first alert and showing the second:
struct ContentView: View {
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@State private var userActivityAlert = false
@State private var userTimeout = false
@State private var userActivity = Date.now
var body: some View {
VStack {
Text("Hello, World!")
.alert("Foo", isPresented: $userActivityAlert, actions: {
Button("I'm Here") {
userActivity = Date.now
}
}, message: { Text("Are you still there?") }
) // End show alert
Text("Hello, World!")
.alert("Foo", isPresented: $userTimeout, actions: {
Button("OK") {
// self.caRegExpired.navToHome = true
}
}, message: {
Text("Your session has expired.\nTap OK, to start over.")
}) // End Alert
}
.onReceive(timer) { time in
if userTimeout == true {
timer.upstream.connect().cancel()
} else {
caWarning()
caTimeout()
}
}
}
func caWarning() {
if Date.now >= userActivity.addingTimeInterval(5*1) {
userActivityAlert = true
}
}
func caTimeout() {
if Date.now >= userActivity.addingTimeInterval(10*1) {
userActivityAlert = false
//This delay leaves a little gap between alerts
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
userTimeout = true
}
}
}
}