Show errors in Xamarin iOS WebView - xamarin

The Xamarin Forms WebView control shows a white surface if connection fails. On iOS, I want to show any error information I can get.
I am using this code to override WkWebViewRenderer:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.iOS
{
public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
public HybridWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
}
}
I have also found this post which is now obsolete:
https://forums.xamarin.com/discussion/175060/how-can-i-display-a-detailed-error-code-retuned-from-a-webview?
How do I handle LoadFailed/Connection errors and display them to the user with WkWebViewRenderer?

For WkWebViewRenderer we need to implement the WKNavigationDelegate
public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
public HybridWebViewRenderer()
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.NewElement!=null)
{
this.NavigationDelegate = new NavigationDelegate();
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
throw new NotImplementedException();
}
}
public class NavigationDelegate : WKNavigationDelegate
{
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, WKWebpagePreferences preferences, Action<WKNavigationActionPolicy, WKWebpagePreferences> decisionHandler)
{
// base.DecidePolicy(webView, navigationAction, preferences, decisionHandler);
decisionHandler.Invoke(WKNavigationActionPolicy.Allow, preferences);
}
public override void DidFailNavigation(WKWebView webView, WKNavigation navigation, NSError error)
{
base.DidFailNavigation(webView, navigation, error);
//...load fail
}
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
base.DidFinishNavigation(webView, navigation);
//...load success
}
}

Related

CS0115 'HybridWebViewRenderer.OnCreateWindow(WebView, bool, bool, Message)': no suitable method found to override - Xamarin.Forms

I am trying to implement this solution offered as an answer here: Opening target="_blank" links with Xamarin.Forms WebView
In the Android project I have created a custom renderer which has these code excerpts:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(MyApp.controls.HybridWebViewRenderer))]
public class HybridWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
Control.Settings.SetSupportMultipleWindows(true);
}
}
In my shared project I have created a HybridWebView which has among others these excerpts of code:
public class HybridWebView : WebView
{
public override bool OnCreateWindow(Android.Webkit.WebView view, bool isDialog, bool isUserGesture, Android.OS.Message resultMsg)
{
Android.Webkit.WebView newWebView = new Android.Webkit.WebView(_context);
view.AddView(newWebView);
Android.Webkit.WebView.WebViewTransport transport = (Android.Webkit.WebView.WebViewTransport)resultMsg.Obj;
transport.WebView = newWebView;
resultMsg.SendToTarget();
return true;
}
}
This gives as a result the following error:
CS0115 'HybridWebViewRenderer.OnCreateWindow(WebView, bool, bool, Message)': no suitable method found to override
Any idea how to overcome this issue?
As far as I'm concerned,we do not need to override the OnCreateWindow in custom renderer in Target project.Also, please take care of the OnElementChanged method in Custom Webview.We need set SetSupportMultipleWindows to true,enabled JS,set SetWebChromeClient, set WebViewClient,set AddJSInterface and last but not least, LoadUrl for the webview.
Below is the code snippet for your reference:
[assembly: ExportRenderer(typeof(HybridWebView),
typeof(HybridWebViewRenderer))]
namespace AppHybridWebView.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Settings.SetSupportMultipleWindows(false);
Control.Settings.JavaScriptEnabled = true;
Control.SetWebChromeClient(new MyWebChromeClient());
Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");//LoadUrl
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
public class MyWebChromeClient : WebChromeClient
{
public override bool OnCreateWindow(Android.Webkit.WebView view, bool isDialog, bool isUserGesture, Message resultMsg)
{
if (!isDialog)
{
return true;
}
return base.OnCreateWindow(view, isDialog, isUserGesture, resultMsg);
}
}
}
Code in xaml:
<local:HybridWebView x:Name="hybridWebView" Uri="index.html" />

How to hide back button in navigation bar using Xamarin.Forms - AppShell?

