Getting currently focused UIScene when multiple connected scenes are active in foreground - ipad

I have a problem getting the current UIScene when multiple scenes are connected.
Specifically, my app (iOS 13) supports multiple windows on the iPad. When I have two windows side by side and I access the apps connected scenes (with UIApplication.shared.connectedScenes), all of them have the activationState set to .foregroundActive. But I am actually only interacting with one of them.
I found some code here on stackoverflow to retrieve the current key window (can't remember who it was that posted it) but as you can see it will always return one of the two windows.
let keyWindow: UIWindow? = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
Am I missing some method to determine the last window that was interacted with or are the multiple .foregroundActive states a bug?
Thanks!

Although UIApplication.shared.keyWindow is deprecated after iOS 13.0 by Apple, it seems this attribute can help you find the active one when multiple scenes in the foreground by using:
UIApplication.shared.keyWindow?.windowScene

I found the solution to my problem by refactoring my code so that now I access the current UIScenes window through the window attribute of any view or view controller. This way I don't need the keyWindow anymore.
I think it's probably correct that both windows open next to each other have the activationState .foregroundActive
In case anyone needs it, these are the two extensions that helped me get to to current window from anywhere in the app:
extension UIViewController {
var window: UIWindow? {
return view.window
}
}
extension UIScene {
var window: UIWindow? {
return (delegate as? SceneDelegate)?.window
}
}
But the problem still stands if you don't have access to a UIView, UIViewController or UIScene. For example inside a data model with no reference to a view or scene. Then I don't see a way to consistently accessing the correct window the user is interacting with.

I use this extension in my project
// MARK: - Scene Delegate helpers
#available(iOS 13.0, *)
extension UIApplication {
static var firstScene: UIScene? {
UIApplication.shared.connectedScenes.first
}
static var firstSceneDelegate: SceneDelegate? {
UIApplication.firstScene?.delegate as? SceneDelegate
}
static var firstWindowScene: UIWindowScene? {
UIApplication.firstSceneDelegate?.windowScene
}
}
// MARK: - Window Scene sugar
#available(iOS 13.0, *)
protocol WindowSceneDependable {
var windowScene: UIWindowScene? { get }
var window: UIWindow? { get }
}
#available(iOS 13.0, *)
extension WindowSceneDependable {
var windowScene: UIWindowScene? {
window?.windowScene
}
}
#available(iOS 13.0, *)
extension UIScene: WindowSceneDependable { }
#available(iOS 13.0, *)
extension SceneDelegate: WindowSceneDependable { }
#available(iOS 13.0, *)
extension UIViewController: WindowSceneDependable { }
// MARK: - Window sugar
#available(iOS 13.0, *)
extension UIScene {
var window: UIWindow? {
(delegate as? SceneDelegate)?.window
}
}
extension UIViewController {
var window: UIWindow? {
view.window
}
}
And then I can do this when I have to present something:
func present(from navigation: UINavigationController) {
self.navigation = navigation
if #available(iOS 13.0, *) {
guard let currentWindowScene = navigation.windowScene else { return }
myPresenter.present(in: currentWindowScene)
} else {
myPresenter.present()
}
}
Using this I can get a window from almost anywhere. But the thing is that it's not possible if I have no access to view-related objects at all. For example, in my auth service I have to use this bullshit:
func authorize() {
if #available(iOS 13.0, *) {
guard let currentWindowScene = UIApplication.firstWindowScene else { return }
presenter.present(in: currentWindowScene)
} else {
presenter.present()
}
}
This does not look correct for me, and it works only for the first windowScene.
It's better not to do this just like I did, you have to elaborate on this solution.

This solution gives you the last interacted-with UIScene without relying on anything but using a UIWindow subclass for all your scenes.
class Window: UIWindow
{
static var lastActive: Window?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
{
Self.lastActive = self
return super.hitTest(point, with: event)
}
}
Now you can access Window.lastActive and its associated windowScene (UIWindowScene/UIScene).

Related

SceneDelegate.swift deprecated/changed architecture? [duplicate]

