diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index 472a6e8b1fd44..37851be82d988 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -227,9 +227,43 @@ - (BOOL)application:(UIApplication*)application continueUserActivity:(NSUserActivity*)userActivity restorationHandler:(void (^)(NSArray* __nullable restorableObjects))restorationHandler { #endif - return [_lifeCycleDelegate application:application - continueUserActivity:userActivity - restorationHandler:restorationHandler]; + if ([_lifeCycleDelegate application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]) { + return YES; + } else if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) { + return NO; + } else { + NSURLComponents* components = [NSURLComponents componentsWithURL:userActivity.webpageURL + resolvingAgainstBaseURL:YES]; + + if (components == nil or components.path == nil) { + return NO; + } + FlutterViewController* flutterViewController = [self rootFlutterViewController]; + if (flutterViewController) { + [flutterViewController.engine + waitForFirstFrame:3.0 + callback:^(BOOL didTimeout) { + if (didTimeout) { + FML_LOG(ERROR) << "Timeout waiting for the first frame when launching a " + "universal link."; + } else { + NSString* pathAndQuery = components.path; + if (components.query != nil and [components.query length] != 0) { + pathAndQuery = + [NSString stringWithFormat:@"%@?%@", pathAndQuery, components.query]; + } + [flutterViewController.engine.navigationChannel invokeMethod:@"pushRoute" + arguments:pathAndQuery]; + } + }]; + return YES; + } else { + FML_LOG(ERROR) << "Attempting to open a universal link without a Flutter RootViewController."; + return NO; + } + } } #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm index 6a62697dbfe3f..0282c4f6da6b5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm @@ -42,4 +42,28 @@ - (void)skip_testLaunchUrl { OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:@"/custom/route?query=test"]); } +- (void)skip_testLaunchUniversalLink { + FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init]; + FlutterViewController* viewController = OCMClassMock([FlutterViewController class]); + FlutterEngine* engine = OCMClassMock([FlutterEngine class]); + FlutterMethodChannel* navigationChannel = OCMClassMock([FlutterMethodChannel class]); + OCMStub([engine navigationChannel]).andReturn(navigationChannel); + OCMStub([viewController engine]).andReturn(engine); + OCMStub([engine waitForFirstFrame:3.0 callback:([OCMArg invokeBlockWithArgs:@(NO), nil])]); + appDelegate.rootFlutterViewControllerGetter = ^{ + return viewController; + }; + NSURL* url = [NSURL URLWithString:@"http://myApp/custom/route?query=test"]; + NSUserActivity* userActivity = + [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb]; + userActivity.webpageURL = url; + BOOL result = [appDelegate + application:[UIApplication sharedApplication] + continueUserActivity:userActivity + restorationHandler:^(NSArray>* __nullable restorableObjects){ + }]; + XCTAssertTrue(result); + OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:@"/custom/route?query=test"]); +} + @end