Issue #774

Start by defining your quick actions. You can use UIApplicationShortcutIcon(type:) for predefined icons, or use UIApplicationShortcutIcon(systemImageName:) for SFSymbol

enum QuickAction: String {
    case readPasteboard
    case clear

    var shortcutItem: UIApplicationShortcutItem {
        switch self {
        case .readPasteboard:
            return UIApplicationShortcutItem(
                type: rawValue,
                localizedTitle: "Read Pasteboard",
                localizedSubtitle: "",
                icon: UIApplicationShortcutIcon(type: .add),
                userInfo: nil
            )
        case .clear:
            return UIApplicationShortcutItem(
                type: rawValue,
                localizedTitle: "Clear Pasteboard",
                localizedSubtitle: "",
                icon: UIApplicationShortcutIcon(systemImageName: SFSymbol.wind.rawValue),
                userInfo: nil
            )
        }
    }
}

Add a service to store selected quick action. I usually make this conform to ObservableObject to be able to bind to SwiftUI views later

final class QuickActionService: ObservableObject {
    var shortcutItem: UIApplicationShortcutItem?

Expose AppDelegate and SceneDelegate to your SwiftUI App. Listen to scenePhase to add dynamic items

From Define Dynamic Quick Actions

Set dynamic screen quick actions at any point, but the sample sets them in the sceneWillResignActive(_:) function of the scene delegate. During the transition to a background state is a good time to update any dynamic quick actions, because the system executes this code before the user returns to the Home Screen.

@main
struct PastePaliOSApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self)
    var appDelegate
    @Environment(\.scenePhase)
    var scenePhase

    var body: some Scene {
        WindowGroup {
            main
        }
        .onChange(of: scenePhase) { scenePhase in
            switch scenePhase {
            case .background:
                addDynamicQuickActions()
            case .active:
                QuickActionService.shared.perform()
            default:
                break
            }
        }
    }

    private func addDynamicQuickActions() {
        UIApplication.shared.shortcutItems = [
            QuickAction.readPasteboard.shortcutItem,
            QuickAction.clear.shortcutItem
        ]
    }
}

Quick actions are notified in 2 cases

  • If the app isn’t already loaded, it’s launched and passes details of the shortcut item in through the connectionOptions parameter of the scene(_:willConnectTo:options:) function in AppDelegate
  • If your app is already loaded, the system calls the windowScene(_:performActionFor:completionHandler:) function of your SceneDelegate

Therefore we need to handle both cases.

final class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
    ) -> UISceneConfiguration {
        if let shortcutItem = options.shortcutItem {
            QuickActionService.shared.shortcutItem = shortcutItem
        }

        let sceneConfiguration = UISceneConfiguration(
            name: "Default",
            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
    ) {
        QuickActionService.shared.shortcutItem = shortcutItem
        completionHandler(true)
    }

Read more

For more please consult official Apple docs and design