| Flutter iOS Embedder |

FlutterPluginAppLifeCycleDelegate.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
6
7 #include "flutter/fml/paths.h"
8 #include "flutter/lib/ui/plugins/callback_cache.h"
9 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
13
14 FLUTTER_ASSERT_ARC
15
16 static const char* kCallbackCacheSubDir = "Library/Caches/";
17
18 static const SEL kSelectorsHandledByPlugins[] = {
19 @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
20 @selector(application:performFetchWithCompletionHandler:)};
21
22 @interface FlutterPluginAppLifeCycleDelegate ()
23 - (void)handleDidEnterBackground:(NSNotification*)notification
24 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
25 - (void)handleWillEnterForeground:(NSNotification*)notification
26 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
27 - (void)handleWillResignActive:(NSNotification*)notification
28 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
29 - (void)handleDidBecomeActive:(NSNotification*)notification
30 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
31 - (void)handleWillTerminate:(NSNotification*)notification
32 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
33
34 @property(nonatomic, assign) BOOL didForwardApplicationWillLaunch;
35 @property(nonatomic, assign) BOOL didForwardApplicationDidLaunch;
36 @end
37
38 @implementation FlutterPluginAppLifeCycleDelegate {
39 UIBackgroundTaskIdentifier _debugBackgroundTask;
40
41 // Weak references to registered plugins.
42 NSPointerArray* _delegates;
43 }
44
45 - (void)addObserverFor:(NSString*)name selector:(SEL)selector {
46 [[NSNotificationCenter defaultCenter] addObserver:self selector:selector name:name object:nil];
47 }
48
49 - (instancetype)init {
50 if (self = [super init]) {
51 std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
52 [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
53 if (FlutterSharedApplication.isAvailable) {
54 [self addObserverFor:UIApplicationDidEnterBackgroundNotification
55 selector:@selector(handleDidEnterBackground:)];
56 [self addObserverFor:UIApplicationWillEnterForegroundNotification
57 selector:@selector(handleWillEnterForeground:)];
58 [self addObserverFor:UIApplicationWillResignActiveNotification
59 selector:@selector(handleWillResignActive:)];
60 [self addObserverFor:UIApplicationDidBecomeActiveNotification
61 selector:@selector(handleDidBecomeActive:)];
62 [self addObserverFor:UIApplicationWillTerminateNotification
63 selector:@selector(handleWillTerminate:)];
64 }
65 _delegates = [NSPointerArray weakObjectsPointerArray];
66 _debugBackgroundTask = UIBackgroundTaskInvalid;
67 }
68 return self;
69 }
70
71 static BOOL IsPowerOfTwo(NSUInteger x) {
72 return x != 0 && (x & (x - 1)) == 0;
73 }
74
75 - (BOOL)isSelectorAddedDynamically:(SEL)selector {
76 for (const SEL& aSelector : kSelectorsHandledByPlugins) {
77 if (selector == aSelector) {
78 return YES;
79 }
80 }
81 return NO;
82 }
83
84 - (BOOL)hasPluginThatRespondsToSelector:(SEL)selector {
85 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
86 if (!delegate) {
87 continue;
88 }
89 if ([delegate respondsToSelector:selector]) {
90 return YES;
91 }
92 }
93 return NO;
94 }
95
96 - (BOOL)appSupportsSceneLifecycle {
97 // When UIScene lifecycle is being used, some application lifecycle events are not call by UIKit.
98 // However, the notifications are still sent. When a Flutter app has been migrated to UIScene,
99 // Flutter should not use the notifications to forward application events to plugins since they
100 // are not expected to be called.
101 // See https://flutter.dev/go/ios-ui-scene-lifecycle-migration?tab=t.0#heading=h.eq8gyd4ds50u
102 return FlutterSharedApplication.hasSceneDelegate;
103 }
104
105 - (BOOL)pluginSupportsSceneLifecycle:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
106 // The fallback is unnecessary if the plugin conforms to FlutterSceneLifeCycleDelegate.
107 // This means that the plugin has migrated to scene lifecycle events and shouldn't require
108 // application events. However, the plugin may still have the application event implemented to
109 // maintain compatibility with un-migrated apps, which is why the fallback should be checked
110 // before checking that the delegate responds to the selector.
111 return [delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleDelegate)];
112 }
113
114 - (void)addDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
115 [_delegates addPointer:(__bridge void*)delegate];
116 if (IsPowerOfTwo([_delegates count])) {
117 [_delegates compact];
118 }
119 }
120
121 - (void)sceneFallbackDidFinishLaunchingApplication:(UIApplication*)application {
122 // If the application:didFinishingLaunchingWithOptions: event has already been sent to plugins, do
123 // not send again.
124 if (self.didForwardApplicationDidLaunch) {
125 return;
126 }
127 // Send nil launchOptions since UIKit sends nil when UIScene is enabled.
128 [self application:application didFinishLaunchingWithOptions:@{}];
129 }
130
131 - (void)sceneFallbackWillFinishLaunchingApplication:(UIApplication*)application {
132 // If the application:willFinishLaunchingWithOptions: event has already been sent to plugins, do
133 // not send again.
134 if (self.didForwardApplicationWillLaunch) {
135 return;
136 }
137 // If the application:didFinishingLaunchingWithOptions: event has already been sent to plugins, do
138 // not send willFinishLaunchingWithOptions since it should happen before, not after.
139 if (self.didForwardApplicationDidLaunch) {
140 return;
141 }
142 // Send nil launchOptions since UIKit sends nil when UIScene is enabled.
143 [self application:application willFinishLaunchingWithOptions:@{}];
144 }
145
146 - (BOOL)application:(UIApplication*)application
147 didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
148 if (_delegates.count > 0) {
149 self.didForwardApplicationDidLaunch = YES;
150 }
151 return [self application:application
152 didFinishLaunchingWithOptions:launchOptions
153 isFallbackForScene:NO];
154 }
155
156 - (BOOL)sceneWillConnectFallback:(UISceneConnectionOptions*)connectionOptions {
157 UIApplication* application = FlutterSharedApplication.application;
158 if (!application) {
159 return NO;
160 }
161 NSDictionary<UIApplicationLaunchOptionsKey, id>* convertedLaunchOptions =
162 ConvertConnectionOptions(connectionOptions);
163 if (convertedLaunchOptions.count == 0) {
164 // Only use fallback if there are meaningful launch options.
165 return NO;
166 }
167 if (![self application:application
168 didFinishLaunchingWithOptions:convertedLaunchOptions
169 isFallbackForScene:YES]) {
170 return YES;
171 }
172 return NO;
173 }
174
175 - (BOOL)application:(UIApplication*)application
176 didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
177 isFallbackForScene:(BOOL)isFallback {
178 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
179 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
180 continue;
181 }
182 if ([delegate respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) {
183 if (![delegate application:application didFinishLaunchingWithOptions:launchOptions]) {
184 return NO;
185 }
186 }
187 }
188 return YES;
189 }
190
191 /* Makes a best attempt to convert UISceneConnectionOptions from the scene event
192 * (`scene:willConnectToSession:options:`) to a NSDictionary of options used to the application
193 * lifecycle event.
194 *
195 * For more information on UISceneConnectionOptions, see
196 * https://developer.apple.com/documentation/uikit/uiscene/connectionoptions.
197 *
198 * For information about the possible keys in the NSDictionary and how to handle them, see
199 * https://developer.apple.com/documentation/uikit/uiapplication/launchoptionskey
200 */
201 static NSDictionary<UIApplicationLaunchOptionsKey, id>* ConvertConnectionOptions(
202 UISceneConnectionOptions* connectionOptions) {
203 NSMutableDictionary<UIApplicationLaunchOptionsKey, id>* convertedOptions =
204 [NSMutableDictionary dictionary];
205
206 if (connectionOptions.shortcutItem) {
207 convertedOptions[UIApplicationLaunchOptionsShortcutItemKey] = connectionOptions.shortcutItem;
208 }
209 if (connectionOptions.sourceApplication) {
210 convertedOptions[UIApplicationLaunchOptionsSourceApplicationKey] =
211 connectionOptions.sourceApplication;
212 }
213 if (connectionOptions.URLContexts.anyObject.URL) {
214 convertedOptions[UIApplicationLaunchOptionsURLKey] =
215 connectionOptions.URLContexts.anyObject.URL;
216 }
217 return convertedOptions;
218 }
219
220 - (BOOL)application:(UIApplication*)application
221 willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
222 if (_delegates.count > 0) {
223 self.didForwardApplicationWillLaunch = YES;
224 }
225 flutter::DartCallbackCache::LoadCacheFromDisk();
226 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
227 if (!delegate) {
228 continue;
229 }
230 if ([delegate respondsToSelector:_cmd]) {
231 if (![delegate application:application willFinishLaunchingWithOptions:launchOptions]) {
232 return NO;
233 }
234 }
235 }
236 return YES;
237 }
238
239 - (void)handleDidEnterBackground:(NSNotification*)notification
240 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
241 if ([self appSupportsSceneLifecycle]) {
242 return;
243 }
244 UIApplication* application = [UIApplication sharedApplication];
245 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
246 // The following keeps the Flutter session alive when the device screen locks
247 // in debug mode. It allows continued use of features like hot reload and
248 // taking screenshots once the device unlocks again.
249 //
250 // Note the name is not an identifier and multiple instances can exist.
251 _debugBackgroundTask = [application
252 beginBackgroundTaskWithName:@"Flutter debug task"
253 expirationHandler:^{
254 if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
255 [application endBackgroundTask:_debugBackgroundTask];
256 _debugBackgroundTask = UIBackgroundTaskInvalid;
257 }
258 [FlutterLogger
259 logWarning:@"\nThe OS has terminated the Flutter debug connection for being "
260 "inactive in the background for too long.\n\n"
261 "There are no errors with your Flutter application.\n\n"
262 "To reconnect, launch your application again via 'flutter run'"];
263 }];
264 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
265 [self applicationDidEnterBackground:application isFallbackForScene:NO];
266 }
267
268 - (void)sceneDidEnterBackgroundFallback {
269 UIApplication* application = FlutterSharedApplication.application;
270 if (!application) {
271 return;
272 }
273 [self applicationDidEnterBackground:application isFallbackForScene:YES];
274 }
275
276 - (void)applicationDidEnterBackground:(UIApplication*)application
277 isFallbackForScene:(BOOL)isFallback {
278 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
279 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
280 continue;
281 }
282 if ([delegate respondsToSelector:@selector(applicationDidEnterBackground:)]) {
283 [delegate applicationDidEnterBackground:application];
284 }
285 }
286 }
287
288 - (void)handleWillEnterForeground:(NSNotification*)notification
289 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
290 if ([self appSupportsSceneLifecycle]) {
291 return;
292 }
293 UIApplication* application = [UIApplication sharedApplication];
294 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
295 if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
296 [application endBackgroundTask:_debugBackgroundTask];
297 _debugBackgroundTask = UIBackgroundTaskInvalid;
298 }
299 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
300 [self applicationWillEnterForeground:application isFallbackForScene:NO];
301 }
302
303 - (void)sceneWillEnterForegroundFallback {
304 UIApplication* application = FlutterSharedApplication.application;
305 if (!application) {
306 return;
307 }
308 [self applicationWillEnterForeground:application isFallbackForScene:YES];
309 }
310
311 - (void)applicationWillEnterForeground:(UIApplication*)application
312 isFallbackForScene:(BOOL)isFallback {
313 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
314 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
315 continue;
316 }
317 if ([delegate respondsToSelector:@selector(applicationWillEnterForeground:)]) {
318 [delegate applicationWillEnterForeground:application];
319 }
320 }
321 }
322
323 - (void)handleWillResignActive:(NSNotification*)notification
324 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
325 if ([self appSupportsSceneLifecycle]) {
326 return;
327 }
328 UIApplication* application = [UIApplication sharedApplication];
329 [self applicationWillResignActive:application isFallbackForScene:NO];
330 }
331
332 - (void)sceneWillResignActiveFallback {
333 UIApplication* application = FlutterSharedApplication.application;
334 if (!application) {
335 return;
336 }
337 [self applicationWillResignActive:application isFallbackForScene:YES];
338 }
339
340 - (void)applicationWillResignActive:(UIApplication*)application
341 isFallbackForScene:(BOOL)isFallback {
342 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
343 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
344 continue;
345 }
346 if ([delegate respondsToSelector:@selector(applicationWillResignActive:)]) {
347 [delegate applicationWillResignActive:application];
348 }
349 }
350 }
351
352 - (void)handleDidBecomeActive:(NSNotification*)notification
353 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
354 if ([self appSupportsSceneLifecycle]) {
355 return;
356 }
357 UIApplication* application = [UIApplication sharedApplication];
358 [self applicationDidBecomeActive:application isFallbackForScene:NO];
359 }
360
361 - (void)sceneDidBecomeActiveFallback {
362 UIApplication* application = FlutterSharedApplication.application;
363 if (!application) {
364 return;
365 }
366 [self applicationDidBecomeActive:application isFallbackForScene:YES];
367 }
368
369 - (void)applicationDidBecomeActive:(UIApplication*)application isFallbackForScene:(BOOL)isFallback {
370 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
371 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
372 continue;
373 }
374 if ([delegate respondsToSelector:@selector(applicationDidBecomeActive:)]) {
375 [delegate applicationDidBecomeActive:application];
376 }
377 }
378 }
379
380 - (void)handleWillTerminate:(NSNotification*)notification
381 NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
382 UIApplication* application = [UIApplication sharedApplication];
383 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
384 if (!delegate) {
385 continue;
386 }
387 if ([delegate respondsToSelector:@selector(applicationWillTerminate:)]) {
388 [delegate applicationWillTerminate:application];
389 }
390 }
391 }
392
393 #pragma GCC diagnostic push
394 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
395 - (void)application:(UIApplication*)application
396 didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
397 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
398 if (!delegate) {
399 continue;
400 }
401 if ([delegate respondsToSelector:_cmd]) {
402 [delegate application:application didRegisterUserNotificationSettings:notificationSettings];
403 }
404 }
405 }
406 #pragma GCC diagnostic pop
407
408 - (void)application:(UIApplication*)application
409 didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
410 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
411 if (!delegate) {
412 continue;
413 }
414 if ([delegate respondsToSelector:_cmd]) {
415 [delegate application:application
416 didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
417 }
418 }
419 }
420
421 - (void)application:(UIApplication*)application
422 didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
423 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
424 if (!delegate) {
425 continue;
426 }
427 if ([delegate respondsToSelector:_cmd]) {
428 [delegate application:application didFailToRegisterForRemoteNotificationsWithError:error];
429 }
430 }
431 }
432
433 - (void)application:(UIApplication*)application
434 didReceiveRemoteNotification:(NSDictionary*)userInfo
435 fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
436 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
437 if (!delegate) {
438 continue;
439 }
440 if ([delegate respondsToSelector:_cmd]) {
441 if ([delegate application:application
442 didReceiveRemoteNotification:userInfo
443 fetchCompletionHandler:completionHandler]) {
444 return;
445 }
446 }
447 }
448 }
449
450 #pragma GCC diagnostic push
451 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
452 - (void)application:(UIApplication*)application
453 didReceiveLocalNotification:(UILocalNotification*)notification {
454 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
455 if (!delegate) {
456 continue;
457 }
458 if ([delegate respondsToSelector:_cmd]) {
459 [delegate application:application didReceiveLocalNotification:notification];
460 }
461 }
462 }
463 #pragma GCC diagnostic pop
464
465 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
466 willPresentNotification:(UNNotification*)notification
467 withCompletionHandler:
468 (void (^)(UNNotificationPresentationOptions options))completionHandler {
469 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
470 if ([delegate respondsToSelector:_cmd]) {
471 [delegate userNotificationCenter:center
472 willPresentNotification:notification
473 withCompletionHandler:completionHandler];
474 }
475 }
476 }
477
478 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
479 didReceiveNotificationResponse:(UNNotificationResponse*)response
480 withCompletionHandler:(void (^)(void))completionHandler {
481 for (id<FlutterApplicationLifeCycleDelegate> delegate in _delegates) {
482 if ([delegate respondsToSelector:_cmd]) {
483 [delegate userNotificationCenter:center
484 didReceiveNotificationResponse:response
485 withCompletionHandler:completionHandler];
486 }
487 }
488 }
489
490 - (BOOL)application:(UIApplication*)application
491 openURL:(NSURL*)url
492 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
493 return [self application:application openURL:url options:options isFallbackForScene:NO];
494 }
495
496 - (BOOL)sceneFallbackOpenURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
497 for (UIOpenURLContext* context in URLContexts) {
498 if ([self application:FlutterSharedApplication.application
499 openURL:context.URL
500 options:ConvertOptions(context.options)
501 isFallbackForScene:YES]) {
502 return YES;
503 };
504 };
505 return NO;
506 }
507
508 - (BOOL)application:(UIApplication*)application
509 openURL:(NSURL*)url
510 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
511 isFallbackForScene:(BOOL)isFallback {
512 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
513 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
514 continue;
515 }
516 if ([delegate respondsToSelector:@selector(application:openURL:options:)]) {
517 if ([delegate application:application openURL:url options:options]) {
518 return YES;
519 }
520 }
521 }
522 return NO;
523 }
524
525 /* Converts UISceneOpenURLOptions from the scene event (`sceneFallbackOpenURLContexts`) to a
526 * NSDictionary of options used to the application lifecycle event.
527 *
528 * For more information on UISceneOpenURLOptions, see
529 * https://developer.apple.com/documentation/uikit/uiopenurlcontext/options.
530 *
531 * For information about the possible keys in the NSDictionary and how to handle them, see
532 * https://developer.apple.com/documentation/uikit/uiapplication/openurloptionskey
533 */
534 static NSDictionary<UIApplicationOpenURLOptionsKey, id>* ConvertOptions(
535 UISceneOpenURLOptions* options) {
536 NSMutableDictionary<UIApplicationOpenURLOptionsKey, id>* convertedOptions =
537 [NSMutableDictionary dictionary];
538 if (options.sourceApplication) {
539 convertedOptions[UIApplicationOpenURLOptionsSourceApplicationKey] = options.sourceApplication;
540 }
541 if (options.annotation) {
542 convertedOptions[UIApplicationOpenURLOptionsAnnotationKey] = options.annotation;
543 }
544 convertedOptions[UIApplicationOpenURLOptionsOpenInPlaceKey] = @(options.openInPlace);
545 if (@available(iOS 14.5, *)) {
546 if (options.eventAttribution) {
547 convertedOptions[UIApplicationOpenURLOptionsEventAttributionKey] = options.eventAttribution;
548 }
549 }
550 return convertedOptions;
551 }
552
553 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
554 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
555 if (!delegate) {
556 continue;
557 }
558 if ([delegate respondsToSelector:_cmd]) {
559 if ([delegate application:application handleOpenURL:url]) {
560 return YES;
561 }
562 }
563 }
564 return NO;
565 }
566
567 - (BOOL)application:(UIApplication*)application
568 openURL:(NSURL*)url
569 sourceApplication:(NSString*)sourceApplication
570 annotation:(id)annotation {
571 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
572 if (!delegate) {
573 continue;
574 }
575 if ([delegate respondsToSelector:_cmd]) {
576 if ([delegate application:application
577 openURL:url
578 sourceApplication:sourceApplication
579 annotation:annotation]) {
580 return YES;
581 }
582 }
583 }
584 return NO;
585 }
586
587 - (void)application:(UIApplication*)application
588 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
589 completionHandler:(void (^)(BOOL succeeded))completionHandler {
590 [self application:application
591 performActionForShortcutItem:shortcutItem
592 completionHandler:completionHandler
593 isFallbackForScene:NO];
594 }
595
596 - (BOOL)sceneFallbackPerformActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
597 completionHandler:(void (^)(BOOL succeeded))completionHandler {
598 UIApplication* application = FlutterSharedApplication.application;
599 if (!application) {
600 return NO;
601 }
602 return [self application:application
603 performActionForShortcutItem:shortcutItem
604 completionHandler:completionHandler
605 isFallbackForScene:YES];
606 }
607
608 - (BOOL)application:(UIApplication*)application
609 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
610 completionHandler:(void (^)(BOOL succeeded))completionHandler
611 isFallbackForScene:(BOOL)isFallback {
612 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
613 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
614 continue;
615 }
616 if ([delegate respondsToSelector:@selector(application:
617 performActionForShortcutItem:completionHandler:)]) {
618 if ([delegate application:application
619 performActionForShortcutItem:shortcutItem
620 completionHandler:completionHandler]) {
621 return YES;
622 }
623 }
624 }
625 return NO;
626 }
627
628 - (BOOL)application:(UIApplication*)application
629 handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
630 completionHandler:(nonnull void (^)())completionHandler {
631 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
632 if (!delegate) {
633 continue;
634 }
635 if ([delegate respondsToSelector:_cmd]) {
636 if ([delegate application:application
637 handleEventsForBackgroundURLSession:identifier
638 completionHandler:completionHandler]) {
639 return YES;
640 }
641 }
642 }
643 return NO;
644 }
645
646 - (BOOL)application:(UIApplication*)application
647 performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
648 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
649 if (!delegate) {
650 continue;
651 }
652 if ([delegate respondsToSelector:_cmd]) {
653 if ([delegate application:application performFetchWithCompletionHandler:completionHandler]) {
654 return YES;
655 }
656 }
657 }
658 return NO;
659 }
660
661 - (BOOL)application:(UIApplication*)application
662 continueUserActivity:(NSUserActivity*)userActivity
663 restorationHandler:(void (^)(NSArray*))restorationHandler {
664 return [self application:application
665 continueUserActivity:userActivity
666 restorationHandler:restorationHandler
667 isFallbackForScene:NO];
668 }
669
670 - (BOOL)sceneFallbackContinueUserActivity:(NSUserActivity*)userActivity {
671 UIApplication* application = FlutterSharedApplication.application;
672 if (!application) {
673 return NO;
674 }
675 return [self application:application
676 continueUserActivity:userActivity
677 restorationHandler:nil
678 isFallbackForScene:YES];
679 }
680
681 - (BOOL)application:(UIApplication*)application
682 continueUserActivity:(NSUserActivity*)userActivity
683 restorationHandler:(void (^)(NSArray*))restorationHandler
684 isFallbackForScene:(BOOL)isFallback {
685 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
686 if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
687 continue;
688 }
689 if ([delegate respondsToSelector:@selector(application:
690 continueUserActivity:restorationHandler:)]) {
691 if ([delegate application:application
692 continueUserActivity:userActivity
693 restorationHandler:restorationHandler]) {
694 return YES;
695 }
696 }
697 }
698 return NO;
699 }
700 @end
FlutterCallbackCache_Internal.h
FlutterPluginAppLifeCycleDelegate.h
kSelectorsHandledByPlugins
static const SEL kSelectorsHandledByPlugins[]
Definition: FlutterPluginAppLifeCycleDelegate.mm:18
_delegates
NSPointerArray * _delegates
Definition: FlutterPluginAppLifeCycleDelegate.mm:38
kCallbackCacheSubDir
static FLUTTER_ASSERT_ARC const char * kCallbackCacheSubDir
Definition: FlutterPluginAppLifeCycleDelegate.mm:16
FlutterSceneLifeCycle_Internal.h
FlutterSharedApplication.h
FlutterCallbackCache
Definition: FlutterCallbackCache.h:38
+[FlutterCallbackCache setCachePath:]
void setCachePath:(NSString *path)
FlutterPluginAppLifeCycleDelegate
Definition: FlutterPluginAppLifeCycleDelegate.h:16
FlutterSharedApplication
Definition: FlutterSharedApplication.h:13
FlutterSharedApplication::application
UIApplication * application
Definition: FlutterSharedApplication.h:27
FlutterSharedApplication::hasSceneDelegate
BOOL hasSceneDelegate
Definition: FlutterSharedApplication.h:32
FlutterSharedApplication::isAvailable
BOOL isAvailable
Definition: FlutterSharedApplication.h:22
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterApplicationLifeCycleDelegate-p
Definition: FlutterPlugin.h:26