Xamarin Webview Gestures not firing - webview

I want to add a pinch event to my webview and did so according to the Xamarin docs:
PinchGestureRecognizer pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += ClearCache;
webview.GestureRecognizers.Add(pinch);
However, the event isn't firing. Is there something else I need to do to make it work?
Here's a messier scattergun approach with all solutions that I've come across that's also not working:
public class SettingsPage : ContentPage {
ICommand tapCommand;
public SettingsPage() {
var url = Path.Combine(DependencyService.Get<IBaseUrl>().Get(), "index.html");
WebView webview = new WebView() {
Source = new UrlWebViewSource { Url = url },
WidthRequest = 1000,
HeightRequest = 1000
};
//webview.Navigating += ClearCache; ********THIS WORKS*********
Padding = Device.OnPlatform<Thickness>(
new Thickness(1, 20, 1, 1),
new Thickness(1),
new Thickness(1)
);
PinchGestureRecognizer pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += ClearCache;
webview.GestureRecognizers.Add(pinch);
tapCommand = new Command(OnTapped);
TapGestureRecognizer tap = new TapGestureRecognizer { NumberOfTapsRequired = 3 };
tap.Tapped += ClearCache;
tap.SetBinding(TapGestureRecognizer.CommandProperty, "TapCommand");
tap.Command = tapCommand;
webview.GestureRecognizers.Add(tap);
StackLayout stackLayout = new StackLayout { Spacing = 0 };
stackLayout.Children.Add(webview);
stackLayout.Children.Add(btnClearCache);
View parent = (View)webview.ParentView;
parent.GestureRecognizers.Add(tap);
parent.GestureRecognizers.Add(pinch);
Content = webview;
}
private void OnTapped(object obj) {
var service = DependencyService.Get<IPlatformService>();
service.ClearWebViewCache();
}
private void ClearCache(object sender, EventArgs e) {
var service = DependencyService.Get<IPlatformService>();
service.ClearWebViewCache();
}
public ICommand TapCommand {
get { return tapCommand; }
}

he WebView is quite nasty when it comes to gestures.
You can use MR.Gestures to handle the Down and Up gestures on iOS, Android and WinPhone 8.0 Silverlight. For Win Phone 8.1 / WinRT I didn't find a way yet.
If you only need iOS and Android, then all the gestures work on the WebView.
More info in the compatibility table on mrgestures.com.

Related

xamarin.forms webview.focused event raised on Android, but not on iOS

I'm using a Xamarin.Forms grid application to show a couple of html elements as WebViews in the cells of the grid CardGrid:
private async void CreateCardView()
{
CardGrid.Children.Clear();
// idx over all count elements of html snippets
for (idx = 0; idx < count; idx++)
{
string html = AuthoringCard(idx);
RenderingCard(html, idx);
}
}
AuthoringCard() creates the html code snippet.
RenderingCard() creates the WebView inside the grid cell.
private void RenderingCard(string htmlCard, int index)
{
int CardWidth = 300;
int CardHeight = 150;
int CardNoHorizontally = 3;
WebView uiCard = new WebView();
uiCard.HeightRequest = CardHeight - 5;
uiCard.WidthRequest= CardWidth - 5;
uiCard.VerticalOptions = LayoutOptions.Start;
uiCard.HorizontalOptions = LayoutOptions.Center;
uiCard.Margin = new Thickness(0);
uiCard.AutomationId = index.ToString();
uiCard.Focused += Card_Tapped;
uiCard.InputTransparent = false;
var htmlSource1 = new HtmlWebViewSource
{
Html = htmlCard,
};
uiCard.Source = htmlSource1;
CardGrid.Children.Add(uiCard);
int row = (int)Math.Floor((double)(index / CardNoHorizontally));
int column = index - (row * CardNoHorizontally);
Grid.SetRow(uiCard, row);
Grid.SetColumn(uiCard, column);
}
I want to catch the Focused event, when the user it tapping on the card (WebView) and using the AutomationId to get the index of the card (html code snippet):
private void Card_Tapped(object sender, EventArgs e)
{
WebView card = (WebView)sender;
int index = Convert.ToInt16(card.AutomationId));
}
This works fine with Android. Under iOS the event is never raised. Any idea for a solution?
Cause:
the property Focus in Forms correspond method that we called becomeFirstResponder in native iOS platform.Unfortunately,UIWebView and WKwebview do not support the method becomeFirstResponder.This method is only available in some 'input-controls' Such as UITextField and UITextView(Entry in Forms).So even if you set the event on a webview.It will not work in iOS.
Workaround:
You can add a TapGestureRecognizer on the webview.And you have to implement it by using CustomRenderer.Because it will create conflict if you add TapGestureRecognizer in forms.
Refer to the following code.
in Forms
public MainPage()
{
if (Device.RuntimePlatform == "iOS")
{
MessagingCenter.Subscribe<Object,string>(this,"webview_click", (sender,arg)=> {
// int index = Convert.ToInt16(arg));
});
}
}
in iOS
using System;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using xxx;
using xxx.iOS;
using ObjCRuntime;
[assembly:ExportRenderer(typeof(WebView),typeof(MyWebViewRenderer))]
namespace xxx.iOS
{
public class MyWebViewRenderer : WebViewRenderer, IUIGestureRecognizerDelegate
{
public bool isFirstLoad = true;
public MyWebViewRenderer()
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e != null)
{
UITapGestureRecognizer tap = new UITapGestureRecognizer(this, new Selector("Tap_Handle:"));
tap.WeakDelegate = this;
this.AddGestureRecognizer(tap);
}
}
[Export("gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:")]
public bool ShouldRecognizeSimultaneously(UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer)
{
return true;
}
[Export("Tap_Handle:")]
void Tap_Handle(UITapGestureRecognizer tap)
{
if(isFirstLoad)
{
isFirstLoad = false;
MessagingCenter.Send<Object,string>(this, "webview_click",Element.AutomationId);
}
}
}
}

