How can I display an image until a WebView has finished loading?

I have a webview, and I want to display a 'loading' image that disappears once the webview has fully loaded. How can I achieve this using XAML and its code-behind?

Answers

  • JohnHardmanJohnHardman GBUniversity mod
    edited September 2018

    @SteveRussell -

    Put your Image and WebView in a Layout (e.g. a StackLayout).
    Initially set IsVisible=true on the Image and set IsVisible=false on the WebView.
    Add a Navigated event handler to the WebView.
    In the Navigated handler, change set IsVisible=true on the WebView and IsVisible=false on the Image.
    Trigger navigation of the WebView to the desired URL.
    You'll want to add similar code to handle errors/timeouts, but that's the gist of it.

  • SteveRussellSteveRussell Member ✭✭✭

    @JohnHardman Do you have an example of this?

  • JohnHardmanJohnHardman GBUniversity mod

    @SteveRussell said:
    @JohnHardman Do you have an example of this?

    I've implemented this, so know that it does work. It's not a piece of code that I can share though unfortunately.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Just a thought... Can you just put an image on the page with the WebView smack on top of it? If the WebView has no content you'll see through to the image. When the content is there, it will hide the image.

    Plan B -rg.plugins.popups
    You show a popup over the entire page while loading. That has the advantage of totally blocking all UI so the user doesn't get antsy and bang on the reload button 20 times.

  • SteveRussellSteveRussell Member ✭✭✭

    @ClintStLaurent My WebView has a lot of whitespace, so once it appears, it still won't completely hide the loading image.

    I was considering the Popup Plugin, but still needed the solution for disappearing once the WebView has finished loading

  • SteveRussellSteveRussell Member ✭✭✭

    private void WebForm_Navigated(object sender, WebNavigatedEventArgs e) { WebForm.IsVisible = true; AnimationView.IsVisible = false; }

    I have this, but my webform never appears, the page is stuck on showing my animationView. The URL is in the source of the WebView in XAML

    <local:ExtendedWebView Source = "{LINK}" HeightRequest="500" WidthRequest="500" Scale="0.95" IsVisible="False" x:Name="WebForm" Navigated="WebForm_Navigated"/>

  • JohnHardmanJohnHardman GBUniversity mod

    @SteveRussell said:
    I have this, but my webform never appears, the page is stuck on showing my animationView. The URL is in the source of the WebView in XAML

    <local:ExtendedWebView Source = "{LINK}" HeightRequest="500" WidthRequest="500" Scale="0.95" IsVisible="False" x:Name="WebForm" Navigated="WebForm_Navigated"/>

    Does the Navigated handler ever get called? I assume not from your post. There are many possible reasons why that would be the case.

    First, try replacing "{LINK}" with "https://visualstudio.microsoft.com/xamarin/"

    If that doesn't do it, then check you have permissions set appropriately for each platform. iOS is the most pernickety in this respect.

  • JoeMankeJoeManke USMember ✭✭✭✭✭

    Since you're subclassing the WebView, do you have custom renderers for it? Do those renderers set the WebViewClient (Android) and Delegate (iOS)? Because that is where the Navigating and Navigated events are fired from in the base renderers.

  • SteveRussellSteveRussell Member ✭✭✭

    @JoeManke This is my custom renderer on Android:

    `public class ExtendedWebViewRenderer : WebViewRenderer
    {
    static ExtendedWebView _xwebView = null;
    WebView _webView;

        public ExtendedWebViewRenderer(Context context) : base(context)
        {
    
        }
    
        class ExtendedWebViewClient : Android.Webkit.WebViewClient
        {
            public override async void OnPageFinished(WebView view, string url)
            {
                if (_xwebView != null)
                {
                    int i = 10;
                    while (view.ContentHeight == 0 && i-- > 0)
                    {
                        await System.Threading.Tasks.Task.Delay(100);
                    }
    
                    _xwebView.HeightRequest = view.ContentHeight;
                }
                base.OnPageFinished(view, url);
            }
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
            _xwebView = e.NewElement as ExtendedWebView;
            _webView = Control;
    
            if (e.OldElement == null)
            {
                _webView.SetWebViewClient(new ExtendedWebViewClient());
            }
            _webView.Reload();
        }
    }`
    

    And the delegate on iOS:

    `public class ExtendedUIWebViewDelegate : UIWebViewDelegate
    {
    ExtendedWebViewRenderer webViewRenderer;

    public ExtendedUIWebViewDelegate(ExtendedWebViewRenderer _webViewRenderer = null)
    {
        webViewRenderer = _webViewRenderer ?? new ExtendedWebViewRenderer();
    }
    
    public override async void LoadingFinished(UIWebView webView)
    {
        var wv = webViewRenderer.Element as ExtendedWebView;
        if (wv != null)
        {
            await System.Threading.Tasks.Task.Delay(100);
            wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
        }
    }
    

    }`

  • JohnHardmanJohnHardman GBUniversity mod
    edited September 2018

    @SteveRussell

    If i reaches 0, you'll not want to set HeightRequest in the following:

                    while (view.ContentHeight == 0 && i-- > 0)
                    {
                        await System.Threading.Tasks.Task.Delay(100);
                    }
    
                    _xwebView.HeightRequest = view.ContentHeight;
    

    You'll probably want to check the value of Height in the following, as well as call base.LoadingFinished:

    public override async void LoadingFinished(UIWebView webView)
    {
        var wv = webViewRenderer.Element as ExtendedWebView;
        if (wv != null)
        {
            await System.Threading.Tasks.Task.Delay(100);
            wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
        }
    }
    

    Whether calling Task.Delay in those bits of code will cause a problem, I'm not sure without investigating.

  • SteveRussellSteveRussell Member ✭✭✭

    @JohnHardman What difference does it make? (Sounds rude but it's not supposed to be!)

  • JohnHardmanJohnHardman GBUniversity mod
    edited September 2018

    @SteveRussell

    In the Android code, if the loop checking ContentHeight and i does not see a non-zero view.ContentHeight before i hits 0, the code explicitly requests the native view have a Height of 0. Whilst I wouldn't expect that to impact events being fired (but I haven't checked), it may not give you the UI that you want.

    In the iOS code, lack of a call to base.LoadingFinished will result in expected behavior not happening, including whether the Navigated event is fired. The relevant code is below:

                public override void LoadingFinished(UIWebView webView)
                {
                    if (webView.IsLoading)
                        return;
    
                    _renderer._ignoreSourceChanges = true;
                    var url = GetCurrentUrl();
                    WebView.SetValueFromRenderer(WebView.SourceProperty, new UrlWebViewSource { Url = url });
                    _renderer._ignoreSourceChanges = false;
    
                    var args = new WebNavigatedEventArgs(_lastEvent, WebView.Source, url, WebNavigationResult.Success);
                    WebView.SendNavigated(args);
    
                    _renderer.UpdateCanGoBackForward();
                }
    

    If you compare your Android and iOS code, you'll see that the Android code does call the base class in overridden virtual methods. The iOS code should for the same reasons.

  • SteveRussellSteveRussell Member ✭✭✭

    @JohnHardman My solution for the height request works, I just added it here because I thought I needed to add something in order to get the Navigated Handler to work. I do, however, have an issue with the height request on iOS. It works, but only when the user stops scrolling. The height doesn't change dynamically, only when the page is still, but that's a separate issue

  • JohnHardmanJohnHardman GBUniversity mod
    edited September 2018

    @SteveRussell said:
    My solution for the height request works

    Has the counter ever hit 0 during your testing?
    To test what happens, change the int i = 10; to int i = 0; temporarily. If it still works when view.ContentHeight == 0, the code is probably ok, otherwise there's a race condition.

Sign In or Register to comment.