Currently I'm using AppShell in my xamarin app. I need to hide back button arrow in NavBar and replace it with menu icon.
NavigationPage.HasBackButton="False" Isn't working for me.
You could use the custom renderer to reset the NavigationIcon. I use a star icon for reference.
[assembly: ExportRenderer(typeof(AppShell), typeof(ShellCustomRenderer))]
namespace ShellDemo.Droid
{
public class ShellCustomRenderer : ShellRenderer
{
public ShellCustomRenderer(Context context) : base(context)
{
}
protected override IShellToolbarAppearanceTracker CreateToolbarAppearanceTracker()
{
return new ToolbarAppearance();
}
}
public class ToolbarAppearance : IShellToolbarAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(Android.Support.V7.Widget.Toolbar toolbar, IShellToolbarTracker toolbarTracker)
{
//toolbar.SetBackgroundColor(Android.Graphics.Color.Red);
toolbar.SetNavigationIcon(Resource.Drawable.star_small);// Resource.Drawable.star_small;
}
public void SetAppearance(Android.Support.V7.Widget.Toolbar toolbar, IShellToolbarTracker toolbarTracker, ShellAppearance appearance)
{
//toolbar.SetBackgroundColor(Android.Graphics.Color.Red);
toolbar.SetNavigationIcon(Resource.Drawable.star_small);
}
}
}

How to migrate MVVMCross based Xamarin.android project into Intune managed one