Xamarin/Android webview hide website element/class

I'm trying to make a little android app showing a webview loading a website.
I got it to show it with the following code snippets. But what I need now is to hide some elements on the header and the footer(a menu for example). I thought I could do it not loading some classes from the webpage, but I'm not sure how to properly do it. Do anyone have some experience on this to share some light? :)
Thanks in advance.
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);
fab.Click += FabOnClick;
DrawerLayout drawer = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, Resource.String.navigation_drawer_open, Resource.String.navigation_drawer_close);
drawer.AddDrawerListener(toggle);
toggle.SyncState();
NavigationView navigationView = FindViewById<NavigationView>(Resource.Id.nav_view);
navigationView.SetNavigationItemSelectedListener(this);
wbv = FindViewById<WebView>(Resource.Id.webView1);
wbv.SetWebViewClient(new ExtendWebViewClient());
WebSettings webSettings = wbv.Settings;
webSettings.JavaScriptEnabled = true;
wbv.LoadUrl(txtUrl);
}
internal class ExtendWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
view.LoadUrl(url);
return true;
}
}
edit: Just to reflect the changes to the second class
public class ExtendWebViewClient : WebViewClient
{
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
string js = "var myElements = document.getElementsByClassName('fusion-main-menu');" +
" myElements[0].style.display = 'none'; ";
if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
{
view.EvaluateJavascript(js, null);
}
else
{
view.LoadUrl(js);
}
}
}
Since you have enabled javascript, there will be a function to run the script in the webview.
This is the code of WebView using Xamarin.Forms.WebView and this is how you can hide any element of webview using it's id.
var hide = await webview.EvaluateJavaScriptAsync("document.getElementById('UserName').style.display = 'none';");

UIPageViewController shows blank view controller when setting programmatically

