Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin.Forms.WebView.Navigating event raised on iOS for internal navigation

Let's say you want to prevent the user from navigating away from your Xamarin.Forms.WebView to an external page.

public App ()
{
    var webView = new WebView
    {
        Source = new HtmlWebViewSource
        {
            Html = "<h1>Hello world</h1><a href='http://example.com'>Can't escape!</a><iframe width='420' height='315' src='https://www.youtube.com/embed/oHg5SJYRHA0' frameborder='0' allowfullscreen></iframe>"
        }
    };
    webView.Navigating += WebView_Navigating;

    MainPage = new ContentPage {
        Content = webView
    };
}

private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
    // we don't want to navigate away from our page
    // open it in a new page instead etc.
    e.Cancel = true;
}

This works fine on Windows and Android. But on iOS, it doesn't load at all!

On iOS, the Navigating event gets raised even when loading the source from a HtmlWebViewSource, with a URL that looks something like file:///Users/[user]/Library/Developer/CoreSimulator/Devices/[deviceID]/data/Containers/Bundle/Application/[appID]/[appName].app/

Alright, so you can get around that with something like this:

private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
    if (e.Url.StartsWith("file:") == false)
        e.Cancel = true;
}

The page finally loads on iOS. Yay. But wait! The embedded YouTube video doesn't load! That's because the Navigating event gets raised for the internal navigation of embedded resources like iframes and even external scripts (like Twitter's <script charset="utf-8" type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>), but only on iOS!

I couldn't find a way to determine if the Navigating event was raised from internal navigation or because the user clicked a link.

How to get around this?

like image 778
Zdeněk Gromnica Avatar asked Mar 13 '23 06:03

Zdeněk Gromnica


2 Answers

I am not sure if it is possible to detect in Xamarin Forms out of the box but the navigation type is easily determined using a custom renderer. In your custom iOS renderer, assign a WebViewDelegate and within that Delegate class, override ShouldStartLoad() like so:

public class CustomWebViewRenderer : WebViewRenderer {

    #region Properties

    public CustomWebView CustomWebViewItem { get { return Element as CustomWebView; } }

    #endregion

    protected override void OnElementChanged(VisualElementChangedEventArgs e) {
        base.OnElementChanged(e);

        if(e.OldElement == null) {
            Delegate = new CustomWebViewDelegate(); //Assigning the delegate
        }
    }
}
internal class CustomWebViewDelegate : UIWebViewDelegate {

    public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) {

        if(navigationType == UIWebViewNavigationType.LinkClicked) {
            //To prevent navigation when a link is click, return false
            return false;
        }

        return true;
    }
}

You could also surface a bool property or even an enum back up to your Xamarin Forms WebView which would say whether the Navigating event was from a link being clicked or from something else, though a custom renderer would be needed for that as well.

like image 172
hvaughan3 Avatar answered Apr 07 '23 00:04

hvaughan3


private bool isNavigated = false;
        public CustomWebView()
        {
            if (Device.OS == TargetPlatform.Android)
            {
                // always true for android
                isNavigated = true;
            }

            Navigated += (sender, e) =>
            {
                isNavigated = true;
            };
            Navigating += (sender, e) =>
            {
                if (isNavigated)
                {
                    try
                    {
                        var uri = new Uri(e.Url);
                        Device.OpenUri(uri);
                    }
                    catch (Exception)
                    {
                    }

                    e.Cancel = true;
                }
            };
        }
like image 38
max Avatar answered Apr 07 '23 00:04

max