I'd very much appreciate a resolution to the following problem, as well as Microsoft both:
- Releasing some official documentation on the issue, and
- Updating the Xamarin Forms Solution Templates in Visual Studio
iOS 13 was released on the 19th of September 2019 and yet there is still a bug in the new iOS SceneDelegate pattern as relates to Xamarin Forms, in that when using Xamarin.Forms (not Xamarin.iOS), that the developer cannot intercept the URL that was clicked on to bring the app into the foreground.
Furthermore, these 18 months later, the Xamarin.Forms solution that is generated in Visual Studio (File / New Solution, etc) does not include the new SceneDelegate pattern _in the iOS project of that Xamarin.Forms solution.
In that changeover from iOS 12 to 13 in September 2019, every single developer has also been left stranded without documentation of the new Xamarin Forms pattern that allows a user to click a link and the application to be opened and the developer being advised of what URL opened the link.
The following code demonstrates that I have a deep link registered, and where iOS is 13 or greater, although clicking on the link opens the app, the OpenUrlContexts override does not fire, hence I am unable to filter then navigate, based upon the URL.
I've included the following code files:
- SceneDelegate
- AppDelegate
- apple-app-site-association
- Entitlements.plist
- Info.plist extract
- App.xaml.cs
Note: the following has the pre-iOS 13 code stripped out, which in the finmal result should be included to cater for iPhones that have not yet upgraded to iOS 13 and above.
In short, the OpenUrlContexts method does not fire when a link is clicked on, and even in WillConnect I'd have expected connectionOptions.UrlContexts.AnyObject.Url to show the URL however UrlContexts is null.
SceneDelegate file:
[Register("SceneDelegate")]
public class SceneDelegate : UIWindowSceneDelegate
{
[Export("scene:openURLContexts:")]
public override void OpenUrlContexts(UIScene scene, NSSet<UIOpenUrlContext> urlContexts)
{
Debug.WriteLine("*** URL property =====" + urlContexts.AnyObject.Url);
var urlString = urlContexts.AnyObject.Url;
if (urlString.Equals("https://REDACTED"))
{
// Run code
}
}
public override UIWindow Window { get; set; }
public override void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
{
var windowScene = scene as UIWindowScene;
if (windowScene != null)
{
Window = new UIWindow(windowScene.CoordinateSpace.Bounds);
if (Window != null)
{
// This just creates a new App instance without creating a new AppDelegate
// In iether case I still see issues with navigation events going to the wrong window,
// i.e. I tap Add Item in one window, but the new item page launches in another window
// Or use the menu to go to the ABout page, and the About page opens in the other window
// This is in keeping with what I have seen in reagrds to embedding forms pages into native
// projects. In this case, you need to use the platforms navigation APIs, not Xamarin.Forms
// navigation APIs
var fApp = new App();
try
{
// Set the scene's RootViewController from the one X.Forms created
Window.RootViewController = fApp.MainPage.CreateViewController();
//// This creates the Xamarin.Forms UI with a new app delegate
var ad = new AppDelegate();
ad.GetUI();
//
//// Set the scene's RootViewController from the one X.Forms created
Window.RootViewController = ad.Window.RootViewController;
// Set the WindowScene
Window.WindowScene = windowScene;
}
catch(Exception ex)
{
}
}
}
}
}
AppDelegate file:
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public UIApplication application;
public NSDictionary options;
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
[Export("application:didFinishLaunchingWithOptions:")]
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
// only used when creating a new app delegate in the scene delegate in order to be able to call
// LoadApplication in GetUI method
this.application = app;
this.options = options;
return true;
}
else
{
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
// only used when creating a new app delegate in the scene delegate
public void GetUI()
{
LoadApplication(new App());
// This call populates the Window property so it needs to be called after LoadApplication for every scene
base.FinishedLaunching(application, options);
}
}
apple-app-site-association file:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "REDACTED",
"paths": [ "/", "/REDACTED/*"]
}
]
}
}
Entitlements.plist file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:REDACTED</string>
</array>
</dict>
</plist>
Registration of SceneDelegate in info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- other stuff not shown here -->
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>EmptyStoryboard</string>
</dict>
</array>
</dict>
</dict>
</dict>
Xamarin.Forms App.xaml.cs
(Does not want to paste however the example only has the standard overrides without any user-added code.)