Xamarin: Open Dialer with Tel: Links in WKWebView - c#

In a WKWebView, clicking on the Tel: links (example: ) does not open the phone dialer with the number from the link as they do in Chrome/Safari.
I have looked at the solution from the link below:
https://forums.xamarin.com/discussion/103689/after-ios-11-upgrade-wkwebview-does-not-load-my-website
However, in my C# project, I am unable to use two base classes (UIViewController, WKNavigationDelegate) in my class as my class WebViewController cannot have multiple base classes.
Is it possible to do this in the DidFinishNavigation method to open the dialer when Tel: links are clicked?
My full code is below with changes that mimics the idea from the link above. Would it be possible for me to achieve this with the way my web view is designed?
[Register("WebViewController")]
public class WebViewController : UIViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
WKWebView webView = new WKWebView(View.Frame, new
WKWebViewConfiguration());
View.AddSubview(webView);
View.SendSubviewToBack(webView);
webView.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
var url = new NSUrl("link goes here");
var request = new NSUrlRequest(url);
webView.LoadRequest(request);
webView.AllowsBackForwardNavigationGestures = true;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
NavigationController.NavigationBarHidden = true;
}
//open email and tel links
// https://forums.xamarin.com/discussion/103689/after-ios-11-upgrade-wkwebview-does-not-load-my-website
//https://forums.xamarin.com/discussion/47335/how-to-call-a-set-phone-number-from-a-button-click-using-xamarin-ios
[Export("webView:didFinishNavigation:")]
//[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
void DidFinishNavigation(WKWebView webView, WKNavigation navigation, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
{
var navType = navigationAction.NavigationType;
var targetFrame = navigationAction.TargetFrame;
var url = navigationAction.Request.Url;
if (
(url.ToString().StartsWith("http") && targetFrame == null)
||
url.ToString().StartsWith("mailto:")
|| url.ToString().StartsWith("tel:")
|| url.ToString().StartsWith("Tel:"))
{
UIApplication.SharedApplication.OpenUrl(url);
}
}
}
}

Fixed it by adding a custom navigation delegate class:
public override void ViewDidLoad()
{
base.ViewDidLoad();
WKWebView webView = new WKWebView(View.Frame, new WKWebViewConfiguration());
View.AddSubview(webView);
View.SendSubviewToBack(webView);
webView.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
var url = new NSUrl("link");
var request = new NSUrlRequest(url);
webView.LoadRequest(request);
webView.AllowsBackForwardNavigationGestures = true;
//assign delegate
webView.NavigationDelegate = new MyWKNavigationDelegate();
}
//custom delegate
class MyWKNavigationDelegate : WKNavigationDelegate
{
[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction
navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
{
var navType = navigationAction.NavigationType;
var targetFrame = navigationAction.TargetFrame;
var url = navigationAction.Request.Url;
if (
url.ToString().StartsWith("http") && (targetFrame != null &&
targetFrame.MainFrame == true)
)
{
decisionHandler(WKNavigationActionPolicy.Allow);
}
else if (
//(url.ToString().StartsWith("http") && targetFrame == null)
//||
url.ToString().StartsWith("mailto:")
|| url.ToString().StartsWith("tel:")
|| url.ToString().StartsWith("Tel:"))
{
//decisionHandler(WKNavigationActionPolicy.Allow);
UIApplication.SharedApplication.OpenUrl(url);
}
}
}

Related

WindowsAppSDK doesnt have ProtocolActivatedEventArgs

I am trying to handle protocol activation and as per docs I should handle all of that within OnLaunched method so that is what I am trying to do here, but Microsoft.Windows.AppLifecycle.ProtocolActivatedEventArgs doesnt exist.
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var activatedArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
var e = args.UWPLaunchActivatedEventArgs;
InitializeRootFrame(e);
if (activatedArgs.Kind is ExtendedActivationKind.Launch)
{
if (!e.PrelaunchActivated)
{
if (RootFrame.Content == null)
{
RootFrame.Navigate(typeof(LoginPage), e.Arguments);
}
Window.Current.Activate();
}
}
else //Launched by some other means other than normal launching
{
try
{
if (activatedArgs.Kind is ExtendedActivationKind.Protocol && activatedArgs is Microsoft.Windows.AppLifecycle.ProtocolActivatedEventArgs eventArgs)
{
//var a = activatedArgs.Data as ProtocolActivatedEventArgs;
var queryParameters = HttpUtility.ParseQueryString(activatedArgs.Data.Uri.Query);
PocessQueryForToken(queryParameters);
}
}
catch (Exception)
{
}
finally
{
RootFrame.Navigate(typeof(LoginPage));
Window.Current.Activate();
HasLaunched = true;
}
}
HasLaunched = true;
}
There is only a AppActivationArguments Class in the Microsoft.Windows.AppLifecycle NameSpace. So the behavior you got is expected because you are looking for a class that doesn't even exist.
Based on the document for AppActivationArguments, we could know that the activatedArgs we got contains a data object which has one of the following data types, depending on the activation type specified by the Kind property.
File ->IFileActivatedEventArgs
Protocol ->IProtocolActivatedEventArgs
StartupTask ->IStartupTaskActivatedEventArgs
The IProtocolActivatedEventArgs should be the thing that we are looking for. The document here-ProtocolActivatedEventArgs Class shows that this Class comes from the Windows.ApplicationModel.Activation Namespace.
So the code should looks like this:
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var eventargs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
if (eventargs.Kind is ExtendedActivationKind.Protocol && eventargs.Data is Windows.ApplicationModel.Activation.ProtocolActivatedEventArgs)
{
ProtocolActivatedEventArgs ProtocolArgs = eventargs.Data as ProtocolActivatedEventArgs;
var uri = ProtocolArgs.Uri;
}
}