I have a UIPageViewController in which I've implemented a caching mechanism. Practically I have a pool of view controllers that I try to reuse as much as possible when the next or previous view controller is requested in the UIPageViewControllerDataSource. So when the data source requests the previous or the next page I first check if that page has already been shown and return the appropriate view controller.
It all works fine with the default swipe gesture, and the internal method to change page. Now I need to disable the swipe gesture, so I'm not setting the data source, and instead, I have two buttons to go to the previous and next page, and using setViewControllers(_:direction:animated:completion:) to change page programmatically. It works fine when going always in the same direction, but as soon as I change it, and so I'm using a cached view controller, it shows a blank page.
The code is exactly the same as before, if not for setting the view controller programmatically. I've tried both with or without animation and the result is the same and if I remove caching it also works well. My question is, am I forgetting something? Is the UIPageViewController doing something internally that I'm not doing when the data source is assigned?
EDIT
Here's a simplified version of the code. It's a Xamarin Project, but I suppose it's understandable
public class MyPageViewController : AbstractPageViewController
{
const int CacheCapacity = 5;
public int initialItemId { get; set; }
public List<int> ItemIds { get; set; }
readonly List<ChildViewController> viewControllerCache = new List<ChildViewController>(CacheCapacity + 1);
public override void ViewDidLoad()
{
base.ViewDidLoad();
DataSource = null;
var vc = GetDocumentViewController(initialItemId);
SetViewControllers(new[] { vc }, UIPageViewControllerNavigationDirection.Forward, false, null);
var nextButtonItem = new UIBarButtonItem
{
Enabled = true
};
var previousButtonItem = new UIBarButtonItem
{
Enabled = true
};
nextButtonItem.Clicked += NextDocumentButton_Clicked;
previousButtonItem.Clicked += PreviousDocumentButton_Clicked;
var rightButtons = new UIBarButtonItem[2];
rightButtons[0] = nextButtonItem;
rightButtons[1] = previousButtonItem;
NavigationItem.SetRightBarButtonItems(rightButtons, false);
}
private void PreviousDocumentButton_Clicked(object sender, EventArgs e)
{
var referenceVc = (ChildViewController)ViewControllers.FirstOrDefault();
var referenceId = referenceVc.ItemId;
var index = ItemIds.FindIndex(dp => dp.Id == referenceId);
if (index < 1)
return;
var previousDocumentId = ItemIds[index - 1];
var vc = GetDocumentViewController(previousDocumentId);
SetViewControllers(new[] { vc }, UIPageViewControllerNavigationDirection.Reverse, false, null);
}
private void NextDocumentButton_Clicked(object sender, EventArgs e)
{
var referenceVc = (ChildViewController)ViewControllers.FirstOrDefault();
var referenceId = referenceVc.ItemId;
var index = ItemIds.FindIndex(dp => dp.Id == referenceId);
if (index < 0 || index >= ItemIds.Count)
return;
var nextDocumentId = ItemIds[index + 1];
var vc = GetDocumentViewController(nextDocumentId);
SetViewControllers(new[] { vc }, UIPageViewControllerNavigationDirection.Forward, false, null);
}
ChildViewController GetDocumentViewController(int ItemId)
{
var cachedViewController = viewControllerCache.FirstOrDefault(dvc => dvc.ItemId == ItemId);
if (cachedViewController != null)
return cachedViewController;
var vc = new ChildViewController();
vc.SetData(itemId);
viewControllerCache.Add(vc);
if (viewControllerCache.Count > CacheCapacity)
{
viewControllerCache[0].RecycleIfNeeded();
viewControllerCache.RemoveAt(0);
}
return vc;
}
}
It turned out that it was due to a recycling mechanism that was implemented in my code.
In particular, there's a forced recycling of the content of the view controller on ViewDidDisappear. I've disabled it in this case and then it works as it should. This also means that when the UIPageViewController has a data source, and is managing the child view controller directly, ViewDidDisappear is not called when changing page through swiping.

xamarin.forms android - linear layout not presented as expected

I have this code for custom keyboard.
Its xamarin.forms for android.
I want the keyboard to be presented from bottom of the page and to raise the Entry if needed.
the result is that the keyboard is presented on top of the page and covers the entry if the entry is on the top.
~
public class CustomEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
Android.InputMethodServices.Keyboard numericKeyboard = new Android.InputMethodServices.Keyboard(Control.Context, Resource.Xml.keyboard2);
CustomKeyboardView numericKeyboardView = new CustomKeyboardView(Control.Context, null);
numericKeyboardView.Id = Control.Id;
numericKeyboardView.Keyboard = numericKeyboard;
numericKeyboardView.Visibility = ViewStates.Gone;
numericKeyboardView.PreviewEnabled = false;
/////////////////////////////////////////////////
// THIS IS THE LAYOUT CREATION
////////////////////////////////////////////////
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent); // maybe WrapContent on all
lp.Gravity = GravityFlags.Bottom;
lp.BottomMargin = 0;
/////////////////////////////////////////////////
Activity activity = this.Context as Activity;
activity.AddContentView(numericKeyboardView, lp);
Control.Touch += (sender, ex2) =>
{
if (numericKeyboardView.Visibility == ViewStates.Gone)
{
//Xamarin.Forms.Animation animation = Android.Views.Animations.AnimationUtils.LoadAnimation(
Android.Views.Animations.Animation animation = Android.Views.Animations.AnimationUtils.LoadAnimation(
this.Context,
Resource.Animation.slide_in_bottom
);
numericKeyboardView.ShowWithAnimation(animation);
numericKeyboardView.Visibility = ViewStates.Visible;
}
ex2.Handled = true;
};
}
}
~
I want the keyboard to be presented from bottom of the page and to raise the Entry if needed.
You need to wrap your numericKeyboardView with a RelativeLayout and set the Rule of AlignParentBottom.
In OnElementChanged method, add the following codes:
//Create RelativeLayout for Keyboard
Android.Widget.RelativeLayout rl = new Android.Widget.RelativeLayout(this.Context);
//Create the LayoutParams for Keyboard
Android.Widget.RelativeLayout.LayoutParams rlp = new Android.Widget.RelativeLayout.LayoutParams(LayoutParams.FillParent, LayoutParams.WrapContent);
//set the AlignParentBottom rule
rlp.AddRule(LayoutRules.AlignParentBottom);
//set the LayoutParams to Keyboard
numericKeyboardView.LayoutParameters = rlp;
//add the keyboard to RelativeLayout
rl.AddView(numericKeyboardView);
//get current activity
Activity activity = this.Context as Activity;
//Add the Layout View to activity
activity.AddContentView(rl,rlp);

