I have a webview that starts dial tel number from url with OverrideUrlLoading and now after deploying the app events are not firing. How can I send an event after LoadUrl from custom WebViewClient?
public class OmaWeb : ContentPage{
...
frame = new Frame
{
Padding = new Thickness(5),
BackgroundColor = Color.White,
Opacity = 1,
CornerRadius = 10,
Content = loading
};
layout = new StackLayout
{
Margin = new Thickness(150, 300, 150, 0),
Children =
{
frame
}
};
//not firing
webView.Navigated += (object sender, WebNavigatedEventArgs args) =>{
layout.IsVisible = false; // should hide the layout of spinner
};
}
WebViewRender.cs
public class CustomWebViewClient : WebViewClient{
Activity mActivity = null;
public CustomWebViewClient(Activity activity){
mActivity = activity;
}
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
if (request.Url.ToString().StartsWith("tel:"))
{
Intent intent = new Intent(Intent.ActionDial, request.Url);
mActivity.StartActivity(intent);
}
else
{
view.LoadUrl(request.Url.ToString());
}
return true;
}
}
I solved the problem. What I did is I added a WebView renderer as parameter of my custom WebViewClient
WebviewRenderer _renderer = null;
public CustomWebViewClient(WebviewRenderer renderer, Activity activity)
{
mActivity = activity;
_renderer = renderer ?? throw new ArgumentNullException("renderer");
}
then I override OnPageFinished method to send navigated event.
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
var source = new UrlWebViewSource { Url = url };
var args = new WebNavigatedEventArgs(WebNavigationEvent.NewPage, source, url, WebNavigationResult.Success);
_renderer.ElementController.SendNavigated(args);
}
This article helped me
enter link here
Related
I have form in xamarin.forms and I want to show a popup message when users click on the nav bar button if there are pending data to save. I found this example but it doesn't not working on Xamarin.Forms 5.0
Any idea of how to do it?
I did a quick test on this you can refer to it.
First, I create a contentpage and set CustomBackButtonAction, EnableBackButtonOverride to add navigate method:
public partial class TestPage5 : ContentPage
{public Action CustomBackButtonAction { get; set; }
public static readonly BindableProperty EnableBackButtonOverrideProperty = BindableProperty.Create(
nameof(EnableBackButtonOverride),
typeof(bool),
typeof(TestPage5),
false
);
public bool EnableBackButtonOverride {
get { return (bool)GetValue(EnableBackButtonOverrideProperty); }
set { SetValue(EnableBackButtonOverrideProperty, value); }
}
public TestPage5()
{
InitializeComponent();
EnableBackButtonOverride = true;
CustomBackButtonAction = async () => { var result = await DisplayAlert("Alert", "Are you Sure?", "Yes", "No");
if (result)
{ await Navigation.PopAsync(true); } };
}
}
Then create renderer on ios while override OnOptionsItemSelected on android:
ios(create a new backbutton and override):
[assembly:ExportRenderer(typeof(TestPage5),typeof(MyRenderer))]
namespace My_Forms_Test3.iOS
{
public class MyRenderer:Xamarin.Forms.Platform.iOS.PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (((TestPage5)Element).EnableBackButtonOverride)
{
SetButton();
}
}
private void SetButton()
{
var backbuttonimg = UIImage.FromBundle("backarrow.png");
backbuttonimg = backbuttonimg.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
var backbutton = new UIButton(UIButtonType.Custom)
{ HorizontalAlignment=UIControlContentHorizontalAlignment.Left,
TitleEdgeInsets=new UIEdgeInsets(11.5f,15f,10f,0f),
ImageEdgeInsets=new UIEdgeInsets(1f,8f,0f,0f)};
backbutton.SetTitle("Back", UIControlState.Normal);
backbutton.SetTitleColor(UIColor.White, UIControlState.Normal);
backbutton.SetTitleColor(UIColor.LightGray, UIControlState.Highlighted);
backbutton.Font = UIFont.FromName("HelveticaNeue", (nfloat)17);
backbutton.SetImage(backbuttonimg, UIControlState.Normal);
backbutton.SizeToFit();
backbutton.TouchDown += (sender, e) =>
{
if (((TestPage5)Element)?.CustomBackButtonAction != null)
{
((TestPage5)Element)?.CustomBackButtonAction.Invoke();
}
};
backbutton.Frame = new CoreGraphics.CGRect(0, 0, UIScreen.MainScreen.Bounds.Width / 4,
NavigationController.NavigationBar.Frame.Height);
var buttoncontainer = new UIView(new CoreGraphics.CGRect(0, 0, backbutton.Frame.Width, backbutton.Frame.Height));
buttoncontainer.AddSubview(backbutton);
var fixspace = new UIBarButtonItem(UIBarButtonSystemItem.FixedSpace)
{ Width = -16f };
var backbuttonitem = new UIBarButtonItem("", UIBarButtonItemStyle.Plain, null) { CustomView = backbutton };
NavigationController.TopViewController.NavigationItem.LeftBarButtonItems = new[] { fixspace, backbuttonitem };
}
}
}
android:
add following on main activity:
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
//important to trigger OnOptionItemSelected
Android.Support.V7.Widget.Toolbar toolbar
= this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
also in mainactivity.cs:
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332) // xam forms nav bar back button id
{
// retrieve the current xamarin
// forms page instance
var currentpage = (TestPage5)Xamarin.Forms.Application.Current.
MainPage.Navigation.NavigationStack.LastOrDefault();
// check if the page has subscribed to the custom back button event
if (currentpage?.CustomBackButtonAction != null)
{
// invoke the Custom back button action
currentpage?.CustomBackButtonAction.Invoke();
// and disable the default back button action
return false;
}
// if its not subscribed then go ahead
// with the default back button action
return base.OnOptionsItemSelected(item);
}
else
{
// since its not the back button
//click, pass the event to the base
return base.OnOptionsItemSelected(item);
}
}
//android Hardware back button event
public override void OnBackPressed()
{
// this is really not necessary, but in Android user has both Nav bar back button
// and physical back button, so its safe to cover the both events
var currentpage = (BaseContentPage)Xamarin.Forms.Application.Current.
MainPage.Navigation.NavigationStack.LastOrDefault();
if (currentpage?.CustomBackButtonAction != null)
{
currentpage?.CustomBackButtonAction.Invoke();
}
else
{
base.OnBackPressed();
}
}
Here is the full blog I have written which handles the same,
Android:
I have used NavigationPage Renderer to achieve this functionality in android
Android Implementtion
iOS:
I have used Page Renderer to achieve this functionality in iOS
public class CustomPageRenderer:PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (Element != null && Element is BasePage basePage && basePage.BindingContext != null &&
basePage.BindingContext is BaseViewModel baseViewModel)
{
SetCustomBackButton(baseViewModel);
}
}
private void SetCustomBackButton(BaseViewModel baseViewModel)
{
UIButton btn = new UIButton();
btn.Frame = new CGRect(0, 0, 50, 40);
btn.BackgroundColor = UIColor.Clear;
btn.TouchDown += (sender, e) =>
{
// Whatever your custom back button click handling
baseViewModel.BackPressedAction?.Invoke(false);
};
//var views = NavigationController?.NavigationBar.Subviews;
NavigationController?.NavigationBar.AddSubview(btn);
}
}
Note:
Do create BackPressedAction Action in your base view model to capture the back press event
So I'm creating an app that uses Xamarin.Forms Webview. I'm trying to detect when the URL changes and if it does then compare the Original URL with the Current URL and then show or hide a button depending. The buttons purpose is to go back to the previous page and keep going until it reaches it's original destination. I only want this "go back" button to show when the user is not on the homescreen. Otherwise, always show.
I've tried everything with if(webview.cangoback...) but that doesn't detect the url change. I've tried setting a string that is equal to the original URL and using .Equals to compare the webview.source (which is where I'm currently at)
I just started looking into webviewNavigating but still nothing.
namespace Webview_Test
{
public partial class MainPage : ContentPage
{
public static string CurrentUrl { get; set; }
public MainPage()
{
InitializeComponent();
string CurrentUrl = "https://www.google.com/";
var _webView = new WebView()
{
Source = "https://www.google.com/",
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
};
Button BackButton = new Button
{
Text = "Go Back",
BackgroundColor = Color.FromHex("990000"),
TextColor = Color.White
};
BackButton.Clicked += OnBackButtonClicked;
void OnBackButtonClicked(object sender, EventArgs e)
{
_webView.GoBack();
}
Grid grid = new Grid
{
VerticalOptions = LayoutOptions.FillAndExpand,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(50, GridUnitType.Absolute) },
new RowDefinition { Height = new GridLength(15, GridUnitType.Absolute) },
new RowDefinition { Height = new GridLength(15, GridUnitType.Absolute) },
new RowDefinition { Height = new GridLength(36, GridUnitType.Absolute) }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Auto },
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(50, GridUnitType.Absolute) },
new ColumnDefinition { Width = new GridLength(50, GridUnitType.Absolute) },
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = GridLength.Auto }
}
};
grid.Children.Add(_webView, 0, 6, 0, 7);
if (_webView.Source.Equals(CurrentUrl))
{
grid.Children.Remove(BackButton);
}
else
{
grid.Children.Add(BackButton, 2, 4, 4, 6);
}
Content = grid;
}
}
}
My expected result is that on the homepage the button that says "go back" doesn't show. But on any page other than the homepage it should show the "go back" button. In logical terms it's if OriginalURL = CurrentURL don't show button. if OriginalURL != CurrentURL show button.
Have you tried Navigating event? i'll put some examples below.
public string OriginalURL = "https://www.stackoverflow.com"
private async void Webview_Navigating(object sender, WebNavigatingEventArgs e)
{
if(e.Url != OriginalURL)
{
//Write code, show the button or use if(webview.CanGoBack){//your code}
}
}
It might be late, but this helps, easy, quick, without any problem.
var url = await webView.EvaluateJavaScriptAsync("window.location.href");
I create custom renderer of webView on each platform to get the current url of webView, it's a little complex but it works finally.
In iOS part, override the LoadingFinished method to get the current url:
[assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
namespace App374.iOS
{
public class MyWebViewRenderer : WebViewRenderer,IUIWebViewDelegate
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{ // perform initial setup
UIWebView myWebView = (UIWebView)this.NativeView;
Delegate = new CustomWebViewDelegate(e.NewElement as WebView);
}
}
}
public class CustomWebViewDelegate : UIWebViewDelegate
{
Xamarin.Forms.WebView formsWebView;
public CustomWebViewDelegate(WebView webView)
{
formsWebView = webView;
}
public override void LoadingFinished(UIWebView webView)
{
var url = webView.Request.Url.AbsoluteUrl.ToString();
MainPage.CurrentUrl = webView.Request.Url.AbsoluteString;
MainPage.checkToShowButton();
}
}
}
In Android part, override the OnPageFinished method to get the current url:
[assembly: ExportRenderer (typeof (MyWebView), typeof (MyWebViewRenderer))]
namespace App374.Droid
{
public class MyWebViewRenderer : WebViewRenderer
{
public MyWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
// lets get a reference to the native control
var webView = (global::Android.Webkit.WebView)Control;
webView.SetWebViewClient(new MyWebViewClient());
webView.SetInitialScale(0);
webView.Settings.JavaScriptEnabled = true;
}
}
}
public class MyWebViewClient : WebViewClient
{
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
MainPage.CurrentUrl = url;
MainPage.checkToShowButton();
}
}
}
And code behiend, check whether to show the go back button after every navigation:
public partial class MainPage : ContentPage
{
public static string CurrentUrl { get; set; }
public static MyWebView _webView;
public static Grid grid;
public static Button BackButton;
public MainPage()
{
InitializeComponent();
string CurrentUrl = "https://www.baidu.com/";
_webView = new MyWebView()
{
Source = CurrentUrl,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
};
BackButton = new Button
{
Text = "Go Back",
BackgroundColor = Color.FromHex("990000"),
TextColor = Color.White
};
grid = new Grid
{
//...
};
grid.Children.Add(_webView, 0, 6, 0, 7);
Content = grid;
checkToShowButton();
//Button click
BackButton.Clicked += OnBackButtonClicked;
void OnBackButtonClicked(object sender, EventArgs e)
{
_webView.GoBack();
checkToShowButton();
if (_webView.CanGoBack == false)
{
grid.Children.Remove(BackButton);
}
}
}
//Check whther to show goBack button
public static void checkToShowButton()
{
if ("https://www.baidu.com/".Equals(MainPage.CurrentUrl) || CurrentUrl == null || CurrentUrl == "")
{
grid.Children.Remove(BackButton);
}
else
{
if (grid != null)
{
grid.Children.Add(BackButton, 2, 4, 4, 6);
}
}
}
}
public class MyWebView : WebView { }
I uploaded my whole sample here and you can check it. Let me know if it works.
Note: I tested the _webView.Navigated and I found it only fires at the first time when loading the webView.
_webView.Navigated += (sender, e) => {
};
Refer : webview-check-when-website-address-changed
Inside my application I have LoginView and after that MainView which is MvxTabBarViewController with two tabs. Here is my code for MainView:
public class MainView : MvxTabBarViewController<MainViewModel>
{
private bool _constructed;
public MainView()
{
_constructed = true;
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override void ViewDidLoad()
{
if (!_constructed)
return;
base.ViewDidLoad();
Title = "SampleTabs";
View.BackgroundColor = UIColor.Red;
var viewControllers = new List<UIViewController>();
viewControllers.Add(CreateTabFor("Second", ViewModel.TabEvents, 0));
viewControllers.Add(CreateTabFor("First", ViewModel.TabDashboard, 1));
ViewControllers = viewControllers.ToArray();
CustomizableViewControllers = new UIViewController[] { };
// SelectedViewController = ViewControllers[1];
}
private UIViewController CreateTabFor(string title, IMvxViewModel viewModel, int index)
{
var controller = new UINavigationController();
var screen = this.CreateViewControllerFor(viewModel) as UIViewController;
screen.Title = title;
// screen.TabBarItem = new UITabBarItem(title, null, index);
screen.TabBarItem = new UITabBarItem(UITabBarSystemItem.Search, index);
controller.PushViewController(screen, false);
controller.NavigationBarHidden = true;
return controller;
}
}
Problem is with tab items, I can't change it to second after initial tab is showed. Tabs are simple with only background color change. Any help is welcome.
Fixed! Problem was with core core not with tab view.
I'd like to have a UIPickerView in a Xamarin.iOS project. The UIPicker that I need, must be like this (hide by default and with toolbar and done button):
which is an example for Xamarin.forms !
I've seen already all questions on stack overflow and they are not in my case or they are not complete explanation for this purpose.
For demonstrating that I've tried already for create Done Toolbar, here is my code :
public class TestPickerViewController : UIViewController
{
PickerModel picker_model;
UIPickerView picker;
public TestPickerViewController()
{
Title = Texts.Home;
View.BackgroundColor = UIColor.White;
this.EdgesForExtendedLayout = UIRectEdge.None;
}
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.
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
List<Object> state_list = new List<Object>();
state_list.Add("1");
state_list.Add("2");
state_list.Add("3");
state_list.Add("4");
picker_model = new PickerModel(state_list);
picker = new UIPickerView();
picker.Model = picker_model;
picker.ShowSelectionIndicator = true;
UIToolbar toolbar = new UIToolbar();
toolbar.BarStyle = UIBarStyle.Black;
toolbar.Translucent = true;
toolbar.SizeToFit();
UIBarButtonItem doneButton = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done, (s, e) =>
{
foreach (UIView view in this.View.Subviews)
{
if (view.IsFirstResponder)
{
UITextField textview = (UITextField)view;
textview.Text = picker_model.values[(int)picker.SelectedRowInComponent(0)].ToString();
textview.ResignFirstResponder();
}
}
});
toolbar.SetItems(new UIBarButtonItem[] { doneButton }, true);
View.AddSubviews(picker);
//How to add toolbar, action for opening toolbar and hide by default the list
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.AddConstraints(
picker.AtTopOf(View, 90),
picker.AtLeftOf(View, 50),
picker.WithSameWidth(View).Minus(100)
);
}
private void SetPicker(object sender, EventArgs e)
{
UITextField field = (UITextField)sender;
picker.Select(picker_model.values.IndexOf(field.Text), 0, true);
}
}
public class PickerModel : UIPickerViewModel
{
public IList<Object> values;
public event EventHandler<PickerChangedEventArgs> PickerChanged;
public PickerModel(IList<Object> values)
{
this.values = values;
}
public override nint GetComponentCount(UIPickerView picker)
{
return 1;
}
public override nint GetRowsInComponent(UIPickerView picker, nint component)
{
return values.Count;
}
public override string GetTitle(UIPickerView picker, nint row, nint component)
{
return values[(int)row].ToString();
}
public override nfloat GetRowHeight(UIPickerView picker, nint component)
{
return 40f;
}
public override void Selected(UIPickerView picker, nint row, nint component)
{
if (this.PickerChanged != null)
{
this.PickerChanged(this, new PickerChangedEventArgs { SelectedValue = values[(int)row] });
}
}
}
public class PickerChangedEventArgs : EventArgs
{
public object SelectedValue { get; set; }
}
I know that I have to add toolbar to somewhere which has done button. And I need also the action which hide the default Picker and show the list when we click on Select section and etc ...
Just assign the UIToolbar to the InputAccessoryView property of UITextField. Here's a code snippet for example:
UIToolbar toolBar = new UIToolbar(new CGRect(0, 0, 320, 44));
UIBarButtonItem flexibleSpaceLeft = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace,null,null);
UIBarButtonItem doneButton = new UIBarButtonItem("OK",UIBarButtonItemStyle.Done,this, new ObjCRuntime.Selector("DoneAction"));
UIBarButtonItem[] list = new UIBarButtonItem[] { flexibleSpaceLeft, doneButton };
toolBar.SetItems(list, false);
UIPickerView pickerView = new UIPickerView(new CGRect(0, 44, 320, 216));
pickerView.DataSource = new MyUIPickerViewDataSource();
pickerView.Delegate = new MyUIPickerViewDelegate();
pickerView.ShowSelectionIndicator = true;
//Assign the toolBar to InputAccessoryView
textField.InputAccessoryView = toolBar;
textField.InputView = pickerView;
And implement the Action like this:
[Export("DoneAction")]
private void DoneAction()
{
Console.WriteLine("Your Action!");
}
It works like this:
I used sidebar navigation xamarin component in my app, but navigationbar is showing on the sidemenu, it does not slide with the navigation controller.
xamarin component: https://components.xamarin.com/view/sidebarnavigation
Here is my code.
public AgentDetails agent { get; set; }
public SidebarController SidebarController { get; private set; }
public RootController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
public override void LoadView()
{
base.LoadView();
UIImageView img = new UIImageView(new CGRect(0, 0, 120, 50));
img.Image = UIImage.FromBundle("Logo.png");
img.ContentMode = UIViewContentMode.ScaleAspectFit;
this.NavigationItem.LeftBarButtonItem = new UIBarButtonItem(img);
//this.NavigationItem.Title = true;
//menuOpen.SetBackgroundImage(UIImage.FromBundle("Menu.png"), UIControlState.Normal);
UIBarButtonItem back = new UIBarButtonItem();
back.SetBackButtonBackgroundImage(UIImage.FromBundle("Back.png"), UIControlState.Normal, UIBarMetrics.Compact);
this.NavigationItem.BackBarButtonItem = back;
this.NavigationController.NavigationBar.BackgroundColor = UIColor.FromRGB(255, 0, 0);
var storyboard = UIStoryboard.FromName("Main", null);
MainTabController tab = storyboard.InstantiateViewController("TabPage") as MainTabController;
SideMenuController side = storyboard.InstantiateViewController("SideMenu") as SideMenuController;
side.rootController = this;
tab.agent = agent;
SidebarController = new SidebarController(this, tab, side);
this.NavigationController.NavigationBar.Translucent = false;
menuOpen.TouchUpInside += MenuOpen_TouchUpInside;
LoadSchedule();
}
void LoadSchedule()
{
AgentAPI agentData = new AgentAPI();
agentData.EmpId = agent.PortalId;
agentData.ScheduleHours((schedule) =>
{
JObject jsondata = JsonConvert.DeserializeObject<JObject>(schedule);
if (jsondata["status"].ToString() == "200")
{
agent.Schedule = JsonConvert.DeserializeObject<List<Schedule>>(jsondata["activity"].ToString());
}
});
}
private void MenuOpen_TouchUpInside(object sender, EventArgs e)
{
SidebarController.ToggleMenu();
}
image of my sidemenu
Don't embed the RootViewController in the NavigationController.
Embed the ContentController instead.
For example , the code snippet in RootViewController.cs like this:
SidebarController = new SidebarController(this, new UINavigationController(new ContentController()), new SideMenuController());