Xamarin forms block user back key press - xamarin

In my Xamarin forms application I want to show a confirmation message when user clicks the back button from Main-page. Is there any way to achieve this?
I overrided the OnBackButtonPressed method in my MainPage. But still the app is closing while back key press. Here is my code
protected override bool OnBackButtonPressed ()
{
//return base.OnBackButtonPressed ();
return false;
}

You can override OnBackButtonPressed for any Xamarin.Form Page. But it only will work for the physical button in Android and Windows Phone devices.
protected override bool OnBackButtonPressed () {
DisplayAlert("title","message","ok");
return true;
}
For the virtual one, you will need to create CustomRenderers and to intercept the click handler. In iOS it can be tricky because the user can go back doing other actions (e.g. the swipe gesture). Once you intercept it you just need to create your Confirmation Message (which I assume that you know how to do it).
For iOS you can do something like this:
[assembly: ExportRenderer (typeof (YourPage), typeof (YourPageRenderer))]
namespace YourNamespace {
public class YourPageRenderer : PageRenderer {
public override void ViewWillAppear (bool animated) {
base.ViewWillAppear (animated);
Action goBack = () => page.DisplayAlert("title","message","ok");
var backButton = new NavBackButton (goBack);
navigationItem.LeftBarButtonItem = new UIBarButtonItem (backButton);
}
}
public class NavBackButton : UIView {
public NavBackButton (Action onButtonPressed) {
SetButton (onButtonPressed);
}
UILabel text;
UIImageView arrow;
void SetButton(Action onButtonPressed){
arrow = new UIImageView(new CGRect(-25,0, 50, 50)) {
Image = new UIImage("Images/back").ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
};
arrow.TintColor = Colors.DarkGreen.ToUIColor ();
text = new UILabel(new CGRect(arrow.Frame.Width + arrow.Frame.X -15, arrow.Frame.Height /2 - 10, 40, 20)) { Text = "Back" };
text.TextColor = Colors.DarkGreen.ToUIColor ();
Frame = new CGRect(0,0,text.Frame.Size.Width + arrow.Frame.Width, arrow.Frame.Height);
AddSubviews (new UIView[] { arrow, text });
var tapGesture = new UITapGestureRecognizer (onButtonPressed);
AddGestureRecognizer (tapGesture);
}
public override void TouchesBegan (Foundation.NSSet touches, UIEvent evt) {
base.TouchesBegan (touches, evt);
text.TextColor = UIColor.YourColor;
arrow.TintColor = UIColor.YourColor;
}
public override void TouchesEnded (Foundation.NSSet touches, UIEvent evt){
base.TouchesEnded (touches, evt);
arrow.TintColor = UIColor.YourColor;
text.TextColor = UIColor.YourColor;
}
}
}
PS You will need to provide an arrow image ("Images/back")

Related

Xamarin Forms iOS custom renderer for tabbed page