Now that AppDelegate and SceneDelegate are removed from SwiftUI, where do I put the code that I used to have in SceneDelegate and AppDelegate, Firebase config for ex?
So I have this code currently in my AppDelegate:
Where should I put this code now?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseConfiguration.shared.setLoggerLevel(.min)
FirebaseApp.configure()
return true
}
Here is a solution for SwiftUI life-cycle. Tested with Xcode 12b / iOS 14
import SwiftUI
import UIKit
// no changes in your AppDelegate class
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print(">> your code here !!")
return true
}
}
#main
struct Testing_SwiftUI2App: App {
// inject into SwiftUI life-cycle via adaptor !!!
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Overriding the initializer in your App also works:
import SwiftUI
import Firebase
#main
struct BookSpineApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
BooksListView()
}
}
}
Find a more detailed write-up here:
The Ultimate Guide to the SwiftUI 2 Application Life Cycle
Firebase and the new SwiftUI 2 Application Life Cycle
You should not put that kind of codes in the app delegate at all or you will end up facing the Massive App Delegate. Instead, you should consider refactoring your code to more meaningful pieces and then put the right part in the right place. For this case, the only thing you need is to be sure that the code is executing those functions once the app is ready and only once. So the init method could be great:
#main
struct MyApp: App {
init() {
setupFirebase()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
private extension MyApp {
func setupFirebase() {
FirebaseConfiguration.shared.setLoggerLevel(.min)
FirebaseApp.configure()
}
}
AppDelegate ?
You can have your own custom class and assign it as the delegate. But note that it will not work for events that happen before assignment. For example:
class CustomDelegate: NSObject, UIApplicationDelegate {
static let Shared = CustomDelegate()
}
And later:
UIApplication.shared.delegate = CustomDelegate.Shared
Observing For Notifications
Most of AppDelegate methods are actually observing on notifications that you can observe manually instead of defining a new class. For example:
NotificationCenter.default.addObserver(
self,
selector: #selector(<#T###objc method#>),
name: UIApplication.didBecomeActiveNotification,
object: nil
)
Native AppDelegate Wrapper
You can directly inject app delegate into the #main struct:
#UIApplicationDelegateAdaptor(CustomDelegate.self) var appDelegate
Note: Using AppDelegate
Remember that adding AppDelegate means that you are killing default multiplatform support and you have to check for platform manually.
You can also use the new ScenePhase for certain code that the AppDelegate and SceneDelegate had. Like going to the background or becoming active. From
struct PodcastScene: Scene {
#Environment(\.scenePhase) private var phase
var body: some Scene {
WindowGroup {
TabView {
LibraryView()
DiscoverView()
SearchView()
}
}
.onChange(of: phase) { newPhase in
switch newPhase {
case .active:
// App became active
case .inactive:
// App became inactive
case .background:
// App is running in the background
#unknown default:
// Fallback for future cases
}
}
}
}
Example credit: https://wwdcbysundell.com/2020/building-entire-apps-with-swiftui/
Note the method below will stop cross platform support so should only be used if you are planning on building for iOS only.
It should also be noted that this doesn’t use the SwiftUI lifecycle method, instead it allows you to return to the UIKit lifecycle method.
You can still have an AppDelegate and a SceneDelegate when you create a SwiftUI app in Xcode 12-beta.
You just need to make sure that you have chosen the correct option for the Life Cycle when you create your app.
Make sure you choose UIKit App Delegate for the Life Cycle and you will get an AppDelegate and a SceneDelegate
I would also advise in using the main App's init method for this one, as it seems safe to use (any objections?).
What I usually do, that might be useful to share, is to have a couple of utility types, combined with the Builder pattern.
/// An abstraction for a predefined set of functionality,
/// aimed to be ran once, at app startup.
protocol StartupProcess {
func run()
}
/// A convenience type used for running StartupProcesses.
/// Uses the Builder pattern for some coding eye candy.
final class StartupProcessService {
init() { }
/// Executes the passed-in StartupProcess by running it's "run()" method.
/// - Parameter process: A StartupProcess instance, to be initiated.
/// - Returns: Returns "self", as a means to chain invocations of StartupProcess instances.
#discardableResult
func execute(process: any StartupProcess) -> StartupProcessService {
process.run()
return self
}
}
and then we have some processes
struct CrashlyticsProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}
struct FirebaseProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}
struct AppearanceCustomizationProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}
and finally, running them
#main
struct TheApp: App {
init() {
initiateStartupProcesses()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
private extension TheApp {
func initiateStartupProcesses() {
StartupProcessService()
.execute(process: ExampleProcess())
.execute(process: FirebaseProcess())
.execute(process: AppearanceCustomizationProcess)
}
}
Seems quite nice and super clean.
I see a lot of solutions where init gets used as didFinishLaunching. However, didFinishLaunching gets called AFTER init of the App struct.
Solution 1
Use the init of the View that is created in the App struct. When the body of the App struct gets called, didFinishLaunching just happened.
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#ViewBuilder
var body: some Scene {
WindowGroup {
MainView(appDelegate: appDelegate)
}
}
}
struct MainView: View {
init(appDelegate: AppDelegate) {
// at this point `didFinishLaunching` is completed
setup()
}
}
Solution 2
We can create a block to notify us when didFinishLaunching gets called. This allows to keep more code in SwiftUI world (rather than in AppDelegate).
class AppDelegate: NSObject, UIApplicationDelegate {
var didFinishLaunching: ((AppDelegate) -> Void)?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
didFinishLaunching?(self)
return true
}
}
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#ObservedObject private var applicationModel = ApplicationModel()
// `init` gets called BEFORE `didFinishLaunchingWithOptions`
init() {
// Subscribe to get a `didFinishLaunching` call
appDelegate.didFinishLaunching = { [weak applicationObject] appDelegate in
// Setup any application code...
applicationModel?.setup()
}
}
var body: some Scene {
return WindowGroup {
if applicationObject.isUserLoggedIn {
LoggedInView()
} else {
LoggedOutView()
}
}
}
}

How do i get the following script runs on ios 15? [duplicate]