MvvmCross - How to Add Custom Modal Transition

I am working on a project using MvvmCross and Xamarin. I am trying to add a custom transition when presenting and dismissing my modal view. Currently I am presenting it this way:
[MvxModalPresentation(WrapInNavigationController = true, ModalPresentationStyle = UIModalPresentationStyle.Custom)]
public partial class MyView : MvxViewController
{
and dismissing it this way:
NavigationController.DismissModalViewController(true);
So I have my animation ready, but I am assuming that I need to set the transition delegate to the one I've created. How do I do this?
I am fairly new to MvvmCross, so any tips and tricks are very much appreciated. Thanks!
I Am not sure, what you actually want to achieve here.
If you are looking for syntax help. This should be something like this.
this.NavigationController.TransitioningDelegate = new MyOwnDelegate();
internal class MyOwnDelegate : IUIViewControllerTransitioningDelegate
{
public IntPtr Handle => throw new NotImplementedException();
public void Dispose()
{
//throw new NotImplementedException();
}
}
But normally people use, this one. I am also giving some syntax if that helps
this.NavigationController.Delegate = new NavigationControllerDelegate();
public class NavigationControllerDelegate : UINavigationControllerDelegate
{
public NavigationControllerDelegate(IntPtr handle) : base(handle)
{
}
public NavigationControllerDelegate()
{
}
public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForOperation(UINavigationController navigationController, UINavigationControllerOperation operation, UIViewController fromViewController, UIViewController toViewController)
{
var fromVcConformA = fromViewController as ICustomTransition;
var fromVCConFromB = fromViewController as IWaterFallViewControllerProtocol;
var fromVCCConformc = fromViewController as IHorizontalPageViewControllerProtocol;
var toVcConformA = toViewController as ICustomTransition;
var toVCConfromB = toViewController as IWaterFallViewControllerProtocol;
var toVCCConformc = toViewController as IHorizontalPageViewControllerProtocol;
if ((fromVcConformA != null) && (toVcConformA != null) && ((fromVCConFromB != null && toVCCConformc != null) || (fromVCCConformc != null && toVCConfromB != null)))
{
var transition = new CustomTransition();
transition.presenting = operation == UINavigationControllerOperation.Pop;
return transition;
}
else
{
return null;
}
}
}

Xamarin.Forms: NavigationController is null in PageRenderer

I am trying to use PageRenderer to customize/reposition elements of ToolbarItem for iOS but here NavigationController throwing null reference exception.
Below my code
public class MyNavigationRenderer: PageRenderer
{
public new MyNavigationBar Element
{
get { return (MyNavigationBar)base.Element; }
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
var LeftNavList = new List<UIBarButtonItem>();
var rightNavList = new List<UIBarButtonItem>();
var navigationItem = this.NavigationController.TopViewController.NavigationItem;
for (var i = 0; i < Element.ToolbarItems.Count; i++)
{
var reorder = (Element.ToolbarItems.Count - 1);
var ItemPriority = Element.ToolbarItems[reorder - i].Priority;
if (ItemPriority == 1)
{
UIBarButtonItem LeftNavItems = navigationItem.RightBarButtonItems[i];
LeftNavList.Add(LeftNavItems);
}
else if (ItemPriority == 0)
{
UIBarButtonItem RightNavItems = navigationItem.RightBarButtonItems[i];
rightNavList.Add(RightNavItems);
}
}
navigationItem.SetLeftBarButtonItems(LeftNavList.ToArray(), false);
navigationItem.SetRightBarButtonItems(rightNavList.ToArray(), false);
}
}
Below MyNavigationBar.cs class in portable/shared forms project
public class MyNavigationBar : NavigationPage
{
public MyNavigationBar(Page content) : base(content)
{
Init();
}
private void Init()
{
this.ToolbarItems.Add(new ToolbarItem() { Icon = "kid", Priority = 0, Order = ToolbarItemOrder.Primary });
this.ToolbarItems.Add(new ToolbarItem() { Text = "License", Priority = 0, Order = ToolbarItemOrder.Primary });
}
}
App starting
public App ()
{
InitializeComponent();
MainPage = new MyNavigationBar(new LoginPage());
}
See below screenshot getting exception
I faced this issue, but in my case, I was trying to get NavigationController from content page which didn't had NavigationController, make sure you null check before calling TopViewController,
var navController = this.NavigationController;
if(navController == null)
{
return;
}
UINavigationItem navigationItem = navController.TopViewController.NavigationItem;
For example,
When User opens the app, he will be presented with Login page, which didn't had any Navigation Bar.

Check EF before changing view or URL MVC4

I need check my EF or values of certains data before changing view or url inside the app
I have a view where process import information to other wiew under controller, i need to check values before user changing to other view or want try other process inside the view (import information)
i will try under my button inside the view but is the user want change to other view the process not work...
The process under the button is this:
Controller:
public ActionResult Index(int? page, string filter, int id = 0)
{
ViewBag.OrderPurchaseID = id;
var check_import = db.OrderPurchaseDetails.Where(o => o.OrderPurchaseID == id && o.QtyTraslate > 0).ToList();
if (check_import.Count() > 0)
{
TempData["ErrorMessage"] = "You have articles pending to import, check or delete them";
return RedirectToAction("Edit", "OrderPurchase", new { id = id });
}
#region remember filter stuff
if (filter == "clear")
{
Session["Text"] = null;
Session["From"] = null;
Session["To"] = null;
}
else
{
if ((Session["Text"] != null) || (Session["From"] != null) || (Session["To"] != null))
{
return RedirectToAction("Search", new { text = Session["Text"], from = Session["From"], to = Session["To"] });
}
}
#endregion
var orderpurchases = db.OrderPurchases.Include(o => o.Provider);
int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
return View(orderpurchases.OrderByDescending(p => p.TimeStamp).ToPagedList(currentPageIndex, defaultPageSize));
}
View:
#Html.ActionLink("List", "Index", new { id = Model.OrderPurchaseID }, new { #class = "btn" })
I need replicate this method to global level, if is possible of course...
Thanks for your help.
i use LogAttribute to check data in my EF from import data, thanks to Yuliam and Lee Winter for the help and bring me a solution global level.
public class LogAttribute : ActionFilterAttribute
{
private dbcAmerica db = new dbcAmerica();
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
int data = Convert.ToInt32(filterContext.Controller.TempData["id"]);
var checkIn = db.OrderPurchaseDetails.Where(o => o.QtyTraslate > 0 && o.OrderPurchaseID == data).ToList();
if (checkIn.Count() > 0)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "Edit" },
{ "controller", "OrderPurchase" },
{ "id", data},
});
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
// ... log stuff after execution
}
}