I have tabbed page in Xamarin Forms (iOS side). I need custom renderer for tabbed page - make first tab not scrollable (it could be shown as button or label), rest of tabs should be scrollable.
I think creators of Xamarin Forms tabbed page implemented tabs like a horizontal listview. I just want to put a button as first element on the left and then put that listview with tabs. When button is clicked, the new view is being opened. How to do that?
I am using Naxam Library to provide top tabbed page - this is extension to tabbed page (at iOS it is at bottom). I have tried to use custom renderer, but no breakpoint is hitted. I don's know why.
using CoreAnimation;
using CoreGraphics;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CustomTabbedPage), typeof(CustomTabbedPageRenderer))]
namespace Layout.xxx.iOS.CustomControlRenderers
{
public class CustomTabbedPageRenderer : Naxam.Controls.Platform.iOS.TopTabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
DependencyService.Get<IAlertHandler>().ShowCustomAlertVoid("", "OnElementChanged");
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
AddButtonToTabbedPage();
DependencyService.Get<IAlertHandler>().ShowCustomAlertVoid("", "ViewDidLoad");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
DependencyService.Get<IAlertHandler>().ShowCustomAlertVoid("", "Dispose");
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
DependencyService.Get<IAlertHandler>().ShowCustomAlertVoid("", "ViewDidAppear");
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
DependencyService.Get<IAlertHandler>().ShowCustomAlertVoid("", "ViewDidDisappear");
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
DependencyService.Get<IAlertHandler>().ShowCustomAlertVoid("", "ViewDidLayoutSubviews");
}
public override void DidMoveToParentViewController(UIViewController parent)
{
base.DidMoveToParentViewController(parent);
DependencyService.Get<IAlertHandler>().ShowCustomAlertVoid("", "DidMoveToParentViewController");
}
private void AddButtonToTabbedPage()
{
var btn = new UIButton();
CAGradientLayer btnGradient = new CAGradientLayer();
btnGradient.Frame = btn.Bounds;
btnGradient.Colors = new CGColor[] { Color.Black.ToCGColor(), Color.White.ToCGColor() };
btnGradient.Locations = new NSNumber[] { 0.0f, 0.1f };
btn.Layer.AddSublayer(btnGradient);
btn.Layer.MasksToBounds = true;
btn.Layer.BorderColor = Color.Blue.ToCGColor();
btn.Layer.BorderWidth = 2;
btn.Layer.SetNeedsDisplay();
}
}
}
If you want to fix the first tab, you can use custom renderer to achieve it. Change the index<0 to index<1 to fix the first tab in the method GetPreviousViewController and GetNextViewController:
[assembly: ExportRenderer(typeof(myTopTabbedPage), typeof(myTopTabbedRenderer))]
namespace App12.iOS
{
class myTopTabbedRenderer : TopTabbedRenderer, IUIPageViewControllerDataSource
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
public new UIViewController GetPreviousViewController(UIPageViewController pageViewController, UIViewController referenceViewController)
{
var index = ViewControllers.IndexOf(referenceViewController) - 1;
//in the source, it is if (index < 0) return null;
//change here to if (index < 1) will fix the first tab
if (index < 1) return null;
return ViewControllers[index];
}
public new UIViewController GetNextViewController(UIPageViewController pageViewController, UIViewController referenceViewController)
{
var index = ViewControllers.IndexOf(referenceViewController) + 1;
//in the source, it is if (index == ViewControllers.Count) return null;
//change here to if (index == ViewControllers.Count || index == 1) will fix the first tab
if (index == ViewControllers.Count || index == 1) return null;
return ViewControllers[index];
}
}
}
And in your xamarin.forms project, use myTopTabbedPage to create tabs:
public class myTopTabbedPage : TopTabbedPage {
}
var tabs = new myTopTabbedPage
{
Title = "TopTabs",
BarBackgroundColor = Color.FromHex("#9C27B0"),
SwipeEnabled = true,
BarIndicatorColor = Color.Green,
BarTextColor = Color.White
};
Try and let me know if it works for you.

Pop the UIViewController in xamarin ios

I want to open a UIViewController from a content page and this code helps me in doing this
UIWindow Window = new UIWindow(UIScreen.MainScreen.Bounds);
var cvc = new ScanningPage();
var navController = new UINavigationController(cvc);
Window.RootViewController = navController;
Window.MakeKeyAndVisible();
but I also want a Back button on that Controller Page which navigate me back to that content Page.
this.NavigationController.PopViewController(true);
or
this.DismissViewController(true,null);
not working in this case.
I would recommend not creating a new Window and a UINavigationController...
Xamarin.Forms is contained in a single VC in the first Window, so you can obtain that view controller via:
UIApplication.SharedApplication.Windows[0].RootViewController;
So as an example Dependency service that presents and dismisses a VC (either from Forms or the view controller), you can do something like this.
Dependency interface:
public interface IDynamicVC
{
void Show();
void Dismiss();
}
iOS Dependency implementation
public class DynamicVC : IDynamicVC
{
UIViewController vc;
public void Show()
{
if (vc != null) throw new Exception("DynamicVC already showing");
vc = new UIViewController();
var button = new UIButton(new CGRect(100, 100, 200, 200));
button.SetTitle("Back", UIControlState.Normal);
button.BackgroundColor = UIColor.Red;
button.TouchUpInside += (object sender, EventArgs e) =>
{
Dismiss();
};
vc.Add(button);
var rootVC = UIApplication.SharedApplication.Windows[0].RootViewController;
rootVC.PresentViewController(vc, true, () => { });
}
public void Dismiss()
{
vc?.DismissViewController(true, () =>
{
vc.Dispose();
vc = null;
});
}
}

