imgen
imgen

Reputation: 3133

Reserve the loaded page content of WebView when switch away from current page in a Master / Detail layout in Xamarin Forms

I am using Master / Detail layout to build a Navigation Menu and a few pages. One of the page is a WebView control, nothing else. When I switch away from this WebView page from Navigation Menu and then switch back, the content of the WebView is gone, the state is also gone. This happens both on iOS and Android.

But if I navigate away from the WebView page to a completely different page (non-Master/Detail page), after coming back to WebView page, everything is fine. All the content and the state are preserved.

I have to reload the page, but user's operations will be lost. Is there a way to preserve the page's content and state and restore them without reloading the page?

Upvotes: 4

Views: 3206

Answers (5)

RandomUser
RandomUser

Reputation: 11

To fix this, I created a custom webview view renderer. When I create the new webview in the renderer, (e.g. new WKWebView(Frame, opts)) I replace it with a subclass of WKWebview that overrides Dispose, making it do nothing. Keep track of the original webview in OnElementChanged as desired.

Upvotes: 1

PlusInfosys
PlusInfosys

Reputation: 3446

Navigation controller keeps the existing UIViewController on the Navigation Stack, but when you pop the UIViewController, it removes view controller from the stack, hence its content gets deleted.

To preserve it, you need to have a shared instance of WebViewController, and always use the same WebViewController.

In your WebView controller write the following code to create a shared instance:

private static ViewController vc;

public static WebViewController SharedInstance()
{
    if (vc == null)
    {
        var storyboard = UIStoryboard.FromName("MainStoryboard", null);
        vc = storyboard.InstantiateViewController("WevViewController") as WevViewController;
    }

    return vc;
}

Whenever you want to display web view controller, use following

public void DisplayWebview()
{
    WevViewController webvw = WevViewController.SharedInstance();
    NavigationController.PushViewController(webvw,true);
}

Upvotes: 0

imgen
imgen

Reputation: 3133

I found a working solution, although more like a hack. That is instead of navigating to a sub-page in Master/Detail when an menu item is touched, I navigate to another page which means down 1 level. When user navigate back from that page, everything will be preserved. But this way, the navigation menu won't always be there anymore. But that's OK.

Upvotes: 0

BrewMate
BrewMate

Reputation: 1020

Unfortunately, you would need to modify the source code of Xamarin.Forms.MasterDetailPage to give the desired behavior. The problem is that when the Detail page is removed from the MasterDetail (switching from Webview to another page), all of the native controls are disposed; even if you keep a hard reference to the Xamarin.Forms.Webview or Xamarin.Forms.Page object.

In the source, you can see that if the _detail property is not null, it is removed from the MasterDetailPage.PageController.InternalChildren. You would want to put a check in there for your Xamarin.Forms.Page that contains the Xamarin.Forms.Webview and make sure it isn't removed from the page controller. This will persist the native webview and webview handlers so nothing is re-loaded. A few things to note:

  1. Make sure you also don't re-add your webview page to the MasterDetail PageController.InternalChildren
  2. The webview page will stay loaded into memory. This is important because if your website does a lot of javascript, storing things or more, you should just be aware that it will stay in memory even when it is not displayed

You can verify all of this by creating a new Forms project and replacing the App class code with what's below:

public class App : Application
{
    public App()
    {
        MainPage = new MasterDetail();
    }
}

public class MasterDetail : MasterDetailPage
{
    static WebViewPage persistentWebPage = new WebViewPage();

    public MasterDetail()
    {
        var masterPage = new MasterPage();
        persistentWebPage = new WebViewPage();

        Master = masterPage;
        Detail = persistentWebPage;

        masterPage.listview.ItemSelected += (sender, e) =>
        {
            var item = e.SelectedItem as string;

            if (string.IsNullOrEmpty(item))
                return;

            switch (item)
            {
                case "1":
                    Detail = persistentWebPage;
                    break;
                default:
                    Detail = new ContentPage { BackgroundColor = Color.Red };
                    break;
            }

            masterPage.listview.SelectedItem = null;
            IsPresented = false;
        };
    }
}

public class MasterPage : ContentPage
{
    public ListView listview = new ListView { ItemsSource = new string[] { "1", "2", "3" } };

    public MasterPage()
    {
        Title = "Master";
        Content = listview;
    }
}

public class WebViewPage : ContentPage
{
    public static WebView webView = new WebView { Source = "https://www.google.com/" };

    public WebViewPage()
    {
        Content = webView;
    }
}

Next create a renderer in the Android project (you could do iOS also, I just did Android) and copy the following code:

public class WebViewRenderer_Droid : WebViewRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
    {
        if (e.NewElement == null)
            Console.WriteLine("*********e.NewElement is null");
        else
            Console.WriteLine("*********e.NewElement is not null");

        if (e.OldElement == null)
            Console.WriteLine("*********e.OldElement is null");
        else
            Console.WriteLine("*********e.OldElement is not null");

        if (Control == null)
            Console.WriteLine("*********Control is null");
        else
            Console.WriteLine("*********Control is not null");

        base.OnElementChanged(e);
    }
}

Run the project and switch away from the webview page and back. You'll see that Control and e.OldElement are both null at one point. This is proof that your native handlers are being disposed even with a hard reference. That is because you don't have any control over how Xamarin.Forms handles the native control reference. From the source code of Xamarin.Forms, we can see that if Control is null, than everything gets recreated.

I hope this helps! It was fun digging deep to figure this one out.

Upvotes: 4

Brandon Minnick
Brandon Minnick

Reputation: 15340

In your App class, create static instance of the ContentPage containing the WebView. Creating the ContentPage as a static variable in the App class will preserve its state and ensure that it opens to the same webpage that the user most recently viewed.

Sample Code

using Xamarin.Forms;

namespace PersistWebViewSample
{
    public class StartPage : ContentPage
    {
        public StartPage()
        {
            var navigateToWebViewButton = new Button
            {
                Text = "Open Persistent Web View"
            };
            navigateToWebViewButton.Clicked += async (sender, e) => await Navigation.PushAsync(App.PersistentWebView);

            Title = "Start";

            Content = navigateToWebViewButton;
        }
    }

    public class App : Application
    {
        static readonly string xamarinUrl = "https://www.xamarin.com/";

        public App()
        {

            MainPage = new NavigationPage(new StartPage());
        }

        public static ContentPage PersistentWebView { get; } = new ContentPage
        {
            Title = "Persistent Web View",
            Content = new WebView
            {
                Source = xamarinUrl
            }
        };
    }
}

enter image description here

Upvotes: 0

Related Questions