Flutter Engine
 
Loading...
Searching...
No Matches
FlutterSceneLifeCycle.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
12
14
16
17/**
18 * An array of weak pointers to `FlutterEngine`s that have views within this scene. Flutter
19 * automatically adds engines to this array.
20 *
21 * This array is lazily cleaned up. `updateFlutterManagedEnginesInScene:` should be called before
22 * use to ensure it is up-to-date.
23 */
24@property(nonatomic, strong) NSPointerArray* flutterManagedEngines;
25
26/**
27 * An array of weak pointers to `FlutterEngine`s that have views within this scene. Developers
28 * manually add engines to this array.
29 *
30 * It is up to the developer to keep this list up-to-date.
31 */
32@property(nonatomic, strong) NSPointerArray* developerManagedEngines;
33
34@property(nonatomic, strong) UISceneConnectionOptions* connectionOptions;
35@property(nonatomic, assign) BOOL sceneWillConnectEventHandledByPlugin;
36@property(nonatomic, assign) BOOL sceneWillConnectFallbackCalled;
37
38@end
39
41- (instancetype)init {
42 if (self = [super init]) {
43 _flutterManagedEngines = [NSPointerArray weakObjectsPointerArray];
44 _developerManagedEngines = [NSPointerArray weakObjectsPointerArray];
45 _sceneWillConnectFallbackCalled = NO;
46 _sceneWillConnectEventHandledByPlugin = NO;
47 }
48 return self;
49}
50
51#pragma mark - Manual Engine Registration
52
53- (BOOL)registerSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine {
54 // If the engine is Flutter-managed, remove it, since the developer as opted to manually register
55 // it
56 [self removeFlutterManagedEngine:engine];
57
58 // Check if the engine is already in the array to avoid duplicates.
59 if ([self manuallyRegisteredEngine:engine]) {
60 return NO;
61 }
62
63 [self.developerManagedEngines addPointer:(__bridge void*)engine];
64
65 [self compactNSPointerArray:self.developerManagedEngines];
66
67 engine.manuallyRegisteredToScene = YES;
68
69 return YES;
70}
71
72- (BOOL)unregisterSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine {
73 NSUInteger index = [self.developerManagedEngines.allObjects indexOfObject:engine];
74 if (index != NSNotFound) {
75 [self.developerManagedEngines removePointerAtIndex:index];
76 return YES;
77 }
78 return NO;
79}
80
81- (BOOL)manuallyRegisteredEngine:(FlutterEngine*)engine {
82 return [self.developerManagedEngines.allObjects containsObject:engine];
83}
84
85#pragma mark - Automatic Flutter Engine Registration
86
87- (BOOL)addFlutterManagedEngine:(FlutterEngine*)engine {
88 // Check if the engine is already in the array to avoid duplicates.
89 if ([self.flutterManagedEngines.allObjects containsObject:engine]) {
90 return NO;
91 }
92
93 // If a manually registered engine, do not add, as it is being handled manually.
94 if (engine.manuallyRegisteredToScene) {
95 return NO;
96 }
97
98 [self.flutterManagedEngines addPointer:(__bridge void*)engine];
99
100 [self compactNSPointerArray:self.flutterManagedEngines];
101 return YES;
102}
103
104- (BOOL)removeFlutterManagedEngine:(FlutterEngine*)engine {
105 NSUInteger index = [self.flutterManagedEngines.allObjects indexOfObject:engine];
106 if (index != NSNotFound) {
107 [self.flutterManagedEngines removePointerAtIndex:index];
108 return YES;
109 }
110 return NO;
111}
112
113- (void)updateFlutterManagedEnginesInScene:(UIScene*)scene {
114 // Removes engines that are no longer in the scene or have been deallocated.
115 //
116 // This also handles the case where a FlutterEngine's view has been moved to a different scene.
117 for (NSUInteger i = 0; i < self.flutterManagedEngines.count; i++) {
118 FlutterEngine* engine = (FlutterEngine*)[self.flutterManagedEngines pointerAtIndex:i];
119
120 // The engine may be nil if it has been deallocated.
121 if (engine == nil) {
122 [self.flutterManagedEngines removePointerAtIndex:i];
123 i--;
124 continue;
125 }
126
127 // There aren't any events that inform us when a UIWindow changes scenes.
128 // If a developer moves an entire UIWindow to a different scene and that window has a
129 // FlutterView inside of it, its engine will still be in its original scene's
130 // FlutterPluginSceneLifeCycleDelegate. The best we can do is move the engine to the correct
131 // scene here. Due to this, when moving a UIWindow from one scene to another, its first scene
132 // event may be lost. Since Flutter does not fully support multi-scene and this is an edge
133 // case, this is a loss we can deal with. To workaround this, the developer can move the
134 // UIView instead of the UIWindow, which will use willMoveToWindow to add/remove the engine from
135 // the scene.
136 UIWindowScene* actualScene = engine.viewController.view.window.windowScene;
137 if (actualScene != nil && actualScene != scene) {
138 [self.flutterManagedEngines removePointerAtIndex:i];
139 i--;
140
141 if ([actualScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
142 id<FlutterSceneLifeCycleProvider> lifeCycleProvider =
143 (id<FlutterSceneLifeCycleProvider>)actualScene.delegate;
144 [lifeCycleProvider.sceneLifeCycleDelegate addFlutterManagedEngine:engine];
145 }
146 continue;
147 }
148 }
149}
150
151- (NSArray*)allEngines {
152 return [_flutterManagedEngines.allObjects
153 arrayByAddingObjectsFromArray:_developerManagedEngines.allObjects];
154}
155
156/**
157 * Makes a best effort to get the FlutterPluginAppLifeCycleDelegate from the AppDelegate if
158 * available. It may not be available if embedded in an iOS app extension or the AppDelegate doesn't
159 * subclass FlutterAppDelegate.
160 */
161- (FlutterPluginAppLifeCycleDelegate*)applicationLifeCycleDelegate {
162 id appDelegate = FlutterSharedApplication.application.delegate;
163 if ([appDelegate respondsToSelector:@selector(lifeCycleDelegate)]) {
164 id lifecycleDelegate = [appDelegate lifeCycleDelegate];
165 if ([lifecycleDelegate isKindOfClass:[FlutterPluginAppLifeCycleDelegate class]]) {
166 return lifecycleDelegate;
167 }
168 }
169 return nil;
170}
171
172#pragma mark - Connecting and disconnecting the scene
173
174- (void)engine:(FlutterEngine*)engine receivedConnectNotificationFor:(UIScene*)scene {
175 // Connection options may be nil if the notification was received before the
176 // `scene:willConnectToSession:options:` event. In which case, we can wait for the actual event.
177 BOOL added = [self addFlutterManagedEngine:engine];
178 if (!added) {
179 // Don't send willConnectToSession event if engine is already tracked as it will be handled by
180 // the actual event.
181 return;
182 }
183 if (self.connectionOptions != nil) {
184 [self scene:scene
185 willConnectToSession:scene.session
186 flutterEngine:engine
187 options:self.connectionOptions];
188 }
189}
190
191- (void)scene:(UIScene*)scene
192 willConnectToSession:(UISceneSession*)session
193 options:(UISceneConnectionOptions*)connectionOptions {
194 self.connectionOptions = connectionOptions;
195 if ([scene.delegate conformsToProtocol:@protocol(UIWindowSceneDelegate)]) {
196 NSObject<UIWindowSceneDelegate>* sceneDelegate =
197 (NSObject<UIWindowSceneDelegate>*)scene.delegate;
198 if ([sceneDelegate.window.rootViewController isKindOfClass:[FlutterViewController class]]) {
199 FlutterViewController* rootViewController =
200 (FlutterViewController*)sceneDelegate.window.rootViewController;
201 [self addFlutterManagedEngine:rootViewController.engine];
202 }
203 }
204
205 [self updateFlutterManagedEnginesInScene:scene];
206
207 for (FlutterEngine* engine in [self allEngines]) {
208 [self scene:scene willConnectToSession:session flutterEngine:engine options:connectionOptions];
209 }
210}
211
212- (void)scene:(UIScene*)scene
213 willConnectToSession:(UISceneSession*)session
214 flutterEngine:(FlutterEngine*)engine
215 options:(UISceneConnectionOptions*)connectionOptions {
216 // Don't send connection options if a plugin has already used them.
217 UISceneConnectionOptions* availableOptions = connectionOptions;
218 if (self.sceneWillConnectEventHandledByPlugin) {
219 availableOptions = nil;
220 }
221 BOOL handledByPlugin = [engine.sceneLifeCycleDelegate scene:scene
222 willConnectToSession:session
223 options:availableOptions];
224
225 // If no plugins handled this, give the application fallback a chance to handle it.
226 // Only call the fallback once since it's per application.
227 if (!handledByPlugin && !self.sceneWillConnectFallbackCalled) {
228 self.sceneWillConnectFallbackCalled = YES;
229 if ([[self applicationLifeCycleDelegate] sceneWillConnectFallback:connectionOptions]) {
230 handledByPlugin = YES;
231 }
232 }
233 if (handledByPlugin) {
234 self.sceneWillConnectEventHandledByPlugin = YES;
235 }
236
237 if (!self.sceneWillConnectEventHandledByPlugin) {
238 // Only process deeplinks if a plugin has not already done something to handle this event.
239 [self handleDeeplinkingForEngine:engine options:connectionOptions];
240 }
241}
242
243- (void)sceneDidDisconnect:(UIScene*)scene {
244 [self updateFlutterManagedEnginesInScene:scene];
245 for (FlutterEngine* engine in [self allEngines]) {
246 [engine.sceneLifeCycleDelegate sceneDidDisconnect:scene];
247 }
248 // There is no application equivalent for this event and therefore no fallback.
249}
250
251#pragma mark - Transitioning to the foreground
252
253- (void)sceneWillEnterForeground:(UIScene*)scene {
254 [self updateFlutterManagedEnginesInScene:scene];
255 for (FlutterEngine* engine in [self allEngines]) {
256 [engine.sceneLifeCycleDelegate sceneWillEnterForeground:scene];
257 }
258 [[self applicationLifeCycleDelegate] sceneWillEnterForegroundFallback];
259}
260
261- (void)sceneDidBecomeActive:(UIScene*)scene {
262 [self updateFlutterManagedEnginesInScene:scene];
263 for (FlutterEngine* engine in [self allEngines]) {
264 [engine.sceneLifeCycleDelegate sceneDidBecomeActive:scene];
265 }
266 [[self applicationLifeCycleDelegate] sceneDidBecomeActiveFallback];
267}
268
269#pragma mark - Transitioning to the background
270
271- (void)sceneWillResignActive:(UIScene*)scene {
272 [self updateFlutterManagedEnginesInScene:scene];
273 for (FlutterEngine* engine in [self allEngines]) {
274 [engine.sceneLifeCycleDelegate sceneWillResignActive:scene];
275 }
276 [[self applicationLifeCycleDelegate] sceneWillResignActiveFallback];
277}
278
279- (void)sceneDidEnterBackground:(UIScene*)scene {
280 [self updateFlutterManagedEnginesInScene:scene];
281 for (FlutterEngine* engine in [self allEngines]) {
282 [engine.sceneLifeCycleDelegate sceneDidEnterBackground:scene];
283 }
284 [[self applicationLifeCycleDelegate] sceneDidEnterBackgroundFallback];
285}
286
287#pragma mark - Opening URLs
288
289- (void)scene:(UIScene*)scene openURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
290 [self updateFlutterManagedEnginesInScene:scene];
291
292 // Track engines that had this event handled by a plugin.
293 NSMutableSet<FlutterEngine*>* enginesHandledByPlugin = [NSMutableSet set];
294 for (FlutterEngine* engine in [self allEngines]) {
295 if ([engine.sceneLifeCycleDelegate scene:scene openURLContexts:URLContexts]) {
296 [enginesHandledByPlugin addObject:engine];
297 }
298 }
299
300 // If no plugins handled this, give the application fallback a chance to handle it.
301 if (enginesHandledByPlugin.count == 0) {
302 if ([[self applicationLifeCycleDelegate] sceneFallbackOpenURLContexts:URLContexts]) {
303 // If the application fallback handles it, don't do any deeplinking.
304 return;
305 }
306 }
307
308 // For any engine that was not handled by a plugin, do deeplinking.
309 for (FlutterEngine* engine in [self allEngines]) {
310 if ([enginesHandledByPlugin containsObject:engine]) {
311 continue;
312 }
313 for (UIOpenURLContext* urlContext in URLContexts) {
314 if ([self handleDeeplink:urlContext.URL flutterEngine:engine relayToSystemIfUnhandled:NO]) {
315 break;
316 }
317 }
318 }
319}
320
321#pragma mark - Continuing user activities
322
323- (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity {
324 [self updateFlutterManagedEnginesInScene:scene];
325
326 // Track engines that had this event handled by a plugin.
327 NSMutableSet<FlutterEngine*>* enginesHandledByPlugin = [NSMutableSet set];
328 for (FlutterEngine* engine in [self allEngines]) {
329 if ([engine.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity]) {
330 [enginesHandledByPlugin addObject:engine];
331 }
332 }
333
334 // If no plugins handled this, give the application fallback a chance to handle it.
335 if (enginesHandledByPlugin.count == 0) {
336 if ([[self applicationLifeCycleDelegate] sceneFallbackContinueUserActivity:userActivity]) {
337 // If the application fallback handles it, don't do any deeplinking.
338 return;
339 }
340 }
341
342 // For any engine that was not handled by a plugin, do deeplinking.
343 for (FlutterEngine* engine in [self allEngines]) {
344 if ([enginesHandledByPlugin containsObject:engine]) {
345 continue;
346 }
347 [self handleDeeplink:userActivity.webpageURL flutterEngine:engine relayToSystemIfUnhandled:YES];
348 }
349}
350
351#pragma mark - Saving the state of the scene
352
353- (NSUserActivity*)stateRestorationActivityForScene:(UIScene*)scene {
354 // Saves state per FlutterViewController.
355 NSUserActivity* activity = scene.userActivity;
356 if (!activity) {
357 activity = [[NSUserActivity alloc] initWithActivityType:scene.session.configuration.name];
358 }
359
360 [self updateFlutterManagedEnginesInScene:scene];
361 int64_t appBundleModifiedTime = FlutterSharedApplication.lastAppModificationTime;
362 for (FlutterEngine* engine in [self allEngines]) {
364 NSString* restorationId = vc.restorationIdentifier;
365 if (restorationId) {
366 NSData* restorationData = [engine.restorationPlugin restorationData];
367 if (restorationData) {
368 [activity addUserInfoEntriesFromDictionary:@{restorationId : restorationData}];
369 [activity addUserInfoEntriesFromDictionary:@{
370 kRestorationStateAppModificationKey : [NSNumber numberWithLongLong:appBundleModifiedTime]
371 }];
372 }
373 }
374 }
375
376 return activity;
377}
378
379- (void)scene:(UIScene*)scene
380 restoreInteractionStateWithUserActivity:(NSUserActivity*)stateRestorationActivity {
381 // Restores state per FlutterViewController.
382 NSDictionary<NSString*, id>* userInfo = stateRestorationActivity.userInfo;
383 [self updateFlutterManagedEnginesInScene:scene];
384 int64_t appBundleModifiedTime = FlutterSharedApplication.lastAppModificationTime;
385 NSNumber* stateDateNumber = userInfo[kRestorationStateAppModificationKey];
386 int64_t stateDate = 0;
387 if (stateDateNumber && [stateDateNumber isKindOfClass:[NSNumber class]]) {
388 stateDate = [stateDateNumber longLongValue];
389 }
390 if (appBundleModifiedTime != stateDate) {
391 // Don't restore state if the app has been re-installed since the state was last saved
392 return;
393 }
394
395 for (FlutterEngine* engine in [self allEngines]) {
396 UIViewController* vc = (UIViewController*)engine.viewController;
397 NSString* restorationId = vc.restorationIdentifier;
398 if (restorationId) {
399 NSData* restorationData = userInfo[restorationId];
400 if ([restorationData isKindOfClass:[NSData class]]) {
401 [engine.restorationPlugin setRestorationData:restorationData];
402 }
403 }
404 }
405}
406
407#pragma mark - Performing tasks
408
409- (void)windowScene:(UIWindowScene*)windowScene
410 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
411 completionHandler:(void (^)(BOOL succeeded))completionHandler {
412 [self updateFlutterManagedEnginesInScene:windowScene];
413
414 BOOL handledByPlugin = NO;
415 for (FlutterEngine* engine in [self allEngines]) {
416 BOOL result = [engine.sceneLifeCycleDelegate windowScene:windowScene
417 performActionForShortcutItem:shortcutItem
418 completionHandler:completionHandler];
419 if (result) {
420 handledByPlugin = YES;
421 }
422 }
423 if (!handledByPlugin) {
424 [[self applicationLifeCycleDelegate]
425 sceneFallbackPerformActionForShortcutItem:shortcutItem
426 completionHandler:completionHandler];
427 }
428}
429
430#pragma mark - Helpers
431
432- (void)handleDeeplinkingForEngine:(FlutterEngine*)engine
433 options:(UISceneConnectionOptions*)connectionOptions {
434 // If your app has opted into Scenes, and your app is not running, the system delivers the
435 // universal link to the scene(_:willConnectTo:options:) delegate method after launch, and to
436 // scene(_:continue:) when the universal link is tapped while your app is running or suspended in
437 // memory.
438 for (NSUserActivity* userActivity in connectionOptions.userActivities) {
439 if ([self handleDeeplink:userActivity.webpageURL
440 flutterEngine:engine
441 relayToSystemIfUnhandled:YES]) {
442 return;
443 }
444 }
445
446 // If your app has opted into Scenes, and your app isn’t running, the system delivers the URL to
447 // the scene:willConnectToSession:options: delegate method after launch, and to
448 // scene:openURLContexts: when your app opens a URL while running or suspended in memory.
449 for (UIOpenURLContext* urlContext in connectionOptions.URLContexts) {
450 if ([self handleDeeplink:urlContext.URL flutterEngine:engine relayToSystemIfUnhandled:YES]) {
451 return;
452 }
453 }
454}
455
456- (BOOL)handleDeeplink:(NSURL*)url
457 flutterEngine:(FlutterEngine*)engine
458 relayToSystemIfUnhandled:(BOOL)throwBack {
459 if (!url) {
460 return NO;
461 }
462 // Don't process the link if deep linking is disabled.
464 return NO;
465 }
466 // if deep linking is enabled, send it to the framework
467 [engine sendDeepLinkToFramework:url
468 completionHandler:^(BOOL success) {
469 if (!success && throwBack) {
470 // throw it back to iOS
471 [FlutterSharedApplication.application openURL:url
472 options:@{}
473 completionHandler:nil];
474 }
475 }];
476 return YES;
477}
478
479+ (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene {
480 if ([scene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
481 NSObject<FlutterSceneLifeCycleProvider>* sceneProvider =
482 (NSObject<FlutterSceneLifeCycleProvider>*)scene.delegate;
483 return sceneProvider.sceneLifeCycleDelegate;
484 }
485
486 // When embedded in a SwiftUI app, the scene delegate does not conform to
487 // FlutterSceneLifeCycleProvider even if it does. However, after force casting it,
488 // selectors respond and can be used.
489 NSObject<FlutterSceneLifeCycleProvider>* sceneProvider =
490 (NSObject<FlutterSceneLifeCycleProvider>*)scene.delegate;
491 if ([sceneProvider respondsToSelector:@selector(sceneLifeCycleDelegate)]) {
492 id sceneLifeCycleDelegate = sceneProvider.sceneLifeCycleDelegate;
493 // Double check that the selector is the expected class.
494 if ([sceneLifeCycleDelegate isKindOfClass:[FlutterPluginSceneLifeCycleDelegate class]]) {
495 return (FlutterPluginSceneLifeCycleDelegate*)sceneLifeCycleDelegate;
496 }
497 }
498 return nil;
499}
500
501- (void)compactNSPointerArray:(NSPointerArray*)array {
502 // NSPointerArray is clever and assumes that unless a mutation operation has occurred on it that
503 // has set one of its values to nil, nothing could have changed and it can skip compaction.
504 // That's reasonable behaviour on a regular NSPointerArray but not for a weakObjectPointerArray.
505 // As a workaround, we mutate it first. See: http://www.openradar.me/15396578
506 [array addPointer:nil];
507 [array compact];
508}
509@end
510
512 // Weak references to registered plugins.
513 NSPointerArray* _delegates;
514}
515
516- (instancetype)init {
517 if (self = [super init]) {
518 _delegates = [NSPointerArray weakObjectsPointerArray];
519 }
520 return self;
521}
522
523- (void)addDelegate:(NSObject<FlutterSceneLifeCycleDelegate>*)delegate {
524 [_delegates addPointer:(__bridge void*)delegate];
525
526 // NSPointerArray is clever and assumes that unless a mutation operation has occurred on it that
527 // has set one of its values to nil, nothing could have changed and it can skip compaction.
528 // That's reasonable behaviour on a regular NSPointerArray but not for a weakObjectPointerArray.
529 // As a workaround, we mutate it first. See: http://www.openradar.me/15396578
530 [_delegates addPointer:nil];
531 [_delegates compact];
532}
533
534#pragma mark - Connecting and disconnecting the scene
535
536- (BOOL)scene:(UIScene*)scene
537 willConnectToSession:(UISceneSession*)session
538 options:(UISceneConnectionOptions*)connectionOptions {
539 BOOL handledByPlugin = NO;
540 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
541 if ([delegate respondsToSelector:_cmd]) {
542 // If this event has already been consumed by a plugin, send the event with nil options.
543 // Only allow one plugin to process the connection options.
544 if ([delegate scene:scene
545 willConnectToSession:session
546 options:(handledByPlugin ? nil : connectionOptions)]) {
547 handledByPlugin = YES;
548 }
549 }
550 }
551 return handledByPlugin;
552}
553
554- (void)sceneDidDisconnect:(UIScene*)scene {
555 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
556 if ([delegate respondsToSelector:_cmd]) {
557 [delegate sceneDidDisconnect:scene];
558 }
559 }
560}
561
562#pragma mark - Transitioning to the foreground
563
564- (void)sceneWillEnterForeground:(UIScene*)scene {
565 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
566 if ([delegate respondsToSelector:_cmd]) {
567 [delegate sceneWillEnterForeground:scene];
568 }
569 }
570}
571
572- (void)sceneDidBecomeActive:(UIScene*)scene {
573 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
574 if ([delegate respondsToSelector:_cmd]) {
575 [delegate sceneDidBecomeActive:scene];
576 }
577 }
578}
579
580#pragma mark - Transitioning to the background
581
582- (void)sceneWillResignActive:(UIScene*)scene {
583 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
584 if ([delegate respondsToSelector:_cmd]) {
585 [delegate sceneWillResignActive:scene];
586 }
587 }
588}
589
590- (void)sceneDidEnterBackground:(UIScene*)scene {
591 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
592 if ([delegate respondsToSelector:_cmd]) {
593 [delegate sceneDidEnterBackground:scene];
594 }
595 }
596}
597
598#pragma mark - Opening URLs
599
600- (BOOL)scene:(UIScene*)scene openURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
601 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
602 if ([delegate respondsToSelector:_cmd]) {
603 if ([delegate scene:scene openURLContexts:URLContexts]) {
604 // Only allow one plugin to process this event.
605 return YES;
606 }
607 }
608 }
609 return NO;
610}
611
612#pragma mark - Continuing user activities
613
614- (BOOL)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity {
615 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
616 if ([delegate respondsToSelector:_cmd]) {
617 if ([delegate scene:scene continueUserActivity:userActivity]) {
618 // Only allow one plugin to process this event.
619 return YES;
620 }
621 }
622 }
623 return NO;
624}
625
626#pragma mark - Performing tasks
627
628- (BOOL)windowScene:(UIWindowScene*)windowScene
629 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
630 completionHandler:(void (^)(BOOL succeeded))completionHandler {
631 for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
632 if ([delegate respondsToSelector:_cmd]) {
633 if ([delegate windowScene:windowScene
634 performActionForShortcutItem:shortcutItem
635 completionHandler:completionHandler]) {
636 // Only allow one plugin to process this event.
637 return YES;
638 }
639 }
640 }
641 return NO;
642}
643@end
NSPointerArray * _delegates
FlutterEngine engine
Definition main.cc:84
void sceneWillEnterForeground:(ios(13.0) API_AVAILABLE)
void sceneDidEnterBackground:(ios(13.0) API_AVAILABLE)
FlutterViewController * viewController
int BOOL