i am trying to create a transaction between two view controllers on xamarin.ios (i am not using the navigation controllers but the viewcontrollers), i would like that when the swipe gesture is in progress it appears under the view controller that moves another side viewcontroller (the target one) I'm trying to emulate the classic "go back" gesture of ios present both on the system itself and on whatsapp. I had already posted and I had partially solved, managing the scrolling animation, but for the second view controller nothing to do :(
my code...
private void InteractiveTransitionRecognizerActionWithoutIndex(UIScreenEdgePanGestureRecognizer sender, UIView View, string x)
{
//Contiene un valore numerico che varia in base allo stato della gesture
var percento = sender.TranslationInView(View).X * 100 / sender.View.Bounds.Size.Width;
var storyboard = UIStoryboard.FromName("Main", null);
// var viewController = storyboard.InstantiateViewController(x);
//Quando la gesture rileva una variazione
if (sender.State == UIGestureRecognizerState.Changed)
{
var minTransform = CGAffineTransform.MakeTranslation(sender.TranslationInView(View).X, 0); //*2 = piu rapido
var maxTransform = CGAffineTransform.MakeTranslation(sender.TranslationInView(View).X, 0);
View.Transform = true ? minTransform : maxTransform;
UIView.Animate(0.1, 0, UIViewAnimationOptions.CurveEaseInOut,
() =>
{
View.Transform = true ? maxTransform : minTransform;
//???
},
null
);
If you are using storyboards and familiar with segues. To define a custom transition animation you will have to configure your segue as follows:
Then in the first view controller you can override the PrepareForSegue method:
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
var destinationVC = segue.DestinationViewController as SecondViewController;
destinationVC.Callee = this;
destinationVC.TransitioningDelegate = new CustomTransitioningDelegate(sender as UIView);
destinationVC.ModalPresentationStyle = UIModalPresentationStyle.Custom;
}
public class CustomTransitioningDelegate: UIViewControllerTransitioningDelegate
{
readonly UIView _animationOrigin;
public CustomTransitioningDelegate(UIView animationOrigin)
{
_animationOrigin = animationOrigin;
}
public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
{
var customTransition = new GrowTransitionAnimator(_animationOrigin);
return customTransition;
}
}
The animations are defined in a class that inherit from UIViewControllerAnimatedTransitioning.
public class GrowTransitionAnimator : UIViewControllerAnimatedTransitioning
{
readonly UIView _animationOrigin;
public GrowTransitionAnimator(UIView animationOrigin)
{
_animationOrigin = animationOrigin;
}
public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
// The animation here
}
public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
{
return 0.3;
}
}
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
I am new to MvvmCross and still figuring out how things are done. I am using BEMCheckBox for one of my developing applications and I tried to bind BEMCheckBox's "On" property to ViewModel. Unfortunately, it is not working as expected.
BEMCheckBox
Github : https://github.com/saturdaymp/XPlugins.iOS.BEMCheckBox
NuGet : SaturdayMP.XPlugins.iOS.BEMCheckBox
Version: 1.4.3
MvvmCross
Version : 6.4.2
This is my View Class
[MvxFromStoryboard("Main")]
[MvxRootPresentation(WrapInNavigationController = false)]
public partial class MyView : BaseView<MyViewModel>
{
private BEMCheckBox CheckBox;
public MyView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//add check box
CheckBox = new BEMCheckBox(new CoreGraphics.CGRect(0, 0, 20, 20))
{
BoxType = BEMBoxType.Square,
TintColor = UIColor.FromRGBA(0, 0, 0, 0.14f),
OnFillColor = UIColor.FromRGB(42, 183, 202),
OnCheckColor = UIColor.White,
OnTintColor = UIColor.FromRGBA(0, 0, 0, 0.14f),
OnAnimationType = BEMAnimationType.Bounce,
OffAnimationType = BEMAnimationType.Bounce,
On = false,
CornerRadius = 0,
};
//CheckBoxContainerView is an UIView
CheckBoxContainerView.AddSubview(CheckBox);
CheckBoxContainerView.BackgroundColor = UIColor.Clear;
//Binding to View Model
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(CheckBox).For(c => c.On).To(vm => vm.IsCheckBoxOn);
set.Apply();
}
}
This is my ViewModel Class
public class MyViewModel : BaseViewModel
{
private bool _isCheckBoxOn;
public bool IsCheckBoxOn
{
get => _isCheckBoxOn;
set
{
SetProperty(ref _isCheckBoxOn, value);
Console.WriteLine(_isCheckBoxOn);
}
}
public MyViewModel : base()
{
}
public override Task Initialize()
{
return base.Initialize();
}
}
When I do the same thing for UISwitch it works perfectly. Any help would be highly appreciated.
MvvmCross includes many target bindings out of the box, UISwitch being one of them. For a complete list see "built-in-bindings" in MvvmCross documentation.
One-way binding (ViewModel -> View)
By default, you can create a binding to assign a value to a property on your view from your view model, this is called one-way binding. The approach works even with custom 3rd party controls, as long as the types match.
Two-way binding (ViewModel -> View && View -> ViewModel)
In order to do a two-way bind, MvvmCross needs a mechanise for the view to notify the view model that a value of the view has changed. For this MvvmCross using target bindings. See MvvCross documentation for creating custom target bindings.
Example
See MvvmCross source code for MvxUISwitchOnTargetBinding.cs which shows how they do the binding for the UISwitch control and the On state.
public class MvxUISwitchOnTargetBinding : MvxTargetBinding<UISwitch, bool>
{
private IDisposable _subscription;
public MvxUISwitchOnTargetBinding(UISwitch target)
: base(target)
{
}
protected override void SetValue(bool value)
{
Target.SetState(value, true);
}
public override void SubscribeToEvents()
{
var uiSwitch = Target;
if (uiSwitch == null)
{
MvxBindingLog.Error( "Error - Switch is null in MvxUISwitchOnTargetBinding");
return;
}
_subscription = uiSwitch.WeakSubscribe(nameof(uiSwitch.ValueChanged), HandleValueChanged);
}
public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (!isDisposing) return;
_subscription?.Dispose();
_subscription = null;
}
private void HandleValueChanged(object sender, EventArgs e)
{
FireValueChanged(Target.On);
}
}
Note the HandleValueChanged method which calls the FireValueChanged() method, this is the method that passes the value that you want to send back up to your bound view model.
You will then need to register your custom target bindings in your Setup.cs class by overriding the FillTargetFactories.
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 have created a custom NSView that i would like to place over the top of the content of a window to block any interaction while all the content is loading. The problem i was having is that i could click through the NSView to the controls below though that has now been fixed. The new problem is that even though i cannot click on the controls, when i move the mouse over text controls, the mouse switches to the I Beam icon.
How do i make the NSView completely block all interaction with everything below it?
The NSView i created is below:
[Register("StupidView")]
public class StupidView : NSView
{
public StupidView()
{
// Init
Initialize();
}
public StupidView(IntPtr handle) : base (handle)
{
// Init
Initialize();
}
[Export("initWithFrame:")]
public StupidView(CGRect frameRect) : base(frameRect) {
// Init
Initialize();
}
private void Initialize()
{
this.AcceptsTouchEvents = true;
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}
public override void DrawRect(CGRect dirtyRect)
{
var ctx = NSGraphicsContext.CurrentContext.GraphicsPort;
ctx.SetFillColor(new CGColor(128, 128, 128, 0.7f));
ctx.FillRect(dirtyRect);
}
public override void MouseDown(NSEvent theEvent)
{
if (Hidden)
{
base.MouseDown(theEvent);
}
}
public override void MouseDragged(NSEvent theEvent)
{
if (Hidden)
{
base.MouseDragged(theEvent);
}
}
public override bool AcceptsFirstResponder()
{
return !this.Hidden;
}
public override bool AcceptsFirstMouse(NSEvent theEvent)
{
return !this.Hidden;
}
public override NSView HitTest(CGPoint aPoint)
{
return Hidden ? null : this;
}
}
I had the same problem a few weeks ago, and here is how I could manage this :
First, to prevent user interactions on the superview placed below, I added a transparent button which was there only to catch the mouse click and, if you don't have to do anything, do nothing :
private void Initialize()
{
this.AcceptsTouchEvents = true;
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
//Add button to prevent user interactions
NSButton buttonToPreventUserInteraction = new NSButton();
buttonToPreventUserInteraction.Bordered = false;
buttonToPreventUserInteraction.Transparent = true;
buttonToPreventUserInteraction.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(buttonToPreventUserInteraction);
//If you want to add some constraints on the button, for it to resize and keep the same size of your subview
var dicoViews = new NSMutableDictionary();
dicoViews.Add((NSString)"buttonToPreventUserInteraction", buttonToPreventUserInteraction);
NSLayoutConstraint[] buttonToPreventUserInteractionHorizontalConstraints = NSLayoutConstraint.FromVisualFormat("H:|[buttonToPreventUserInteraction]|", NSLayoutFormatOptions.DirectionLeadingToTrailing, null, dicoViews);
NSLayoutConstraint[] buttonToPreventUserInteractionVerticalConstraints = NSLayoutConstraint.FromVisualFormat("V:|[buttonToPreventUserInteraction]|", NSLayoutFormatOptions.DirectionLeadingToTrailing, null, dicoViews);
AddConstraints(buttonToPreventUserInteractionHorizontalConstraints);
AddConstraints(buttonToPreventUserInteractionVerticalConstraints);
}
For your other problem, which is the mouse cursor changing from the content in your superview placed below, you can add a NSTrackingArea on your subview, and implement the override method "MouseMoved" to change the cursor. You can do something like this :
First Add the NSTrackingArea on your subview (you can put this code in your "Initialize" method)
NSTrackingAreaOptions opts = ((NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.InVisibleRect));
var trackingArea = new NSTrackingArea(new CGRect(0, 0, FittingSize.Width, FittingSize.Height), opts, Self, null);
AddTrackingArea(trackingArea);
And then implement the override method :
public override void MouseMoved(NSEvent theEvent)
{
//You can choose the type of cursor you want to use here
NSCursor.ArrowCursor.Set();
}
This made it for me, hope it will for you too
I'm still learning the ropes in Xamarin ios and have implemented a side drawer based on the following example Monotouch.SlideoutNavigation. In this tutorial,there's a main view controller class which then assigns a main navigation controller and a side menu.
The drawer menu options are fed into the menu class while the "home screen/first screen" is passed onto the main navigation controller class which is a subclass of a UINavigationController class.
My home screen is a tabcontroller class and I'm trying to make a reference to the navigation controller inside this class but it always returns null.
These are the two challenges I'm facing:
The navigation controller inside the tab controller and single tab view controllers is always null
The titles of my individual tab controller classes are not shown on the navigation bar.
Here's the AppDelegate class :
[Register ("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public SlideoutNavigationController Menu { get; private set; }
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
Menu = new SlideoutNavigationController ();
var tabBarController = GetViewController (Main, "MainTabBarController");
Menu.MainViewController = new MainNavigationController (tabBarController, Menu);
Menu.MenuViewController = new MenuNavigationController (new MenuControllerLeft (), Menu) { NavigationBarHidden = true };
SetRootViewController (Menu, false);
return true;
}
}
The MainTabController class
public partial class MainTabBarController : UITabBarController
{
UINavigationItem titleRequest,titleHome,titleSell;
public MainTabBarController (IntPtr handle) : base (handle)
{
//Create an instance of our AppDelegate
appDelegate = UIApplication.SharedApplication.Delegate as AppDelegate;
//Get an instance of our Main.Storyboard
var mainStoryboard = appDelegate.Main;
var tab1 = appDelegate.GetViewController (mainStoryboard, "Tab1");
var tab2 = appDelegate.GetViewController (mainStoryboard, "Tab2");
var tab3 = appDelegate.GetViewController (mainStoryboard, "Tab3");
var tabs = new UIViewController[] {
tab1, tab2, tab3
};
this.SelectedIndex = 1;
ViewControllers = tabs;
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
if(this.SelectedIndex == 0)
{
titleRequest = new UINavigationItem ("TAB 1");
this.NavigationController.NavigationBar.PushNavigationItem (titleRequest, true); // NavigationController here is null
}else if(this.SelectedIndex == 1)
{
titleHome = new UINavigationItem ("TAB 2");
this.NavigationController.NavigationBar.PushNavigationItem (titleHome, true);
}else{
titleSell = new UINavigationItem ("TAB 3");
this.NavigationController.NavigationBar.PushNavigationItem (titleSell, true);
}
}
}
The MainNavigation controller class
public class MainNavigationController : UINavigationController
{
public MainNavigationController(UIViewController rootViewController, SlideoutNavigationController slideoutNavigationController)
: this(rootViewController, slideoutNavigationController,
new UIBarButtonItem(UIImage.FromBundle("icon_sidemenu.png"), UIBarButtonItemStyle.Plain, (s, e) => {}))
{
}
public MainNavigationController(UIViewController rootViewController, SlideoutNavigationController slideoutNavigationController, UIBarButtonItem openMenuButton)
: base(rootViewController)
{
openMenuButton.Clicked += (s, e) => slideoutNavigationController.Open(true);
rootViewController.NavigationItem.LeftBarButtonItem = openMenuButton;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.Delegate = new NavigationControllerDelegate();
InteractivePopGestureRecognizer.Enabled = true;
}
public override void PushViewController(UIViewController viewController, bool animated)
{
// To avoid corruption of the navigation stack during animations disabled the pop gesture
if (InteractivePopGestureRecognizer != null)
InteractivePopGestureRecognizer.Enabled = false;
base.PushViewController(viewController, animated);
}
private class NavigationControllerDelegate : UINavigationControllerDelegate
{
public override void DidShowViewController(UINavigationController navigationController, UIViewController viewController, bool animated)
{
// Enable the gesture after the view has been shown
navigationController.InteractivePopGestureRecognizer.Enabled = true;
}
}
}
Edit - Results after making changes suggested by Jason below
Could someone help me see what I'm doing wrong.
do this in AppDelegate:
tabs = new UITabBarController();
tabs.ViewControllers = new UIViewController[]{
new UINavigationController(new UIViewController() { Title = "Tab A" }),
new UINavigationController(new UIViewController() { Title = "Tab B" }),
new UINavigationController(new UIViewController() { Title = "Tab C" })
};
Menu = new SlideoutNavigationController();
Menu.MainViewController = new MainNavigationController(tabs, Menu);
Menu.MenuViewController = new MenuNavigationController(new DummyControllerLeft(), Menu) { NavigationBarHidden = true };
I finally found a work around this. For anyone using Dillan's solution and has a TabBarController class as one of the Menu classes, here's how I got it to work.
I wrapped the TabBarController class in a NavigationController,apart from the MainNavigationController class. I didn't have to wrap each tab in it's own NavigationController after this.That solves the null reference to the NavigationController inside the TabBarController class
To solve the titles being obscured inside each tab, I found a simple solution:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
try{
this.ViewControllerSelected += (object sender, UITabBarSelectionEventArgs e) => {
switch(TabBar.SelectedItem.Title)
{
case"TAB 1" :
Title = "TAB 1";
break;
case "TAB 2":
Title = "TAB 2";
break;
default:
Title = "TAB 3";
break;
}
};
}catch(Exception e)
{
Console.WriteLine (e.Message);
}
}