Change "Active" icon on selected NavigationPage when using TabbedPage

I'm very, very new to Xamarin.Forms. My task, if it is possible, and I'm not sure if it is, is to change our icon from the default blue when it is active.
I was given icons that are orange and they would like to display those or at least the color instead of the default blue. Again, I'm not sure if this is possible.
This is the code I'm using for the tabbed page.
public class LandingPage : TabbedPage
{
public LandingPage ()
{
NavigationPage homepage = new NavigationPage (new CarouselPage {
Title = "Title",
Children = {
//code removed
}
});
NavigationPage eventspage = new NavigationPage (new ContentPage {
Title = "Calendar Event List",
Content = new EventList ()
});
NavigationPage morepage = new NavigationPage (new MorePage ());
homepage.BarBackgroundColor = Device.OnPlatform (Color.FromHex (DependencyService.Get<IContentStrings>().BarBackgroundColor), Color.Transparent, Color.Transparent);
homepage.BarTextColor = Color.FromHex(DependencyService.Get<IContentStrings>().BarTextColor);
homepage.Title = DependencyService.Get<IContentStrings>().HomeTitle;
homepage.Icon = DependencyService.Get<IContentStrings>().HomeImage;
eventspage.BarBackgroundColor = Device.OnPlatform (Color.FromHex (DependencyService.Get<IContentStrings>().BarBackgroundColor), Color.Transparent, Color.Transparent);
eventspage.BarTextColor = Color.FromHex(DependencyService.Get<IContentStrings>().BarTextColor);
eventspage.Title = DependencyService.Get<IContentStrings>().EventTitle;
eventspage.Icon = DependencyService.Get<IContentStrings>().EventImage;
morepage.BarBackgroundColor = Device.OnPlatform (Color.FromHex (DependencyService.Get<IContentStrings>().BarBackgroundColor), Color.Transparent, Color.Transparent);
morepage.BarTextColor = Color.FromHex(DependencyService.Get<IContentStrings>().BarTextColor);
morepage.Title = DependencyService.Get<IContentStrings>().MoreTitle;
morepage.Icon = DependencyService.Get<IContentStrings>().MoreImage;
Children.Add (homepage);
Children.Add (eventspage);
Children.Add (morepage);
}
}
I'm not sure if I'm able to use a custom renderer or anything. I do not know if I have any options and any guidance is greatly appreciated!
You can set the active tab icon color with a simple custom iOS renderer like this:
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))]
namespace MyApp.iOS.Renderers
{
public class MyTabbedPageRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
TabBar.TintColor = UIColor.Orange;
}
}
}
I found was finally able to find the answer after searching the internet a few hours and then coming back to the app on a different day. To change the default from the blue, I changed the UITabbar tint color in the AppDelegate.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
UIApplication.SharedApplication.SetStatusBarStyle (UIStatusBarStyle.LightContent, false);
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
//this changes the default iOS tintcolor for the icon when it's activated
UITabBar.Appearance.TintColor = UIColor.FromRGB(223, 112, 13);
return base.FinishedLaunching (app, options);
}

Resources