Reputation: 3133
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
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
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
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
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:
PageController.InternalChildren
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
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.
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
}
};
}
}
Upvotes: 0