I have a android project running smoothly, it uses MVVMCross at its core.
The problem came when I was asked manage the app protection policies with Intune.
Now Intune is forcing me to use their managed activity and all other managed namespaces provided by Intune SDK.
In that case, how I can proceed with it?
I tried changing activities base class to Intune's one, in hope to use general things provided by Mvvmcross, such as IOC, dependency injections.
I customised App startup as Intune wants that means there will not be any setup/app.cs class calls involvement.
So I launch Splash activity -> and it launches MainActivity, in MainActivity I am manually injecting all the Dependencies which I require.
Because all these syntaxes are throwing exception under Intune managed activities
example: Mvx.RegisterType<IDeviceInformation, DeviceInformation>();
Above throws exception.
How do I proceed with this migration keeping MVVMcross basic functionality intact?
There is a couple of solutions to that matter that I can think of.
If you only need the DI you can add another DI manager package and handle it from there which will be simpler than configuring Mvx to do that only.
If you need other capabilities of Mvx then you will have to do what Mvx does in its base classes and implement them taking into consideration setting the appropiate interfaces to your base classes.
In Android, in order to get the Setup and Activities working you'll have to:
Register your setup in your android Application file as done here
this.RegisterSetupType<TMvxAndroidSetup>();
Implement your own base activity that takes into consideration the implementation of IMvxEventSourceActivity such as here and also the MvxActivity like here in order to have the events and the data context / viewmodel handling
[Register("mvvmcross.platforms.android.views.base.MvxEventSourceActivity")]
public abstract class MvxEventSourceActivity
: Activity, IMvxEventSourceActivity
{
protected MvxEventSourceActivity()
{
}
protected MvxEventSourceActivity(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected override void OnCreate(Bundle bundle)
{
CreateWillBeCalled.Raise(this, bundle);
base.OnCreate(bundle);
CreateCalled.Raise(this, bundle);
}
protected override void OnDestroy()
{
DestroyCalled.Raise(this);
base.OnDestroy();
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
NewIntentCalled.Raise(this, intent);
}
protected override void OnResume()
{
base.OnResume();
ResumeCalled.Raise(this);
}
protected override void OnPause()
{
PauseCalled.Raise(this);
base.OnPause();
}
protected override void OnStart()
{
base.OnStart();
StartCalled.Raise(this);
}
protected override void OnRestart()
{
base.OnRestart();
RestartCalled.Raise(this);
}
protected override void OnStop()
{
StopCalled.Raise(this);
base.OnStop();
}
public override void StartActivityForResult(Intent intent, int requestCode)
{
StartActivityForResultCalled.Raise(this, new MvxStartActivityForResultParameters(intent, requestCode));
base.StartActivityForResult(intent, requestCode);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
ActivityResultCalled.Raise(this, new MvxActivityResultParameters(requestCode, resultCode, data));
base.OnActivityResult(requestCode, resultCode, data);
}
protected override void OnSaveInstanceState(Bundle outState)
{
SaveInstanceStateCalled.Raise(this, outState);
base.OnSaveInstanceState(outState);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
DisposeCalled.Raise(this);
}
base.Dispose(disposing);
}
public event EventHandler DisposeCalled;
public event EventHandler<MvxValueEventArgs<Bundle>> CreateWillBeCalled;
public event EventHandler<MvxValueEventArgs<Bundle>> CreateCalled;
public event EventHandler DestroyCalled;
public event EventHandler<MvxValueEventArgs<Intent>> NewIntentCalled;
public event EventHandler ResumeCalled;
public event EventHandler PauseCalled;
public event EventHandler StartCalled;
public event EventHandler RestartCalled;
public event EventHandler StopCalled;
public event EventHandler<MvxValueEventArgs<Bundle>> SaveInstanceStateCalled;
public event EventHandler<MvxValueEventArgs<MvxStartActivityForResultParameters>> StartActivityForResultCalled;
public event EventHandler<MvxValueEventArgs<MvxActivityResultParameters>> ActivityResultCalled;
}
[Register("mvvmcross.platforms.android.views.MvxActivity")]
public abstract class MvxActivity
: MvxEventSourceActivity
, IMvxAndroidView
{
protected View _view;
protected MvxActivity(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected MvxActivity()
{
BindingContext = new MvxAndroidBindingContext(this, this);
this.AddEventListeners();
}
public object DataContext
{
get { return BindingContext.DataContext; }
set { BindingContext.DataContext = value; }
}
public IMvxViewModel ViewModel
{
get
{
return DataContext as IMvxViewModel;
}
set
{
DataContext = value;
OnViewModelSet();
}
}
public void MvxInternalStartActivityForResult(Intent intent, int requestCode)
{
StartActivityForResult(intent, requestCode);
}
public IMvxBindingContext BindingContext { get; set; }
public override void SetContentView(int layoutResId)
{
_view = this.BindingInflate(layoutResId, null);
SetContentView(_view);
}
protected virtual void OnViewModelSet()
{
}
protected override void AttachBaseContext(Context #base)
{
if (this is IMvxSetupMonitor)
{
// Do not attach our inflater to splash screens.
base.AttachBaseContext(#base);
return;
}
base.AttachBaseContext(MvxContextWrapper.Wrap(#base, this));
}
private readonly List<WeakReference<Fragment>> _fragList = new List<WeakReference<Fragment>>();
public override void OnAttachFragment(Fragment fragment)
{
base.OnAttachFragment(fragment);
_fragList.Add(new WeakReference<Fragment>(fragment));
}
public List<Fragment> Fragments
{
get
{
var fragments = new List<Fragment>();
foreach (var weakReference in _fragList)
{
if (weakReference.TryGetTarget(out Fragment f))
{
if (f.IsVisible)
fragments.Add(f);
}
}
return fragments;
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
ViewModel?.ViewCreated();
}
protected override void OnDestroy()
{
base.OnDestroy();
ViewModel?.ViewDestroy(IsFinishing);
}
protected override void OnStart()
{
base.OnStart();
ViewModel?.ViewAppearing();
}
protected override void OnResume()
{
base.OnResume();
ViewModel?.ViewAppeared();
}
protected override void OnPause()
{
base.OnPause();
ViewModel?.ViewDisappearing();
}
protected override void OnStop()
{
base.OnStop();
ViewModel?.ViewDisappeared();
}
}
public abstract class MvxActivity<TViewModel>
: MvxActivity
, IMvxAndroidView<TViewModel> where TViewModel : class, IMvxViewModel
{
public new TViewModel ViewModel
{
get { return (TViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
}
Also you'll have to implement your own splash activity like here which implements the IMvxSetupMonitor and is the one who ends up calling the setup here by calling MvxAndroidSetupSingleton.EnsureSingletonAvailable(ApplicationContext); and initializing a monitor.
[Register("mvvmcross.platforms.android.views.MvxSplashScreenActivity")]
public abstract class MvxSplashScreenActivity
: MvxActivity, IMvxSetupMonitor
{
protected const int NoContent = 0;
private readonly int _resourceId;
private Bundle _bundle;
public new MvxNullViewModel ViewModel
{
get { return base.ViewModel as MvxNullViewModel; }
set { base.ViewModel = value; }
}
protected MvxSplashScreenActivity(int resourceId = NoContent)
{
RegisterSetup();
_resourceId = resourceId;
}
protected MvxSplashScreenActivity(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected virtual void RequestWindowFeatures()
{
RequestWindowFeature(WindowFeatures.NoTitle);
}
protected override void OnCreate(Bundle bundle)
{
RequestWindowFeatures();
_bundle = bundle;
var setup = MvxAndroidSetupSingleton.EnsureSingletonAvailable(ApplicationContext);
setup.InitializeAndMonitor(this);
base.OnCreate(bundle);
if (_resourceId != NoContent)
{
// Set our view from the "splash" layout resource
// Be careful to use non-binding inflation
var content = LayoutInflater.Inflate(_resourceId, null);
SetContentView(content);
}
}
private bool _isResumed;
protected override void OnResume()
{
base.OnResume();
_isResumed = true;
var setup = MvxAndroidSetupSingleton.EnsureSingletonAvailable(ApplicationContext);
setup.InitializeAndMonitor(this);
}
protected override void OnPause()
{
_isResumed = false;
var setup = MvxAndroidSetupSingleton.EnsureSingletonAvailable(ApplicationContext);
setup.CancelMonitor(this);
base.OnPause();
}
public virtual async Task InitializationComplete()
{
if (!_isResumed)
return;
await RunAppStartAsync(_bundle);
}
protected virtual async Task RunAppStartAsync(Bundle bundle)
{
if (Mvx.IoCProvider.TryResolve(out IMvxAppStart startup))
{
if(!startup.IsStarted)
{
await startup.StartAsync(GetAppStartHint(bundle));
}
else
{
Finish();
}
}
}
protected virtual object GetAppStartHint(object hint = null)
{
return hint;
}
protected virtual void RegisterSetup()
{
}
}
public abstract class MvxSplashScreenActivity<TMvxAndroidSetup, TApplication> : MvxSplashScreenActivity
where TMvxAndroidSetup : MvxAndroidSetup<TApplication>, new()
where TApplication : class, IMvxApplication, new()
{
protected MvxSplashScreenActivity(int resourceId = NoContent) : base(resourceId)
{
}
protected override void RegisterSetup()
{
this.RegisterSetupType<TMvxAndroidSetup>();
}
}
This will cover the basics I think.
Hope it helps you to get you to the right direction

Native Xamarin.IOS App with MVVMCross and Storyboard not working

I created a new MVVMCross Native project, migrated it to v6.2.2. Created a single view called HomeView similar to https://www.mvvmcross.com/documentation/tutorials/tipcalc/a-xamarinios-ui-project
The xib page has only one UITextField and UIButton. However after the app starts I get the following exception
Name: NSInvalidArgumentException Reason: -[HomeView _setViewDelegateContentOverlayInsetsAreClean:]: unrecognized selector sent to instance 0x7fe8edc3cd70
My HomeView.cs file is as follows:
[MvxRootPresentation(WrapInNavigationController = true)]
public partial class HomeView : MvxViewController
{
public HomeView() : base("HomeView", null){}
public HomeView(IntPtr handle) : base(handle){}
public override void DidReceiveMemoryWarning()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
#region View lifecycle
public override void ViewDidLoad()
{
base.ViewDidLoad();
var set = this.CreateBindingSet<HomeView, HomeViewModel>();
set.Bind(TextField).To(vm => vm.Text);
set.Bind(Button).To(vm => vm.ResetTextCommand);
set.Apply();
// Perform any additional setup after loading the view, typically from a nib.
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
}
#endregion
}

Xamarin: override PushAsync on Android navigation

I my app, I create a custom navigation rendered, OnPushAsync and PopViewController are override and it works on iOS.
public class NavRenderer : NavigationRenderer
{
protected override async Task<bool> OnPushAsync(Page page, bool animated)
{ ...
}
public override UIViewController PopViewController(bool animated)
{
....
return base.PopViewController(false);
}
Trying to do the same (?) on Android, how can override OnPushAsync and PopViewController?
Tx
You can override these methods in android renderer to push & pop Pages
public class NavRenderer : NavigationRenderer
{
public NavRenderer(Context context) : base(context)
{
}
protected override Task<bool> OnPushAsync(Page view, bool animated)
{
return base.OnPushAsync(view, animated);
}
protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
return base.OnPopViewAsync(page, animated);
}
}
These are the equivelent methods in your iOS renderer to push & pop Controllers
public class NavRenderer : NavigationRenderer
{
public override void PushViewController(UIViewController viewController, bool animated)
{
base.PushViewController(viewController, animated);
}
public override UIViewController PopViewController(bool animated)
{
return base.PopViewController(animated);
}
}

Resources