Native Xamarin.IOS App with MVVMCross and Storyboard not working - xamarin

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
}

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" />

Show errors in Xamarin iOS WebView

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
}
}

Removing back swipe gesture from page using xamarin forms

Is there a way i can disable the back swipe to previous page option for iOS on one single page of my project ?
You can achieve this by implementing a custom renderer and setting the right property for this. You can see a sample implementation underneath. The right property, in this case, is InteractivePopGestureRecognizer which you need to set to false.
Do this in the ViewWillAppear so the NavigationController is initialized.
using DisableSwipe.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ContentPage), typeof(NoBackSwipeRenderer))]
namespace DisableSwipe.iOS
{
public class NoBackSwipeRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (ViewController?.NavigationController != null)
ViewController.NavigationController.InteractivePopGestureRecognizer.Enabled = false;
}
}
}
#Symorp
You could do it like so:
public class YourCustomPageRenderer : PageRenderer
{
private YourCustomPage _yourCustomPage;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
_yourCustomPage = e.NewElement as YourCustomPage;
if (_yourCustomPage != null)
{
_yourCustomPage.PropertyChanged += YourCustomPagePropertyChangedEventHandler;
}
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
SetInteractivePopGestureRecognizerEnabled(isEnabled: false);
}
private void YourCustomPagePropertyChangedEventHandler(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (propertyChangedEventArgs.PropertyName == nameof(YourCustomPage.IsInteractivePopGestureRecognizerEnabled))
{
SetInteractivePopGestureRecognizerEnabled(_yourCustomPage.IsInteractivePopGestureRecognizerEnabled);
}
}
private void SetInteractivePopGestureRecognizerEnabled(bool isEnabled)
{
var interactivePopGestureRecognizer = ViewController?.NavigationController?.InteractivePopGestureRecognizer;
if (interactivePopGestureRecognizer != null)
{
//Prevents the back-swipe-gesture when the user wants to swipe a page away (from left edge of the screen)
interactivePopGestureRecognizer.Enabled = isEnabled;
}
}
}
public class YourCustomPage : ContentPage
{
/// <summary>
/// If you need it as bindable property, feel free to create a <see cref="BindableProperty"/>.
/// </summary>
public bool IsInteractivePopGestureRecognizerEnabled { get; set; }
}
Feel free to adjust to your needs! :-)
I omitted the export renderer attribute etc., just for simplicity.

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);
}
}

Listening for UITableView events inside a View Controller

I created a View Controller using storyboards and dropped a UITableView on top of it.
My issue is that I can not figure out how to listen for row selections in the UITableView.
I know how it works in UITableViewController. You override RowSelected. But this is a table INSIDE of a View Controller.
I am new to Xamarin, and this is driving me crazy. I would appreciate any help.
I prefer to use something similar to this solution:
public class TestTableViewSource : UITableViewSource
{
public delegate void RowSelectedEventHandler(NSIndexPath selectedIndexPath);
public event RowSelectedEventHandler RowSelectedEvent;
protected virtual void OnRowSelectedEvent(NSIndexPath selectedindexpath)
{
RowSelectedEventHandler handler = RowSelectedEvent;
if (handler != null) handler(selectedindexpath);
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
OnRowSelectedEvent(indexPath);
tableView.DeselectRow(indexPath, true);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
throw new NotImplementedException();
}
public override nint RowsInSection(UITableView tableview, nint section)
{
throw new NotImplementedException();
}
}
So you can do this in your UIViewController:
TestTableViewSource source = new TestTableViewSource();
source.RowSelectedEvent += RowSelected;
YourTableView.Source = source;
private void RowSelected(NSIndexPath path)
{
// handle the selected row.
}
Of course this solution can be further improved by extracting RowSelected to an abstract class so you can reuse it later - but that's up to you :).

Resources