Xamarin.Forms WebView Does Not Work Once the Navigating EventHandler is Setup

CN_AndersonCN_Anderson C.N. AndersonUSUniversity

I am working on a project that is using a Xamarin.Forms WebView to display HTML on a ContentView.
I want the user to be able to click the links in the HTML, then the app will ask the user if they want to go to their browser, and if so, do a Device.OpenUri(uri) to take the user there. Like so:

var webView = new WebView();

webView.Source = new HtmlWebViewSource
{
    Html = "<p>This is some html</p><a href='http://www.google.com'>Google</a>",
};

webView.Navigating += Helper.ConfirmNavigationAndSendToWebBrowserApp;

Then the event is handled by this method

public static class Helper
{
    public static async void ConfirmNavigationAndSendToWebBrowserApp(object sender, WebNavigatingEventArgs e)
    {
        var uri = new Uri(e.Url);
        e.Cancel = true; // prevent local browsing

        var webViewSender = (WebView)sender;

        if (e.Url.StartsWith("http"))
        {
            try
            {
                // Must stay on UI thread for iOS
                var response = await webViewSender.Navigation.NavigationStack.FirstOrDefault().DisplayAlert("Are you sure?",
                    "Are you sure you want to open this page in your web browser?", "Go to Browser", "Cancel")
                    .ConfigureAwait(continueOnCapturedContext: true);

                if (response)
                {
                    Device.OpenUri(uri);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }
    }
}

The WebView displays fine as long as the webView.Navigating EventHandler set up is commented, but once I uncomment it, the WebView will not display on the page. It seems like the WebView just does not work or is broken once the event handler is setup.

I have already read about allowing http web addresses on iOS in the Info.plist and I have added that to my project with the same result. I do not get this issue on Android, this code works fine on Android. I am working to create a custom render on iOS, but it looks like I might be having the same issue with Xamarin iOS UIWebView where is does not work once the LoadStarted EventHandler is setup.

Has anyone else experienced either the issue with Xamarin.Forms or Xamarin iOS? Does anyone have an idea for a solution?

I am working to get a demo project that would replicate the issue to post here.

Best Answer

Answers

  • CN_AndersonCN_Anderson C.N. Anderson USUniversity

    Attached is the project that has an example of both issues:
    1. Xamarin.Forms WebView Does Not Work Once the Navigating EventHandler is Setup
    2. Xamarin iOS UIWebView Does Not Work Once the LoadStarted EventHandler is Setup

    Please feel free to troubleshoot one or both issues. The troublesome code is commented so that you can run it and see what it should look like, but clicking the link in the HTML does not work as desired (it should load in phone's web browser, not in the app itself). I have marked the trouble code with a comment "ISSUE CODE HERE:" so that you may find it quickly.

    For the first issue, Xamarin.Forms WebView Does Not Work Once the Navigating EventHandler is Setup, when you uncomment that code and run the project, the page will load, but CustomWebView will not show on the page. It is is an ContentView with a black background, which you will see is empty.

    For the second issue, Xamarin iOS UIWebView Does Not Work Once the LoadStarted EventHandler is Setup, when you uncomment that code and run the project, you will hit the button to go to the page with the CustomWebView and that button will not work. I looks like the code is running, I don't see any errors, but the page will not load.

    I am hoping this is just a silly issue, such a using the wrong method, or wiring up the custom renderer incorrectly that can be easily fixed. Please post any thoughts here to help me troubleshoot.

  • AdamPAdamP Adam Pedley AUUniversity ✭✭✭✭✭

    @C.N.Anderson - It is strange that the WebView event doesn't work for iOS. However in going down to the custom render for UIWebView, there are some things you need to know. You can't attach yourself to an event because iOS is overwriting the delegate, or your overwriting the iOS delegate (one of those ways around).

    As such you need to create your own delegate to completely overwrite the existing one.

    First you create a delegate inheriting and implementing the below.

    public class myDelegate: UIWebViewDelegate, INSUrlConnectionDelegate

    Then you assign it to the UIWebView. You need to assign it null first to override any warnings.

                    Delegate = null;
                    Delegate = myDelegate;
    

    Also note this is generally only if you need some very specific enhancements to the webview that you can't get through other means. If you are just wanting to get the Navigating event, then your original code should work and is the preferred way to go.

    What do you mean that your WebView is broken when you attach an event handler? Does it go to that event at all, or does anything try to load?

  • CN_AndersonCN_Anderson C.N. Anderson USUniversity

    Hi @AdamP thanks so much for taking the time to look at this issue and respond. I had forgotten about the delegate on iOS, thanks for reminding me, I will give that a try.

    It would be ideal if the (Xamarin.Forms) Navigating event worked, just as it works on Android. But I cannot get it to work. The iOS custom renderer was an attempt to bypass the Xamarin.Forms issue, but as you can see I had problems with the iOS renderer too.

    What do you mean that your WebView is broken when you attach an event handler? Does it go to that event at all, or does anything try to load?

    In Xamarin.Forms, the page still loads, but the WebView does not display on the screen. In my example project and the WithNavigatingCodeRunning.tiff screen shot you can see I have the background of the ContentView set to Color.Black, so the WebView should appear in there, but as you can see, it is just the black box of the ContentView, with no WebView displayed.

    I have a ContentPage that has a ContentView that contains the WebView. Here is some code from the sample project I posted, the following code works and runs:

    public class WebViewPage : ContentPage
    {
        public WebViewPage ()
        {
            var customWebView = new CustomWebView {
                RequestedUrl = "http://www.google.com",
                RequestedHtml = "<p>This is some html<p> <a href=\"http://www.google.com\">Google</a>",
            };
    
            var contentView = new ContentView {
                Padding = new Thickness(5),
                BackgroundColor = Color.Black,
                WidthRequest = 200,
                HeightRequest = 400,
                Content = customWebView,
            };
    
            Content = new StackLayout { 
                Children = {
                    new Label { Text = "Hello ContentPage" },
                    contentView
                }
            };
    
            // if this code is uncommented and the project is run, this page will load, but the webview
            // will not display on the screen
            // ISSUE CODE HERE:
            //customWebView.Navigating += Helper.ConfirmNavigationAndSendToWebBrowserApp;
        }
    }
    

    When I uncomment this bit of code:

    webView.Navigating += Helper.ConfirmNavigationAndSendToWebBrowserApp;

    That is when the WebView will not show (only on iOS).

    I have included three screen shots, (1) the main page with text and button that take the user to the page that should have a WebView, (2) one with the Navigating Event on the page with the WebView commented and (3) one with Navigating Event code running. Please take a look.

  • CN_AndersonCN_Anderson C.N. Anderson USUniversity
    edited March 2016

    Hi @AdamP I have tried what you suggested:

    As such you need to create your own delegate to completely overwrite the existing one.

    Although per Xamarin's documentation both the event handler and delegate should work:

    The MonoTouch API supports two styles of event notification: the Objective-C style that uses a delegate class or the C# style using event notifications.

    Source: https://developer.xamarin.com/api/type/MonoTouch.UIKit.UIWebView/

    This is what I wrote for the delegate:

    public class CustomDelegate : UIWebViewDelegate, INSUrlConnectionDelegate
    {
        int counter = 0;
    
        public override void LoadStarted (UIWebView webView)
        {
            // do not use this otherwise you get this error:
            // Foundation.You_Should_Not_Call_base_In_This_Method
            //base.LoadStarted (webView);
    
            counter++;
    
            // this does not get the correct url
            var url = webView.Request.Url.AbsoluteString;
    
            Console.WriteLine ("counter: {0}", counter);
            Console.WriteLine ("CustomDelegate.LoadStarted");
            Console.WriteLine("webView.Request.Url.AbsoluteString: {0}", url);
    
            // don't load in the current view
            webView.StopLoading ();
    
            var jsWindowLocation = webView.EvaluateJavascript ("window.location");
    
            var jsDocumentUrl = webView.EvaluateJavascript ("document.URL");
    
            // if it is not the first load (e.g. user clicked a link in the html), open this in
            //  the phone's browser
            if (counter > 1) {
                Device.OpenUri (new Uri (url));
            }
        }
    
        public override void LoadFailed (UIWebView webView, NSError error)
        {
            Console.WriteLine(error.ToString());
        }
    }
    

    Now I am having trouble getting the url of the link that was clicked, the current url variable I have created keeps getting something like:

    file:///Users/[username]/Library/Developer/CoreSimulator/Devices/[DeviceID]/data/Containers/Bundle/Application/[AppID]/WebViewOnAppleIssue.iOS.app/

    But then when I try to call

    Device.OpenUri (new Uri (url));

    or

    UIApplication.SharedApplication.OpenUrl(new NSUrl(url));

    neither of these work. I am guessing because the url is pointing to my local file system, but I cannot find where the correct link is stored (in this example, should be http://google.com).

    Any ideas where Xamarin iOS keeps the link that has been clicked?

  • CN_AndersonCN_Anderson C.N. Anderson USUniversity

    Hi @AdamP, I ended up just giving up on trying to fix this issue in Xamarin.Forms and ended up using the custom renderer in Xamarin iOS. I used a delegate you suggested, thank you so much for posting that idea.

    Once I found the right override method for the UIWebViewDelegate, I was set. I ended up using ShouldStartLoad which I found referenced on this post.

  • AdamPAdamP Adam Pedley AUUniversity ✭✭✭✭✭

    @C.N.Anderson - glad you managed to get a workaround. Its unfortunate the Navigating event didn't work.

    I was just looking over your code again and because Helper.ConfirmNavigationAndSendToWebBrowserApp sets Cancel=true it stops the WebView from loading the page. Does it get to the Device.OpenUri method?

  • CN_AndersonCN_Anderson C.N. Anderson USUniversity

    Hi AdamP, again, thank you for looking into this. I wanted to reply to your question.

    Does it get to the Device.OpenUri method?

    In the previous solution I posted, I believe the program never even got to the Device.OpenUri, because there is some error behind the scenes (that is not thrown) when Xamarin.Forms attempts to wire up the Navigating event. The only evidence is that the WebView does not load (although the page does load).

    In my most recent solution (I’ll post this shortly), yes, the program did get to the Device.OpenUri method (as long as the user responds affirmatively in the alert displayed). What I wanted to do with the e.Cancel= true was to cancel the redirect in the WebView, and open the link in an external browser. This should not cancel the load of the link click, only the allowing it to open in the WebView.

    The problem was that I was overriding LoadStarted (which I choose because it seemed self-explanatory and iOS has so many methods to choose from), but the LoadStarted method has no way to access the link that was clicked in the HTML. I needed that link so the Device.OpenUri method could send the user to the correct website. To get access to that link, you must override/create a delegate for the ShouldStartLoad, then the link is stored in the request that is sent in. I found that out from the Stack Overflow post “How can I link a local pdf file on html file - the html file is showing on UIWebView”. The posted code was written in Objective-C (or it could be Swift), so I just translated it to C#, Xamarin is great and has access to all the Apple APIs that I needed to rewrite it.

    I will post the full workaround solution (writing the iOS custom renderer) and full explanation shortly. I do think this is a bug with the Xamarin.Forms WebView Navigating event.

  • AlexRutherfordAlexRutherford Alex Rutherford GBUniversity ✭✭

    @CN_Anderson said:
    Please find the working solution with workaround attached and my full explanation below.

    The technique for the workaround is to use the ShouldStartLoad method, instead of the LoadStarted method when creating an overriding delegate for the WebView’s custom renderer on the Xamarin.iOS platform. The difference is that ShouldStartLoad intercepts the LoadRequest before the load is started and any action can be taken at this point, including canceling the load.

    If we try to intercept the LoadRequest at LoadStarted, it is too late too cancel the load, the load has already started, and although we can add additional steps at this point, we cannot cancel the load. When we intercept, or override, the ShouldStartLoad method, the navigation type is passed in, this way we can tell if the UIWebView is loading for the first time (on page load, the navigation type is Other, so we do not want to redirect when the method is called that time) or if the user has clicked a link. So in the ShouldStartLoad we need to check the UINavigationType and make sure it is of type LinkClicked and then we can call the code to redirect the user to the external browser.

    This really looks like a bug in Xamarin.Forms, especially since the WebView’s Navigating event works as expected on Android. I am guessing that in Xamarin.Forms the WebView’s Navigating event is incorrectly hooked into the iOS UIWebView’s LoadStarted method and by that point it is too late to cancel the load.

    The Xamarin.Forms WebView’s Navigating event should be hooked into the iOS UIWebView’s ShouldStartLoad method, so for now we need to create this connection manually using the Xamarin.iOS platform to get links clicked in the UIWebView to open in an external web browser.

    I have just ran into the same problem here and will (at least for now) be using your workaround so thank you for that. Also have you created a bug report for this? If so please may I have a link and if you have not I really think you should. I too think this is a bug in Xamarin forms, plus now it is open source we should see a soon!

  • CN_AndersonCN_Anderson C.N. Anderson USUniversity

    @AlexRutherford said:
    I have just ran into the same problem here and will (at least for now) be using your workaround so thank you for that. Also have you created a bug report for this? If so please may I have a link and if you have not I really think you should. I too think this is a bug in Xamarin forms, plus now it is open source we should see a soon!

    Hi @AlexRutherford, I am so glad that you found my solution helpful. I am not sure what the cause is, so I was not sure that anyone else would have this issue.

    I have not created a bug report and I have not had time to wade through the source code, but I did ask a Xamarin Engineer about it at Evolve 2016, and they were going to take a look and let me know any addition information they found out. I was waiting to hear back before I opened a bug.

    If you would like to file the bug, please do, and feel free to reference this post. If I hear back from the Xamarin Engineer, I’ll post here.

  • CN_AndersonCN_Anderson C.N. Anderson USUniversity
    edited May 2016

    Looks like the zip file got wiped when I marked the answer, please find the working solution with workaround attached and explanation here.

Sign In or Register to comment.