I asked this on StackOverflow, but maybe you know the solution.
My app implements a static quick action. When you call it, a sheet is shown and you enter a new item. Simple.
I noticed that when you call the quick action (by long pressing its icon and selecting the action), and you send the app to the background (by going to the home screen or going to another app), and you come back to the app, the quick action is triggered again. As if the quick action was cached and never cleared. This happens when the app is closed and you opened it with a quick action, or when the app was already opened, in the background and you call the quick action from the app icon.
I looked at my code and I thought that maybe I needed to set the quick action to nil once I was done. I tried but no luck. Here's the code and what I have tried as well.
Declaration of the static quick action on info.plist:
<key>UIApplicationShortcutItems</key>
<array>
<dict> <key>UIApplicationShortcutItemIconSymbolName</key>
<string>square.and.pencil</string>
<key>UIApplicationShortcutItemTitle</key>
<string>New Item</string>
<key>UIApplicationShortcutItemType</key>
<string>NewItem</string>
</dict>
</array>
Main app:
import SwiftUI
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) var scenePhase
private let quickActionService = QuickActionService()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(quickActionService)
}
.onChange(of: scenePhase) { scenePhase in
switch scenePhase {
case .active:
guard let shortcutItem = appDelegate.shortcutItem else { return }
quickActionService.action = QuickAction(rawValue: shortcutItem.type)
print(">>>>quick action type: " + shortcutItem.type)
default:
return
}
}
}
}
Main view:
struct ContentView: View {
@EnvironmentObject var quickActions: QuickActionService
@State private var sheetNewItem = false
var body: some View {
NavigationView {
List {
Text("Hello World")
}
.sheet(isPresented: $sheetNewItem) {
SheetViewNewItem()
}
// triggers the quick action when the app is closed
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
checkQuickAction()
}
}
// triggers the quick action when the app comes back from he background
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
checkQuickAction()
}
}
}
}
func checkQuickAction() {
if let quickAction = quickActions.action {
print("Selected Quick Action: \(quickAction.rawValue)")
self.sheetNewItem.toggle()
}
}
}
The class for the quick action:
import Foundation
enum QuickAction: String {
case NewItem
}
final class QuickActionService: ObservableObject {
@Published var action: QuickAction?
init(initialValue: QuickAction? = nil) {
action = initialValue
}
}
And the app delegate:
import UIKit
import CoreData
final class AppDelegate: NSObject, UIApplicationDelegate {
var shortcutItem: UIApplicationShortcutItem? { AppDelegate.shortcutItem }
fileprivate static var shortcutItem: UIApplicationShortcutItem?
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
if let shortcutItem = options.shortcutItem {
AppDelegate.shortcutItem = shortcutItem
}
let sceneConfiguration = UISceneConfiguration(
name: "Scene Configuration",
sessionRole: connectingSceneSession.role
)
sceneConfiguration.delegateClass = SceneDelegate.self
return sceneConfiguration
}
}
private final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func windowScene(
_ windowScene: UIWindowScene,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
AppDelegate.shortcutItem = shortcutItem
completionHandler(true)
}
}
First I thought I needed to add code for handling when the app went into the background, setting the quick action to 'nil', but the problems lingered.
.onChange(of: scenePhase) { scenePhase in
switch scenePhase {
case .active:
guard let shortcutItem = appDelegate.shortcutItem else { return }
quickActionService.action = QuickAction(rawValue: shortcutItem.type)
case .background:
print(">>>>app in background, setting quick action to nil")
quickActionService.action = nil
default:
return
}
}
So I thought maybe I should set it to nil on receive:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
checkQuickAction()
quickActions.action = nil
print(">> coming back: \(quickActions.action.debugDescription)")
}
}
I also tried on the function itself:
func checkQuickAction() {
if let quickAction = quickActions.action {
print("Selected Quick Action: \(quickAction.rawValue)")
self.sheetNewItem.toggle()
quickActions.action = nil
}
}
And in spite of quickActions.action now being nil it is still triggering the quick action once it comes from the background.
At this point I figured that maybe this is not the way, or maybe there is something lingering that I'm not seeing. I searched for examples of quick actions, and everything I have found is similar to my code, with the difference being that the examples call a setting of dynamic action when the app goes to the background. I don't need this, but either way, I tried, and the problem still remained. Some of the more educational articles are this, this, this, and this (this one also teaches you how to debug a quick action).
And here I am. I'm going in circles and I don't know whether this is a bug on SwiftUI, or whether I'm not doing something right.
Do you have any ideas how to prevent this from happening?