I'm using Core Data with Cloud Kit, and have therefore to check the iCloud user status during application startup. In case of problems I want to issue a dialog to the user, and I do it using UIApplication.shared.keyWindow?.rootViewController?.present(...) up to now.
In Xcode 11 beta 4, there is now a new deprecation message, telling me:
'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes
How shall I present the dialog instead?
Edit The suggestion I make here is deprecated in iOS 15. So now what? Well, if an app doesn't have multiple windows of its own, I presume the accepted modern way would be to get the first of the app's connectedScenes, coerce to a UIWindowScene, and take its first window. But that is almost exactly what the accepted answer does! So my workaround feels rather feeble at this point. However, I'll let it stand for historical reasons.
The accepted answer, while ingenious, might be overly elaborate. You can get exactly the same result much more simply:
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
I would also caution that the deprecation of keyWindow should not be taken overly seriously. The full warning message reads:
'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes
So if you are not supporting multiple windows on iPad there is no objection to going ahead and continuing to use keyWindow.
iOS 16, compatible down to iOS 15
As this thread keeps getting traffic three years later, I want to share what I consider the most elegant solution with current functionality. It also works with SwiftUI.
UIApplication
.shared
.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.keyWindow }
.first
iOS 15 and 16, compatible down to iOS 13
UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
Note that connectedScenes is available only since iOS 13. If you need to support earlier versions of iOS, you have to place this in an if #available(iOS 13, *) statement.
A variant that is longer, but easier to understand:
UIApplication
.shared
.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
iOS 13 and 14
The following historical answer is still valid on iOS 15, but should be replaced because UIApplication.shared.windows is deprecated. Thanks to #matt for pointing this out!
Original answer:
Improving slightly on matt's excellent answer, this is even simpler, shorter, and more elegant:
UIApplication.shared.windows.first { $0.isKeyWindow }
This is my solution:
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.compactMap({$0 as? UIWindowScene})
.first?.windows
.filter({$0.isKeyWindow}).first
Usage e.g.:
keyWindow?.endEditing(true)
Here is a backward-compatible way of detecting keyWindow:
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
Usage:
if let keyWindow = UIWindow.key {
// Do something
}
Usually use
Swift 5
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
In addition,in the UIViewController:
self.view.window
view.window is current window for scenes
WWDC 2019:
Key Windows
Track windows manually
Introducing Multiple Windows on iPad - WWDC 2019 - Videos - Apple Developer
Supporting Multiple Windows on iPad | Apple Developer Documentation
For an Objective-C solution
+ (UIWindow *)keyWindow
{
NSArray<UIWindow *> *windows = [[UIApplication sharedApplication] windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
return window;
}
}
return nil;
}
A UIApplication extension:
extension UIApplication {
/// The app's key window taking into consideration apps that support multiple scenes.
var keyWindowInConnectedScenes: UIWindow? {
return windows.first(where: { $0.isKeyWindow })
}
}
Usage:
let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes
Ideally, since it has been deprecated I would advice you to store the window in the SceneDelegate. However if you do want a temporary workaround, you can create a filter and retrieve the keyWindow just like this.
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
Supports iOS 13 and later.
To keep using similar syntax as the older iOS versions UIApplication.shared.keyWindow create this extension:
extension UIApplication {
var mainKeyWindow: UIWindow? {
get {
if #available(iOS 13, *) {
return connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
} else {
return keyWindow
}
}
}
}
Usage
if let keyWindow = UIApplication.shared.mainKeyWindow {
// Do Stuff
}
If you want to use it in any ViewController then you can simply use.
self.view.window
(Tested with iOS 15.2 running on Xcode 13.2.1)
extension UIApplication {
var keyWindow: UIWindow? {
// Get connected scenes
return UIApplication.shared.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
}
If you want to find the presented UIViewController in the key UIWindow , here is another extension you could find useful:
extension UIApplication {
var keyWindowPresentedController: UIViewController? {
var viewController = self.keyWindow?.rootViewController
// If root `UIViewController` is a `UITabBarController`
if let presentedController = viewController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
}
// Go deeper to find the last presented `UIViewController`
while let presentedController = viewController?.presentedViewController {
// If root `UIViewController` is a `UITabBarController`
if let presentedController = presentedController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
} else {
// Otherwise, go deeper
viewController = presentedController
}
}
return viewController
}
}
You can put this wherever you want, but I personally added it as an extension to UIViewController.
This allows me to add more useful extensions, like ones to present UIViewControllers more easily for example:
extension UIViewController {
func presentInKeyWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindow?.rootViewController?
.present(self, animated: animated, completion: completion)
}
}
func presentInKeyWindowPresentedController(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindowPresentedController?
.present(self, animated: animated, completion: completion)
}
}
}
try with that:
UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
As many of developers asking for Objective C code of this deprecation's replacement. You can use this below code to use the keyWindow.
+(UIWindow*)keyWindow {
UIWindow *windowRoot = nil;
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
windowRoot = window;
break;
}
}
return windowRoot;
}
I created and added this method in the AppDelegate class as a class method and use it with very simple way that is below.
[AppDelegate keyWindow];
Don't forget to add this method in AppDelegate.h class like below.
+(UIWindow*)keyWindow;
For an Objective-C solution too
#implementation UIWindow (iOS13)
+ (UIWindow*) keyWindow {
NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:#"isKeyWindow == YES"];
return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
}
#end
Inspired by the answer of berni
let keyWindow = Array(UIApplication.shared.connectedScenes)
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first(where: { $0.isKeyWindow })
I've solved with:
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
As you probably know, the key window is deprecated because of possible multiple scenes. The most convenient solution is to provide a currentWindow as an extension, then search-and-replace.
extension UIApplication {
var currentWindow: UIWindow? {
connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
}
}
NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *window in windowScene.windows) {
UIViewController *viewController = window.rootViewController;
// Get the instance of your view controller
if ([viewController isKindOfClass:[YOUR_VIEW_CONTROLLER class]]) {
// Your code here...
break;
}
}
}
}
Berni's code is nice but it doesn't work when the app comes back from background.
This is my code:
class var safeArea : UIEdgeInsets
{
if #available(iOS 13, *) {
var keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
// <FIX> the above code doesn't work if the app comes back from background!
if (keyWindow == nil) {
keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
}
return keyWindow?.safeAreaInsets ?? UIEdgeInsets()
}
else {
guard let keyWindow = UIApplication.shared.keyWindow else { return UIEdgeInsets() }
return keyWindow.safeAreaInsets
}
}
- (UIWindow *)mainWindow {
NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
for (UIWindow *window in frontToBackWindows) {
BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
BOOL windowIsVisible = !window.hidden && window.alpha > 0;
BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal);
BOOL windowKeyWindow = window.isKeyWindow;
if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) {
return window;
}
}
return nil;
}
I faced the issue when .foregroundActive scenes were empty
So here is my workaround
public extension UIWindow {
#objc
static var main: UIWindow {
// Here we sort all the scenes in order to work around the case
// when no .foregroundActive scenes available and we need to look through
// all connectedScenes in order to find the most suitable one
let connectedScenes = UIApplication.shared.connectedScenes
.sorted { lhs, rhs in
let lhs = lhs.activationState
let rhs = rhs.activationState
switch lhs {
case .foregroundActive:
return true
case .foregroundInactive:
return rhs == .background || rhs == .unattached
case .background:
return rhs == .unattached
case .unattached:
return false
#unknown default:
return false
}
}
.compactMap { $0 as? UIWindowScene }
guard connectedScenes.isEmpty == false else {
fatalError("Connected scenes is empty")
}
let mainWindow = connectedScenes
.flatMap { $0.windows }
.first(where: \.isKeyWindow)
guard let window = mainWindow else {
fatalError("Couldn't get main window")
}
return window
}
}
If your app has not been updated to adopt the Scene based app lifecycle, another simple way to get the active window object is via UIApplicationDelegate:
let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController
if you're using SwiftLint with 'first_where' rule and wanna to silence warring:
UIApplication.shared.windows.first(where: { $0.isKeyWindow })
An Objective C solution:
UIWindow *foundWindow = nil;
NSSet *scenes=[[UIApplication sharedApplication] connectedScenes];
NSArray *windows;
for(id aScene in scenes){ // it's an NSSet so you can't use the first object
windows=[aScene windows];
if([aScene activationState]==UISceneActivationStateForegroundActive)
break;
}
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
foundWindow = window;
break;
}
}
// and to find the parent viewController:
UIViewController* parentController = foundWindow.rootViewController;
while( parentController.presentedViewController &&
parentController != parentController.presentedViewController ){
parentController = parentController.presentedViewController;
}
My solution is the following, works in iOS 15
let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first
I alloc'ed a newWindow for a view, and set it [newWindow makeKeyAndVisible];
When finished using it, set it [newWindow resignKeyWindow];
and then try to show the original key-window directly by [UIApplication sharedApplication].keyWindow.
Everything is all right on iOS 12, but on iOS 13 the original key-window can't been normal shown. It shows a whole white screen.
I solved this problem by:
UIWindow *mainWindow = nil;
if ( #available(iOS 13.0, *) ) {
mainWindow = [UIApplication sharedApplication].windows.firstObject;
[mainWindow makeKeyWindow];
} else {
mainWindow = [UIApplication sharedApplication].keyWindow;
}
For iOS 16, I used the following:
let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow

