File: Platform\iOS\MauiWebViewUIDelegate.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
using System;
using Foundation;
using UIKit;
using WebKit;
 
namespace Microsoft.Maui.Platform
{
	public class MauiWebViewUIDelegate : WKUIDelegate
	{
		WeakReference<IWebViewHandler> _handler;
		static readonly string LocalOK = NSBundle.FromIdentifier("com.apple.UIKit").GetLocalizedString("OK");
		static readonly string LocalCancel = NSBundle.FromIdentifier("com.apple.UIKit").GetLocalizedString("Cancel");
 
		public MauiWebViewUIDelegate(IWebViewHandler handler)
		{
			_ = handler ?? throw new ArgumentNullException(nameof(handler));
			_handler = new WeakReference<IWebViewHandler>(handler);
		}
 
		public override void SetContextMenuConfiguration(WKWebView webView, WKContextMenuElementInfo elementInfo, Action<UIContextMenuConfiguration> completionHandler)
		{
			if (!OperatingSystem.IsIOSVersionAtLeast(13))
				return;
 
			UIContextMenuConfiguration? uIContextMenuConfiguration = null;
			foreach (var interaction in webView.Interactions)
			{
				if (interaction is MauiUIContextMenuInteraction cmi)
				{
					var contextMenu = cmi.GetConfigurationForMenu();
					if (contextMenu != null)
					{
						uIContextMenuConfiguration = contextMenu;
					}
 
					break;
				}
			}
			completionHandler(uIContextMenuConfiguration!);
			return;
		}
 
		public override void RunJavaScriptAlertPanel(WKWebView webView, string message, WKFrameInfo frame, Action completionHandler)
		{
			PresentAlertController(
				webView,
				message,
				okAction: _ => completionHandler()
			);
		}
 
		public override void RunJavaScriptConfirmPanel(WKWebView webView, string message, WKFrameInfo frame, Action<bool> completionHandler)
		{
			PresentAlertController(
				webView,
				message,
				okAction: _ => completionHandler(true),
				cancelAction: _ => completionHandler(false)
			);
		}
 
		public override void RunJavaScriptTextInputPanel(
			WKWebView webView, string prompt, string? defaultText, WKFrameInfo frame, Action<string> completionHandler)
		{
			// TODO the okAction and cancelAction were already taking null when I removed `#nullable disable`
			// and it doesn't seem to have been causing issues so leaving for now
			PresentAlertController(
				webView,
				prompt,
				defaultText: defaultText,
				okAction: x => completionHandler(x.TextFields[0].Text!),
				cancelAction: _ => completionHandler(null!)
			);
		}
 
		static string GetJsAlertTitle(WKWebView webView)
		{
			// Emulate the behavior of UIWebView dialogs.
			// The scheme and host are used unless local html content is what the webview is displaying,
			// in which case the bundle file name is used.
 
			if (webView.Url != null && webView.Url.AbsoluteString != $"file://{NSBundle.MainBundle.BundlePath}/")
				return $"{webView.Url.Scheme}://{webView.Url.Host}";
 
			return new NSString(NSBundle.MainBundle.BundlePath).LastPathComponent;
		}
 
		static UIAlertAction AddOkAction(UIAlertController controller, Action handler)
		{
			var action = UIAlertAction.Create(LocalOK, UIAlertActionStyle.Default, (_) => handler());
			controller.AddAction(action);
			controller.PreferredAction = action;
			return action;
		}
 
		static UIAlertAction AddCancelAction(UIAlertController controller, Action handler)
		{
			var action = UIAlertAction.Create(LocalCancel, UIAlertActionStyle.Cancel, (_) => handler());
			controller.AddAction(action);
			return action;
		}
 
		static void PresentAlertController(
			WKWebView webView,
			string message,
			string? defaultText = null,
			Action<UIAlertController>? okAction = null,
			Action<UIAlertController>? cancelAction = null)
		{
			var controller = UIAlertController.Create(GetJsAlertTitle(webView), message, UIAlertControllerStyle.Alert);
 
			if (defaultText != null)
				controller.AddTextField((textField) => textField.Text = defaultText);
 
			if (okAction != null)
				AddOkAction(controller, () => okAction(controller));
 
			if (cancelAction != null)
				AddCancelAction(controller, () => cancelAction(controller));
 
 
			var handler = (webView.UIDelegate as MauiWebViewUIDelegate)?.Handler;
 
			UIViewController? rootViewController = null;
 
			if (handler != null)
				rootViewController = handler.MauiContext?.GetPlatformWindow()?.RootViewController;
 
			rootViewController ??= UIApplication.SharedApplication?.GetKeyWindow()?.RootViewController;
 
			if (rootViewController != null)
			{
				GetTopViewController(rootViewController)
					.PresentViewController(controller, true, null);
			}
		}
 
		static UIViewController GetTopViewController(UIViewController viewController)
		{
			if (viewController is UINavigationController navigationController)
				return GetTopViewController(navigationController.VisibleViewController);
 
			if (viewController is UITabBarController tabBarController && tabBarController.SelectedViewController != null)
				return GetTopViewController(tabBarController.SelectedViewController);
 
			if (viewController.PresentedViewController != null)
				return GetTopViewController(viewController.PresentedViewController);
 
			return viewController;
		}
 
		IWebViewHandler? Handler
		{
			get
			{
				if (_handler.TryGetTarget(out var handler))
				{
					return handler;
				}
 
				return null;
			}
		}
	}
}