Need event when UIWebView scrolls and option to cancel the scroll

We are building an app using xamarin.forms, which has a certain page that displays a webview. We use custom renderers for the webview to enable some features that are not implemented in the xamarin.forms webview. In the IOS version of the app, this webview is an UIWebView control. What I need, is a way to catch touch start, move and end events on the UIWebView control and be able to calculate the distance that the touch has moved. Furthermore, I need to be able to cancel the touchmove event from firing in the UIWebView, so the content doesn't scroll until I want it to.
I've already accomplished this in the android version by using the Touch eventhandler of the android webview. It can do exactly what i want, and the touch event has a "handled" property that can be used to avoid the event from triggering in the webview.
I've found all kinds ways to catch scroll events in IOS. The most promising seems to be implementing a gestureRecognizer to the UIWebView's scrollview. This way I can catch touch start, move and end events. However, I have a few problems with this implementation:
During touchmove, the events seem to stop firing. The touchend doesn't fire at all most of the time.
I cannot find a way to cancel the event when I want it to be canceled.
The way i currently attach the gesture recognizer:
var test = new MyRecognizer();
webView.ScrollView.AddGestureRecognizer(test);
The gesture recognizer:
public class MyRecognizer : UIGestureRecognizer
{
private nfloat _startY;
public MyRecognizer()
{
this.CancelsTouchesInView = true;
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
var location = (touches.AnyObject as UITouch).LocationInView(View);
_startY = location.Y;
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
var location = (touches.AnyObject as UITouch).LocationInView(View);
var intCurrentY = location.Y;
if(someCondition)
{
//cancel the event somehow to prevent the webview from scrolling
//a post mentioned this should do the trick, but it doesnt work:
this.Enabled = false;
this.Enabled = true;
}
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
}
}
Am I on the right track here? Why do the events stop firing? And how can I cancel the event to prevent the webview from scrolling in SOME cases (I don't want to ALWAYS disable scrolling on webview)
I figured it out myself. At least part of it. The events stopped firing because during the move, the default UIPanGestureRecognizer of the UIWebView's ScrollView captured the gesture. This is the code of my own recognizer that does the trick in capturing the events, and doesn't stop after a while:
public class TopBottomBarScrollRecognizer : UIPanGestureRecognizer
{
private nfloat _startY;
private nfloat _startX;
private HybridWebView _webView;
private UIWebView _nativeWebView;
public TopBottomBarScrollRecognizer()
{
this.DelaysTouchesBegan = false;
this.DelaysTouchesEnded = false;
this.CancelsTouchesInView = false;
//make sure the recognizer can work together with other recognizers
this.ShouldRecognizeSimultaneously = (a, b) => true;
}
public TopBottomBarScrollRecognizer(HybridWebView webView, UIWebView nativeWebView) : this()
{
_webView = webView;
_nativeWebView = nativeWebView;
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
var location = (touches.AnyObject as UITouch).LocationInView(_nativeWebView);
_startY = location.Y;
_startX = location.X;
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
var location = (touches.AnyObject as UITouch).LocationInView(_nativeWebView);
var deltaY = (float)(double)(location.Y - _startY);
var deltaX = (float)(double)(location.X - _startX);
_webView.scrollOccurred(deltaX, deltaY);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
_webView.scrollEndOccurred();
}
}
The above solves part 1 of the question. The question on how to prevent the default recognizer to temporarily stop is not that important anymore, since the behavior as it is now seems to work fine.

How to handle UITextField position while typing in Xamarin iOS?

Its very common problem in iOS mobile development and that is while you are done with your UI and It contains too many UITextFields, If you try to input value in UITextFields those are added center bottom of the screen; these fields hides behind the keyboard. How can we get rid of this general problem?
You could use the AddObserver method in NSNotificationCenter for when keyboard is visible and hidden.
sample code(FYI: Got the code below from another post sometime last year, I can't remember link to the post but it works fine.)
Call the AddObserver in your viewdidload method
// Keyboard popup NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.DidShowNotification, KeyBoardUpNotification);
// Keyboard Down NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, KeyBoardDownNotification);
you can add the methods below in your base controller base if you have one
public void KeyBoardUpNotification(NSNotification notification) {
CGRect keyboardSize = UIKeyboard.BoundsFromNotification(notification);
// Find what opened the keyboard
foreach (UIView view in this.View.Subviews) {
if (view.IsFirstResponder)
activeview = view;
}
bottom = (activeview.Frame.Y + activeview.Frame.Height + offset);
scrollamount = (keyboardSize.Height - (View.Frame.Size.Height - bottom));
if (scrollamount > 0) {
moveViewUp = true;
MoveView(moveViewUp);
} else {
moveViewUp = false;
}
}
public void KeyBoardDownNotification(NSNotification notification) {
if (moveViewUp) {
MoveView(false);
}
}
private void MoveView(bool move) {
UIView.BeginAnimations(string.Empty, IntPtr.Zero);
UIView.SetAnimationDuration(0.3);
CGRect frame = View.Frame;
if (move) {
frame.Y -= scrollamount;
} else {
frame.Y += scrollamount;
scrollamount = 0;
}
View.Frame = frame;
UIView.CommitAnimations();
}
I have used a nuget package to get rid of this problem. I have overrides two methods and initialized code inside these methods.
Download KeyboardHandler and use as following:
using KeyboardHandler;
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.yourScrollView.SubscribeKeyboardManaqger();
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.yourScrollView.UnsubscribeKeyboardManaqger();
}

How can I add a Dialog View Controller as a Subview to a UIView or Vice Versa?

I have looked around the web for some time looking for any resources on this topic and have come up with nothing that solves my dilemma.
I have a dialog view controller and its root is simply displaying a list of strings similar to how the iphone music song scrollable view is laid out. What I need is a subview located at the top of the screen and the scrollable DVC below it. I need to the top view to be always in view while the user can scroll through the root element because the top view will be holding statistics.
I have tried adding a subview but it simply overlaps the dvc below it, and I have not been able to figure out a way to add a dvc as a subview to a UIView.
Any help would be much appreciated.
What is needed to achieve this is a single root view controller that hosts two subview controllers. One subview contains the statistics at the top of the window. The bottom subview contains a navigation controller that holds the dialog view.
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using MonoTouch.Dialog;
using System.Drawing;
namespace delete201205203
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
MyUIViewController _mvc;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
_mvc = new MyUIViewController ();
window.RootViewController = _mvc;
window.MakeKeyAndVisible ();
return true;
}
}
public class MyUIViewController : UIViewController
{
MyDialogViewController _dvc;
UINavigationController _nav;
StatisticsViewController _statistics;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var root = new RootElement ("Root") {
new Section ("Section") {
new EntryElement ("caption", "placeholder", ""),
new RootElement ("Root 2") {
new Section ("Section") {
new EntryElement ("caption", "placeholder", ""),
new StringElement ("Back", () => {
_nav.PopViewControllerAnimated (true);
})
}
}
}
};
_dvc = new MyDialogViewController (root);
_nav = new UINavigationController (_dvc);
_nav.SetNavigationBarHidden (true, false);
_nav.View.Frame = new RectangleF (0, 70f,
this.View.Bounds.Width,
this.View.Bounds.Height -70f);
_statistics = new StatisticsViewController ();
_statistics.View.Frame = new RectangleF (0, 0,
this.View.Bounds.Width,
70f);
this.AddChildViewController (_nav);
this.View.AddSubview (_nav.View);
this.AddChildViewController (_statistics);
this.View.AddSubview (_statistics.View);
}
public override void ViewWillLayoutSubviews ()
{
base.ViewWillLayoutSubviews ();
_nav.View.Frame = new RectangleF (0, 70f,
this.View.Bounds.Width,
this.View.Bounds.Height -70f);
_statistics.View.Frame = new RectangleF (0, 0,
this.View.Bounds.Width,
70f);
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
public class StatisticsViewController : UIViewController
{
UILabel _label;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
this.View.BackgroundColor = UIColor.White;
_label = new UILabel (new RectangleF (this.View.Bounds.Width * .5f - 50f,
this.View.Bounds.Height * .5f -10f,
100f, 20f));
_label.AutoresizingMask = UIViewAutoresizing.FlexibleMargins;
_label.Text = "statistics";
this.View.AddSubview (_label);
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
// This overrde is needed to ensure the pop view animation
// works correctly in landscape mode
public class MyDialogViewController : DialogViewController
{
public MyDialogViewController (RootElement root) : base (root) {}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
}

Resources