How to access own window within SwiftUI view?

The goal is to have easy access to hosting window at any level of SwiftUI view hierarchy. The purpose might be different - close the window, resign first responder, replace root view or contentViewController. Integration with UIKit/AppKit also sometimes require path via window, so…
What I met here and tried before,
something like this
let keyWindow = shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
or via added in every SwiftUI view UIViewRepresentable/NSViewRepresentable to get the window using view.window looks ugly, heavy, and not usable.
Thus, how would I do that?
SwiftUI Lift-Cycle (SwiftUI 2+)
Here is a solution (tested with Xcode 13.4), to be brief only for iOS
We need application delegate to create scene configuration with our scene delegate class
#main
struct PlayOn_iOSApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
// ...
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
if connectingSceneSession.role == .windowApplication {
configuration.delegateClass = SceneDelegate.self
}
return configuration
}
}
Declare our SceneDelegate and confirm it to both (!!!+) UIWindowSceneDelegate and ObservableObject
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {
var window: UIWindow? // << contract of `UIWindowSceneDelegate`
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
self.window = windowScene.keyWindow // << store !!!
}
}
Now we can use our delegate anywhere (!!!) in view hierarchy as EnvironmentObject, because (bonus of confirming to ObservableObject) SwiftUI automatically injects it into ContentView
#EnvironmentObject var sceneDelegate: SceneDelegate
var body: some View {
// ...
.onAppear {
if let myWindow = sceneDelegate.window {
print(">> window: \(myWindow.description)")
}
}
}
Complete code in project is here
UIKit Life-Cycle
Here is the result of my experiments that looks appropriate for me, so one might find it helpful as well. Tested with Xcode 11.2 / iOS 13.2 / macOS 15.0
The idea is to use native SwiftUI Environment concept, because once injected environment value becomes available for entire view hierarchy automatically. So
Define Environment key. Note, it needs to remember to avoid reference cycling on kept window
struct HostingWindowKey: EnvironmentKey {
#if canImport(UIKit)
typealias WrappedValue = UIWindow
#elseif canImport(AppKit)
typealias WrappedValue = NSWindow
#else
#error("Unsupported platform")
#endif
typealias Value = () -> WrappedValue? // needed for weak link
static let defaultValue: Self.Value = { nil }
}
extension EnvironmentValues {
var hostingWindow: HostingWindowKey.Value {
get {
return self[HostingWindowKey.self]
}
set {
self[HostingWindowKey.self] = newValue
}
}
}
Inject hosting window in root ContentView in place of window creation (either in AppDelegate or in SceneDelegate, just once
// window created here
let contentView = ContentView()
.environment(\.hostingWindow, { [weak window] in
return window })
#if canImport(UIKit)
window.rootViewController = UIHostingController(rootView: contentView)
#elseif canImport(AppKit)
window.contentView = NSHostingView(rootView: contentView)
#else
#error("Unsupported platform")
#endif
use only where needed, just by declaring environment variable
struct ContentView: View {
#Environment(\.hostingWindow) var hostingWindow
var body: some View {
VStack {
Button("Action") {
// self.hostingWindow()?.close() // macOS
// self.hostingWindow()?.makeFirstResponder(nil) // macOS
// self.hostingWindow()?.resignFirstResponder() // iOS
// self.hostingWindow()?.rootViewController?.present(UIKitController(), animating: true)
}
}
}
}
Add the window as a property in an environment object. This can be an existing object that you use for other app-wide data.
final class AppData: ObservableObject {
let window: UIWindow? // Will be nil in SwiftUI previewers
init(window: UIWindow? = nil) {
self.window = window
}
}
Set the property when you create the environment object. Add the object to the view at the base of your view hierarchy, such as the root view.
let window = UIWindow(windowScene: windowScene) // Or however you initially get the window
let rootView = RootView().environmentObject(AppData(window: window))
Finally, use the window in your view.
struct MyView: View {
#EnvironmentObject private var appData: AppData
// Use appData.window in your view's body.
}
Access the current window by receiving NSWindow.didBecomeKeyNotification:
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)) { notification in
if let window = notification.object as? NSWindow {
// ...
}
}
At first I liked the answer given by #Asperi, but when trying it in my own environment I found it difficult to get working due to my need to know the root view at the time I create the window (hence I don't know the window at the time I create the root view). So I followed his example, but instead of an environment value I chose to use an environment object. This has much the same effect, but was easier for me to get working. The following is the code that I use. Note that I have created a generic class that creates an NSWindowController given a SwiftUI view. (Note that the userDefaultsManager is another object that I need in most of the windows in my application. But I think if you remove that line plus the appDelegate line you would end up with a solution that would work pretty much anywhere.)
class RootViewWindowController<RootView : View>: NSWindowController {
convenience init(_ title: String,
withView rootView: RootView,
andInitialSize initialSize: NSSize = NSSize(width: 400, height: 500))
{
let appDelegate: AppDelegate = NSApplication.shared.delegate as! AppDelegate
let windowWrapper = NSWindowWrapper()
let actualRootView = rootView
.frame(width: initialSize.width, height: initialSize.height)
.environmentObject(appDelegate.userDefaultsManager)
.environmentObject(windowWrapper)
let hostingController = NSHostingController(rootView: actualRootView)
let window = NSWindow(contentViewController: hostingController)
window.setContentSize(initialSize)
window.title = title
windowWrapper.rootWindow = window
self.init(window: window)
}
}
final class NSWindowWrapper: ObservableObject {
#Published var rootWindow: NSWindow? = nil
}
Then in my view where I need it (in order to close the window at the appropriate time), my struct begins as the following:
struct SubscribeToProFeaturesView: View {
#State var showingEnlargedImage = false
#EnvironmentObject var rootWindowWrapper: NSWindowWrapper
var body: some View {
VStack {
Text("Professional Version Upgrade")
.font(.headline)
VStack(alignment: .leading) {
And in the button where I need to close the window I have
self.rootWindowWrapper.rootWindow?.close()
It's not quite as clean as I would like it to be (I would prefer to have a solution where I did just say self.rootWindow?.close() instead of requiring the wrapper class), but it isn't bad and it allows me to create the rootView object before I create the window.
Instead of ProjectName_App use old fashioned AppDelegate approach as app entry point.
#main
final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
...
}
}
Then pass window as environment object. For example:
struct WindowKey: EnvironmentKey {
static let defaultValue: UIWindow? = nil
}
extension EnvironmentValues {
var window: WindowKey.Value {
get { return self[WindowKey.self] }
set { self[WindowKey.self] = newValue }
}
}
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
let rootView = RootView()
.environment(\.window, window)
window?.rootViewController = UIHostingController(rootView: rootView)
window?.makeKeyAndVisible()
}
}
And use it when need it.
struct ListCell: View {
#Environment(\.window) private var window
var body: some View {
Rectangle()
.onTapGesture(perform: share)
}
private func share() {
let vc = UIActivityViewController(activityItems: [], applicationActivities: nil)
window?.rootViewController?.present(vc, animated: true)
}
}
2022, macOS only
Maybe not best solution, but works well for me and enough universal for almost any situation
Usage:
someView()
.wndAccessor {
$0?.title = String(localized: "All you need to know about FileBo in ONE minute")
}
extension code:
import SwiftUI
#available(OSX 11.0, *)
public extension View {
func wndAccessor(_ act: #escaping (NSWindow?) -> () )
-> some View {
self.modifier(WndTitleConfigurer(act: act))
}
}
#available(OSX 11.0, *)
struct WndTitleConfigurer: ViewModifier {
let act: (NSWindow?) -> ()
#State var window: NSWindow? = nil
func body(content: Content) -> some View {
content
.getWindow($window)
.onChange(of: window, perform: act )
}
}
//////////////////////////////
///HELPERS
/////////////////////////////
// Don't use this:
// Usage:
//.getWindow($window)
//.onChange(of: window) { _ in
// if let wnd = window {
// wnd.level = .floating
// }
//}
#available(OSX 11.0, *)
private extension View {
func getWindow(_ wnd: Binding<NSWindow?>) -> some View {
self.background(WindowAccessor(window: wnd))
}
}
#available(OSX 11.0, *)
private struct WindowAccessor: NSViewRepresentable {
#Binding var window: NSWindow?
public func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
self.window = view.window
}
return view
}
public func updateNSView(_ nsView: NSView, context: Context) {}
}