Redirect UIViewController for Xamarin

I am creating a method to display a dialog, this is what I made so far:
public void ShowDialog(String title = "",String msg = "")
{
this.alert.Title = title;
this.alert.AddButton("Ok");
this.alert.Message = msg;
this.alert.Show();
}
this is located to a common class so it can be used with other classes,
I want to add a parameter so that after pressing the "Ok" button it will redirect to a certain UIViewController, I tried it like this:
public void ShowDialog(String title = "",String msg = "", UIViewController view = null)
{
this.alert.Title = title;
this.alert.AddButton("Ok");
this.alert.Message = msg;
this.alert.Clicked += (object sender1, UIButtonEventArgs e) => {
if(e.ButtonIndex.ToString () == "0" && view != null){
NavigationController.PushViewController(view, true);
}
};
this.alert.Show();
}
but clearly this is not working, am I missing something? or is this possible to do?
Thanks in advance..
HOW I CALL IT
Common common = new Common();
common.ShowDialog("Error","Invalid Process!!", new HomeScreen());
I ended up doing this:
public void ShowDialog(String title = "",String msg = "", UIViewController instance = null, UIViewController redirectTo = null)
{
this.alert.Title = title;
this.alert.AddButton("Ok");
this.alert.Message = msg;
this.alert.Clicked += (object sender1, UIButtonEventArgs e) => {
if(e.ButtonIndex.ToString () == "0" && instance != null && redirectTo != null){
instance.NavigationController.PushViewController(redirectTo, true);
}
};
this.alert.Show();
}
then Call it like this:
Common common = new Common();
common.ShowDialog("Error","Invalid Process!", this, new HomeScreen());

Categories