Embed Unity inside iOS in own ViewController

Using Unity 2019.3.0f3 and its Unity as a library feature I'm trying to embed a Unity project inside my iOS application.
Unity officially only supports full screen rendering. Nevertheless I'm looking for a way around that restriction.
In previous versions of Unity i successfully used swift-unity to do the integration. Within this approach it is easy to just get the View where Unity is rendering to (using UnityGetGLView()). I had no problems regarding stability or resources.
Using the new library approach, every time I try to access the UnityView, unity forces it's complete Window as keyWindow.
I tried accessing the UnityView in my own ViewController using
if let unityView = UnityFramework.getInstance()?.appController()?.rootViewController.view {
// insert subview at index 0 ensures unity view is behind current UI view
view?.insertSubview(unityView, at: 0)
}
But that immediately activates the complete unity-window and hides my parenting UITabBarController.
Trying to make the UnityFramework.getInstance()?.appController()?.rootViewController a child of my UITabBarController failed with the same result.
Furthermore it is not possible to add a child ViewController. Only adding subviews seems possible.
Does anybody know where that window-behaviour is located or how i can access the UnityView (or the RootViewController) and use it freely?
I found a solution to the problem based on this approach from the unity forum. Using this approach I'm able to use the UnityViewController as a child in my own TabBarController.
The approach is working for Unity 2019.3.0f3, but I'm not sure if it will work in future versions. It feels like Unity tries to actively prevent such use. Then again I found hints in comments in the library-code that would suggest that a modified ViewController-Hierarchy was at least contemplated e.g. in UnityAppController+ViewHandling.h. But the instructions are unclear and methods with the hinted names don't exist.
Solution
1. Create UnityEmbeddedSwift.swift
The official example App provided by Unity is a real mess. I ended up using the UnityEmbeddedSwift.swift from the linked forum post with additions for pausing. This class encapsulates all Unity-related functionality in one clean class.
//
// UnityEmbeddedSwift.swift
// Native
//
// Created by NSWell on 2019/12/19.
// Copyright © 2019 WEACW. All rights reserved.
//
//
// Created by Simon Tysland on 19/08/2019.
// Copyright © 2019 Simon Tysland. All rights reserved.
//
import Foundation
import UnityFramework
class UnityEmbeddedSwift: UIResponder, UIApplicationDelegate, UnityFrameworkListener {
private struct UnityMessage {
let objectName : String?
let methodName : String?
let messageBody : String?
}
private static var instance : UnityEmbeddedSwift!
private var ufw : UnityFramework!
private static var hostMainWindow : UIWindow! // Window to return to when exiting Unity window
private static var launchOpts : [UIApplication.LaunchOptionsKey: Any]?
private static var cachedMessages = [UnityMessage]()
// MARK: - Static functions (that can be called from other scripts)
static func getUnityRootViewController() -> UIViewController! {
return instance.ufw.appController()?.rootViewController
}
static func getUnityView() -> UIView! {
return instance.ufw.appController()?.rootViewController?.view
}
static func setHostMainWindow(_ hostMainWindow : UIWindow?) {
UnityEmbeddedSwift.hostMainWindow = hostMainWindow
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
static func setLaunchinOptions(_ launchingOptions : [UIApplication.LaunchOptionsKey: Any]?) {
UnityEmbeddedSwift.launchOpts = launchingOptions
}
static func showUnity() {
if(UnityEmbeddedSwift.instance == nil || UnityEmbeddedSwift.instance.unityIsInitialized() == false) {
UnityEmbeddedSwift().initUnityWindow()
}
else {
UnityEmbeddedSwift.instance.showUnityWindow()
}
}
static func hideUnity() {
UnityEmbeddedSwift.instance?.hideUnityWindow()
}
static func pauseUnity() {
UnityEmbeddedSwift.instance?.pauseUnityWindow()
}
static func unpauseUnity() {
UnityEmbeddedSwift.instance?.unpauseUnityWindow()
}
static func unloadUnity() {
UnityEmbeddedSwift.instance?.unloadUnityWindow()
}
static func sendUnityMessage(_ objectName : String, methodName : String, message : String) {
let msg : UnityMessage = UnityMessage(objectName: objectName, methodName: methodName, messageBody: message)
// Send the message right away if Unity is initialized, else cache it
if(UnityEmbeddedSwift.instance != nil && UnityEmbeddedSwift.instance.unityIsInitialized()) {
UnityEmbeddedSwift.instance.ufw.sendMessageToGO(withName: msg.objectName, functionName: msg.methodName, message: msg.messageBody)
}
else {
UnityEmbeddedSwift.cachedMessages.append(msg)
}
}
// MARK - Callback from UnityFrameworkListener
func unityDidUnload(_ notification: Notification!) {
ufw.unregisterFrameworkListener(self)
ufw = nil
UnityEmbeddedSwift.hostMainWindow?.makeKeyAndVisible()
}
// MARK: - Private functions (called within the class)
private func unityIsInitialized() -> Bool {
return ufw != nil && (ufw.appController() != nil)
}
private func initUnityWindow() {
if unityIsInitialized() {
showUnityWindow()
return
}
ufw = UnityFrameworkLoad()!
ufw.setDataBundleId("com.unity3d.framework")
ufw.register(self)
// NSClassFromString("FrameworkLibAPI")?.registerAPIforNativeCalls(self)
ufw.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: UnityEmbeddedSwift.launchOpts)
sendUnityMessageToGameObject()
UnityEmbeddedSwift.instance = self
}
private func showUnityWindow() {
if unityIsInitialized() {
ufw.showUnityWindow()
sendUnityMessageToGameObject()
}
}
private func hideUnityWindow() {
if(UnityEmbeddedSwift.hostMainWindow == nil) {
print("WARNING: hostMainWindow is nil! Cannot switch from Unity window to previous window")
}
else {
UnityEmbeddedSwift.hostMainWindow?.makeKeyAndVisible()
}
}
private func pauseUnityWindow() {
ufw.pause(true)
}
private func unpauseUnityWindow() {
ufw.pause(false)
}
private func unloadUnityWindow() {
if unityIsInitialized() {
UnityEmbeddedSwift.cachedMessages.removeAll()
ufw.unloadApplication()
}
}
private func sendUnityMessageToGameObject() {
if (UnityEmbeddedSwift.cachedMessages.count >= 0 && unityIsInitialized())
{
for msg in UnityEmbeddedSwift.cachedMessages {
ufw.sendMessageToGO(withName: msg.objectName, functionName: msg.methodName, message: msg.messageBody)
}
UnityEmbeddedSwift.cachedMessages.removeAll()
}
}
private func UnityFrameworkLoad() -> UnityFramework? {
let bundlePath: String = Bundle.main.bundlePath + "/Frameworks/UnityFramework.framework"
let bundle = Bundle(path: bundlePath )
if bundle?.isLoaded == false {
bundle?.load()
}
let ufw = bundle?.principalClass?.getInstance()
if ufw?.appController() == nil {
// unity is not initialized
// ufw?.executeHeader = &mh_execute_header
let machineHeader = UnsafeMutablePointer<MachHeader>.allocate(capacity: 1)
machineHeader.pointee = _mh_execute_header
ufw!.setExecuteHeader(machineHeader)
}
return ufw
}
}
2. Modify AppDelegate.swift
Sets window and launch options needed by UnityEmbeddedSwift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UnityEmbeddedSwift.setHostMainWindow(window)
UnityEmbeddedSwift.setLaunchinOptions(launchOptions)
return true
}
3. Create RootTabBarController.swift
This class sets up the hierarchy.
It is important to use the UnityRootViewController right after calling UnityEmbeddedSwift.showUnity().
The Tab-Switching is not nice, but if it is missing Unity will pause (or freeze?) during loading. The timing seems to depend on the Unity-Projects loading time. It can be faster for small projects and needs more time for larger projects.
import UIKit
class RootTabBarController: UITabBarController, UITabBarControllerDelegate {
var unityNC: UINavigationController?
var nativeNC: UINavigationController?
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
// start unity and immediatly set as rootViewController
// this loophole makes it possible to run unity in the same window
UnityEmbeddedSwift.showUnity()
let unityViewController = UnityEmbeddedSwift.getUnityRootViewController()!
unityViewController.navigationItem.title = "Unity"
unityNC = UINavigationController.init(rootViewController: unityViewController)
unityNC?.tabBarItem.title = "Unity"
let nativeViewController = UIViewController.init()
nativeViewController.view.backgroundColor = UIColor.darkGray
nativeViewController.navigationItem.title = "Native"
nativeNC = UINavigationController.init(rootViewController: nativeViewController)
nativeNC?.tabBarItem.title = "Native"
viewControllers = [unityNC!, nativeNC!]
// select other tab and reselect first tab to unfreeze unity-loading
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
self.selectedIndex = 1
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01, execute: {
self.selectedIndex = 0
})
})
}
// MARK: - UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
// pause unity if unity-tab is not selected
if viewController != unityNC {
UnityEmbeddedSwift.pauseUnity()
} else {
UnityEmbeddedSwift.unpauseUnity()
}
}
}
4. Modify Main.storyboard
Modify the storyboard to start with the RootTabBarController.
For anyone who is still interested in preventing the freezing, I am building on top of aalmigthy's answer:
You do not need to add a TabBar controller and switch between the tabs. All you need to do is:
Add the Unity view as a subview
Send the subview to back
Here's the modified ViewController class (no need for a tab bar):
import UIKit
class HybridViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UnityEmbeddedSwift.showUnity()
let uView = UnityEmbeddedSwift.getUnityView()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
self.view.addSubview(uView!)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
self.view.sendSubviewToBack(uView!)
})
})
}
}

ios - Dynamically edit 3d touch shortcut list

I want to add a "Continue" shortcut to my game. But when user will finish my game completely I want this to be either removed or replaced by another shortcut. Is this possible? I know 3d touch is handled by ios system, but maybe there are still some options
There are two ways to create shortcuts - dynamic and static.
Static are added to the plist and never change.
Dynamic can be added and removed in code.
It sounds like you want a dynamic shortcut, so here's roughly how you would do that:
To add:
if #available(iOS 9.0, *) {
if (UIApplication.sharedApplication().shortcutItems?.filter({ $0.type == "com.app.myshortcut" }).first == nil) {
UIApplication.sharedApplication().shortcutItems?.append(UIMutableApplicationShortcutItem(type: "com.app.myshortcut", localizedTitle: "Shortcut Title"))
}
}
To remove:
if #available(iOS 9.0, *) {
if let shortcutItem = UIApplication.sharedApplication().shortcutItems?.filter({ $0.type == "com.app.myshortcut" }).first {
let index = UIApplication.sharedApplication().shortcutItems?.indexOf(shortcutItem)
UIApplication.sharedApplication().shortcutItems?.removeAtIndex(index!)
}
}
You can then handle the shortcut by checking for it in the app delegate method:
#available(iOS 9.0, *)
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.app.myshortcut" {
// Do something
}
}
Don't forget to check for iOS9 and 3d Touch compatibility.
You can find Apple developer 3d touch pages here:
https://developer.apple.com/ios/3d-touch/
And specifically dynamic shortcuts here:
https://developer.apple.com/library/ios/samplecode/ApplicationShortcuts/Listings/ApplicationShortcuts_AppDelegate_swift.html#//apple_ref/doc/uid/TP40016545-ApplicationShortcuts_AppDelegate_swift-DontLinkElementID_3
Here's a handy class to trigger segues off of 3D touch off of your app icon. Of course you could trigger any action, but this is probably the most common. It then syncs itself when the app comes up or goes to the background. I'm using this to trigger a "My Projects" section only after the user has generated one (VisualizerProject.currentProject.images.count > 0).
class AppShortcut : UIMutableApplicationShortcutItem {
var segue:String
init(type:String, title:String, icon:String, segue:String) {
self.segue = segue
let translatedTitle = NSLocalizedString(title, comment:title)
let iconImage = UIApplicationShortcutIcon(templateImageName: icon)
super.init(type: type, localizedTitle:translatedTitle, localizedSubtitle:nil, icon:iconImage, userInfo:nil)
}
}
class AppShortcuts {
static var shortcuts:[AppShortcut] = []
class func sync() {
var newShortcuts:[AppShortcut] = []
//reverse order for display
newShortcuts.append(AppShortcut(type: "find-color", title: "Find Color", icon:"ic_settings_black_24px", segue: "showColorFinder"))
newShortcuts.append(AppShortcut(type: "samples", title: "Sample Rooms", icon:"ic_photo_black_24px", segue: "showSamples"))
//conditionally add an item like this:
if (VisualizerProject.currentProject.images.count > 0) {
newShortcuts.append(AppShortcut(type: "projects", title: "My Projects", icon:"ic_settings_black_24px", segue: "showProjects"))
}
newShortcuts.append(AppShortcut(type: "visualizer", title: "Paint Visualizer", icon:"ic_photo_camera_black_24px", segue: "showPainter"))
UIApplication.sharedApplication().shortcutItems = newShortcuts
shortcuts = newShortcuts
}
class func performShortcut(window:UIWindow, shortcut:UIApplicationShortcutItem) {
sync()
if let shortcutItem = shortcuts.filter({ $0.type == shortcut.type}).first {
if let rootNavigationViewController = window.rootViewController as? UINavigationController,
let landingViewController = rootNavigationViewController.viewControllers.first {
//Pop to root view controller so that approperiete segue can be performed
rootNavigationViewController.popToRootViewControllerAnimated(false)
landingViewController.performSegueWithIdentifier(shortcutItem.segue, sender: self)
}
}
}
}
Then in your app delegate, add the sync and perform shortcut calls
func applicationDidEnterBackground(application: UIApplication) {
AppShortcuts.sync()
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
AppShortcuts.sync()
}
#available(iOS 9.0, *)
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if let window = self.window {
AppShortcuts.performShortcut(window, shortcut: shortcutItem)
}
}
I assume you're talking about the so-called Quick Actions a user can call by force-touching your app's icon on his home screen. You can dynamically create and update them right from your code. The best way to learn about all the possibilities is looking at Apple's sample code.
[UIApplication sharedApplication].shortcutItems = #[];
worked for me. the other answer that suggests removing something from the array didn't work as shortcutItems is not mutable.

Resources