Is there a way to navigate to a ViewModel from my current view instead the root view?
I have created a view model that is presented modally from the root view and wish to present a view model within that view.
For example, when calling _navigationService.Navigate<MyViewModel>(); from my modal view, the root view navigates to MyViewModel instead of the current modal view. This leaves my modal screen with a blank page until you dismiss it and see that the navigation has taken place below.
Do I create a custom navigation, or is there a better way of going about this?
EDIT
Here is some code:
My View Model
public class ViewModel1 : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public ViewModel1(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public IMvxCommand NavigateCommand => new MvxCommand(ChangeView);
void ChangeView()
{
_navigationService.Navigate<ViewModel2>();
}
}
My View
[MvxModalPresentation(WrapInNavigationController = true, ModalPresentationStyle = UIModalPresentationStyle.Custom, Animated = false)]
public partial class MyView : MvxViewController
{
ViewModel1 VM;
public MyView() : base("MyView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
NavigationController.NavigationBar.Hidden = true;
var set = this.CreateBindingSet<MyView, ViewModel1 >();
set.Bind(NextButton).To(vm => vm.NavigateCommand);
set.Apply();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
I would like to have the navigation command present ViewModel2 within the presented modal.
For this kind of customization, you may need a write a custom presenter and there you can fiddle around to achieve whatever you can achieve with native.
I am dropping some hint.
In AppDelegate, inside finish launching.
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
var presenter = new DelegatingPresenter(this, Window);
var setup = new Setup(this, presenter);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
---
---
}
DelegatingPresenter looks like:
public class DelegatingPresenter : MvxIosViewPresenter {
private UIWindow currentWindow;
public DelegatingPresenter(AppDelegate appDelegate, UIWindow window)
: base(appDelegate, window) {
currentWindow = window;
--
---
}
protected override void SetWindowRootViewController(UIViewController controller, MvvmCross.iOS.Views.Presenters.Attributes.MvxRootPresentationAttribute attribute = null)
{
base.SetWindowRootViewController(controller, attribute);
}
public override void Show(MvxViewModelRequest request)
{
var viewCreator = Mvx.Resolve<IMvxIosViewCreator>();
var controller = (UIViewController)viewCreator.CreateView(request);
Show((UIViewController)controller);
}
//public override void Show(IMvxIosView view) {
//}
public override void ChangePresentation(MvxPresentationHint hint) {
---
----
}
public override void Close(IMvxViewModel toClose) {
---
----
}
public void Show(UIViewController) {
//if (viewController is IFlyoutNavigationControllerCapable) {
//flyoutPresenter.Show(viewController, flyout);
//}
//else {
//currentWindow.RootViewController = viewController;
//navigationPresenter.Show(viewController, flyout);
//}
//Do your thing here.
}
public bool Close(FlyoutNavigationController flyout) {
---
----
}
}
Here, your point of interest are "show" method override thats gets called when load new ViewModel.
Here you have your rootviewController and new controller that you want to present, so I guess you can take it from here.
Related
Does anybody face this problem? I'm trying to implement MVVM using MVVMCross in Xamarin.iOS project, but in ViewController only constructor was called, neither ViewDidLoad nor ViewWillAppear. What's wrong? Or may be I miss something? Thank you.
public partial class VideosViewController : MvxViewController<VideoListViewModel>
{
public VideosViewController() : base(nameof(VideosViewController), null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
}
}
public class VideoListViewModel : MvxViewModel
{
private readonly IVideoService _videoService;
public CancellationTokenSource TokenSource => new CancellationTokenSource();
public VideoListViewModel(IVideoService videoService)
{
_videoService = videoService;
}
public override async Task Initialize()
{
await base.Initialize();
}
}
I have to controller A and B.
From A I'm navigating to B in the B on button click I have open another ViewController using PresentModalViewController, which ask the user to enter some data. On the click of the submit button on that modally presented view I want to dismiss the modal and show the data on the B view that captured in modally presented view.
Here is what I have done:
I have InviteCollaboratorViewController from this I have modally presented AddNewCollaboratorDialogPopUpView and from there again I have modally presented another view named AddCollaboratorPopUpView.
AddCollaboratorPopUpView user will fill the details of and click on addCollaboratorButton, on this button click event I want to dismiss both the modally presented views, and want to reflects the changes in InviteCollaboratorViewController tableview.
InviteCollaboratorViewController
public partial class InviteCollaboratorViewController : UIViewController
{
public override void ViewDidLoad()
{
btnNewDialog.TouchUpInside += BtnNewDialog_TouchUpInside;
MyCollaboratorsTableView.Source = new MyCollaboratorsTableViewSource(this, _listOfMyCollaborators);
MyCollaboratorsTableView.RowHeight = UITableView.AutomaticDimension;
MyCollaboratorsTableView.ReloadData();
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
MyCollaboratorsTableView.ReloadData();
}
public void BtnNewDialog_TouchUpInside(object sender, EventArgs e)
{
AddNewCollaboratorDialogPopUpView addNewCollaboratorDialogPop = this.Storyboard.InstantiateViewController("AddNewCollaboratorDialogPopUpView") as AddNewCollaboratorDialogPopUpView;
if (addNewCollaboratorDialogPop != null)
{
addNewCollaboratorDialogPop.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
addNewCollaboratorDialogPop.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
this.PresentViewController(addNewCollaboratorDialogPop, true, null);
}
}
}
AddNewCollaboratorDialogPopUpView
public partial class AddNewCollaboratorDialogPopUpView : UIViewController
{
public AddNewCollaboratorDialogPopUpView (IntPtr handle) : base (handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
SET_FONTS();
SET_UI_CONTROLS_STYLING();
createNewCollaboratorButton.TouchUpInside += CreateNewCollaboratorButton_TouchUpInside;
}
public void CreateNewCollaboratorButton_TouchUpInside(object sender, EventArgs e)
{
AddCollaboratorPopUpView addCollaboratorPopUp = this.Storyboard.InstantiateViewController("AddCollaboratorPopUpView") as AddCollaboratorPopUpView;
if (addCollaboratorPopUp != null)
{
addCollaboratorPopUp.ModalPresentationStyle = UIModalPresentationStyle.Popover;
addCollaboratorPopUp.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
this.PresentViewController(addCollaboratorPopUp, true, null);
}
}
}
AddCollaboratorPopUpView
public partial class AddCollaboratorPopUpView : UIViewController
{
public AddCollaboratorPopUpView (IntPtr handle) : base (handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
textFieldFirstName.ShouldEndEditing += TextFieldFirstName_ShouldEndEditing;
textFieldLastName.ShouldEndEditing += TextFieldLastName_ShouldEndEditing;
textFieldPhoneNumber.ShouldEndEditing += TextFieldPhoneNumber_ShouldEndEditing;
textFieldEmail.ShouldEndEditing += TextFieldEmail_ShouldEndEditing;
dismissPopupButton.TouchUpInside += DismissPopupButton_TouchUpInside;
addCollaboratorButton.TouchUpInside += AddCollaboratorButton_TouchUpInside;
}
public void DismissPopupButton_TouchUpInside(object sender, EventArgs e)
{
PresentingViewController?.PresentingViewController?.DismissModalViewController(false);
}
public void AddCollaboratorButton_TouchUpInside(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(textFieldFirstName.Text) && !string.IsNullOrEmpty(textFieldLastName.Text)
&& !string.IsNullOrEmpty(textFieldPhoneNumber.Text) && !string.IsNullOrEmpty(textFieldEmail.Text))
{
//Now from here what I want is to call the InviteCollaboratorViewController's ViewWillAppear() because that Refresh's the list of
//users which reflect the added user on list instantly as popup get dismissed.
collaborator.FirstName = textFieldFirstName.Text;
collaborator.LastName = textFieldLastName.Text;
collaborator.Email = textFieldEmail.Text;
collaborator.PhoneNumber = textFieldPhoneNumber.Text;
GlobalConstants.listOfMyCollaborators.Add(collaborator);
PresentingViewController.PresentingViewController.DismissModalViewController(false);
}
else
{
Toast.MakeToast("Please fill all required fields before submitting.").Show();
}
}
}
Try code below, I have made few modifications. It might help you
InviteCollaboratorViewController.cs
public override void ViewDidLoad()
{
btnNewDialog.TouchUpInside += BtnNewDialog_TouchUpInside;
_listOfMyCollaborators.Add(GlobalConstants.Collaborators);
GlobalConstants.Collaborators=null;
MyCollaboratorsTableView.Source = new MyCollaboratorsTableViewSource(this, _listOfMyCollaborators);
MyCollaboratorsTableView.RowHeight = UITableView.AutomaticDimension;
MyCollaboratorsTableView.ReloadData();
}
AddCollaboratorPopUpView.cs
public void AddCollaboratorButton_TouchUpInside(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(textFieldFirstName.Text) && !string.IsNullOrEmpty(textFieldLastName.Text)
&& !string.IsNullOrEmpty(textFieldPhoneNumber.Text) && !string.IsNullOrEmpty(textFieldEmail.Text))
{
var objCollab=new Collaborators();
objCollab.FirstName = textFieldFirstName.Text;
objCollab.LastName = textFieldLastName.Text;
objCollab.Email = textFieldEmail.Text;
objCollab.PhoneNumber = textFieldPhoneNumber.Text;
GlobalConstants.Collaborators=objCollab;
PresentingViewController.PresentingViewController.DismissModalViewController(false);
}
}
GlobalConstants.cs
Public class GlobalConstants
{
public static Collaborators myCollab {get;set;}
}
I'm having trouble Navigating to a MvxTabBarViewController from my main view, and I think there's something I'm missing with my IosViewPresenter, which utilizes a single root view with a NavigationBar and pages in a ContentView.
public class ContainerPresenter : MvxIosViewPresenter
{
public static MvxNavigationController NavigationController = null;
public static UIView MenuView = null;
private UIView _containerView;
private MvxViewController _rootController;
public ContainerPresenter(IMvxApplicationDelegate applicationDelegate, UIWindow window)
: base(applicationDelegate, window)
{
}
public override void Show(MvxViewModelRequest request)
{
if (NavigationController == null)
{
// First controller called from main hamburger menu. Must have decorator: WrapInNavigationController = true
base.Show(request);
}
else
{
// Subsequent controllers called from main menu or other views
MvxViewController c = _rootController.CreateViewControllerFor(request) as MvxViewController;
string val = "";
if (request.PresentationValues != null)
{
request.PresentationValues.TryGetValue("NavigationMode", out val);
}
if (val.Equals("Push", StringComparison.Ordinal))
{
// Push new controller onto navigation stack
PushViewControllerIntoStack(NavigationController, c, true);
}
else
{
// Replace stack in existing navigation controller with new controller
UIViewController[] controllers = { c };
// SetViewControllers unloads all the navigation bar buttons
// Don't animate to avoid visible refresh when switching root menu items
NavigationController.SetViewControllers(controllers, false);
NavigationController.NavigationBar.TopItem.SetRightBarButtonItems(AlertItem.GetAlertItems(), false);
NavigationController.NavigationBar.TopItem.SetLeftBarButtonItem(HamburgerItem.Button, false);
}
}
}
protected override MvxNavigationController CreateNavigationController(UIViewController viewController)
{
// One NavigationController instance for all views
NavigationController = base.CreateNavigationController(viewController);
NavigationController.NavigationBarHidden = false;
//NavigationController.NavigationBar.TintColor = UIColor.FromRGB(15, 79, 140);
NavigationController.NavigationBar.BarTintColor = UIColor.FromRGB(33, 33, 33);
NavigationController.NavigationBar.Translucent = false;
NavigationController.NavigationBar.TopItem.SetLeftBarButtonItem(HamburgerItem.Button, false);
return NavigationController;
}
protected override void SetWindowRootViewController(UIViewController controller, MvxRootPresentationAttribute attribute = null)
{
if (_window.RootViewController == null)
{
// Insert MainView as root controller, subsequent root menu_item controllers will use MainView.ContainerView as root
base.SetWindowRootViewController(controller, attribute);
_rootController = controller as MvxViewController;
// Hack to get around: 'MainView.ContainerView' is inacessible due to its protection level
// _containerView = (_window.RootViewController as MainView).ContainerView;
_containerView = _window.RootViewController.View.ViewWithTag(1);
MenuView = _window.RootViewController.View.ViewWithTag(2);
}
else
{
// Move root menu_item controller to ContainerView
controller.View.Frame = _containerView.Bounds;
controller.WillMoveToParentViewController(_window.RootViewController);
_containerView.AddSubview(controller.View);
_window.RootViewController.AddChildViewController(controller);
controller.DidMoveToParentViewController(_window.RootViewController);
}
}
}
I'm following the tab example provided in the playground at https://github.com/MvvmCross/MvvmCross/tree/develop/Projects/Playground, my base tab view I'm calling looks like this
[MvxRootPresentation(WrapInNavigationController = true)]
public partial class ItemDetailBaseView : MvxTabBarViewController<ItemDetailBaseViewModel>
{
private bool _isPresentedFirstTime = true;
public ItemDetailBaseView(IntPtr handle) : base(handle)
{
Console.WriteLine("Loaded"); //<----Never called
}
public override void ViewDidLoad()
{
base.ViewDidLoad(); //<----Never called
// Perform any additional setup after loading the view, typically from a nib.
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated); //<----Never called
if (ViewModel != null && _isPresentedFirstTime)
{
_isPresentedFirstTime = false;
ViewModel.ShowInitialViewModelsCommand.ExecuteAsync(null);
}
}
While my tab views look like this
public class ItemDetailTabViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public ItemDetailTabViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public override void ViewAppeared()
{
base.ViewAppeared();
}
public override async Task Initialize()
{
await Task.Delay(3000);
}
}
Nothing fancy so far, I navigate to it with
_navigationService.Navigate<ItemDetailBaseViewModel>(new MvxBundle(new Dictionary<string, string> { { "TabbedPage", "Push" } }));
I get "Navigate requested" but nothing else happens, no view change, not as much as a flicker. As I commented above, nothing in ItemDetailBaseView is called. According to the sample, ViewWillAppear should be called after navigation request, and then the command should populate, but I'm not getting anything. I've even tried catching the request in the Presenter, and making the controller the root view, to no success and I'm out of ideas.
Thank you
In Xamarin! Scrolled is fired, but it doesn't contain X and Y info. I need to know when the view is scrolled to adjust some Views for them to look like they aren't moving. DraggingEnded is fired only when the scroll is over and that's too late. How to receive changes as soon as they are made?
public class ScrollViewDelegate : UIScrollViewDelegate
{
public ScrollViewDelegate()
{
//...
}
public override void DraggingStarted(UIScrollView scrollView)
{
System.Diagnostics.Debug.WriteLine("Dragging started offset: " + scrollView.ContentOffset);
}
}
[assembly: ExportRenderer(typeof(SomeScrollViewControl), typeof(SomeScrollView))]
namespace SomeNamespace
{
public class SomeScrollView : ViewRenderer<SomeScrollViewControl, SomeScrollView>
{
public SomeScrollView()
{
//...
}
protected override void OnElementChanged(ElementChangedEventArgs<SomeScrollViewControl> e)
{
if (e.OldElement == null)
{
//Create someScrollView...
someScrollView.Delegate = new ScrollViewDelegate();
}
}
}
}
In your UIScrollView subclass, override the ContentOffset property and monitor the set and you will get new X/Y of the view as the user scrolls/pans arounds.
public override CoreGraphics.CGPoint ContentOffset
{
get
{
return base.ContentOffset;
}
set
{
Console.WriteLine($"{value.X}:{value.Y}");
base.ContentOffset = value;
}
}
Output:
39.0704398717043:41.4920965388485
35.3280093038005:36.4695722954986
31.736242067681:31.6492455873442
28.1199544514634:26.7960113338755
24.5949600095583:22.0652969703334
21.2827079155381:17.6200932075763
17.9479795925504:13.1447252499626
I'm trying to implement a side menu in Xamarin.iOS by using this component and this example.
Everything worked fine except the part where I want to close the Side Menu.
Now it works like this: I can open the side menu either by using the LeftButton from NavigationItem or the finger swipe. But I can't close it with either of those two methods.
Does anyone have any idea why this is happening? What am I missing?
See also code below.
HomeView
public partial class HomeView : MvxViewController
{
public HomeView() : base("HomeView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
CGRect screenRect = UIScreen.MainScreen.Bounds;
NavigationController.View.Frame = new CGRect(0, 0, screenRect.Width, screenRect.Height);
var app = UIApplication.SharedApplication.Delegate as AppDelegate;
NavigationItem.SetLeftBarButtonItem(
new UIBarButtonItem(UIImage.FromBundle("menu"),
UIBarButtonItemStyle.Plain, (sender, e) =>
{
app.SidebarController.ToggleMenu();
}), true);
}
RootView
public partial class RootView : MvxViewController
{
public RootViewModel RootViewModel
{
get { return (RootViewModel)ViewModel; }
set { ViewModel = value; }
}
public RootView() : base("RootView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
if (ViewModel == null)
return;
var app = UIApplication.SharedApplication.Delegate as AppDelegate;
app.SidebarController = new SidebarController(this,
CreateViewFor(RootViewModel.Home, false), CreateViewFor(RootViewModel.Menu, true));
app.SidebarController.MenuWidth = 220;
app.SidebarController.ReopenOnRotate = false;
app.SidebarController.MenuLocation = MenuLocations.Left;
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
private UIViewController CreateViewFor(IMvxViewModel viewModel, bool navBarHidden)
{
var controller = new UINavigationController();
var screen = this.CreateViewControllerFor(viewModel) as UIViewController;
controller.PushViewController(screen, false);
controller.NavigationBarHidden = navBarHidden;
return controller;
}
This is how I've implemented Sidebar Navigation in my app and its working very well.
RootViewController.cs
public class RootViewController : UIViewController
{
UIStoryboard _storyboard;
// the sidebar controller for the app
public SidebarController LeftSidebarController { get; private set; }
// the navigation controller
public NavigationController NavController { get; private set; }
// the storyboard
public override UIStoryboard Storyboard {
get {
if (_storyboard == null)
_storyboard = UIStoryboard.FromName ("Main", null);
return _storyboard;
}
}
public RootViewController () : base (null, null)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var programController = (CareProgramController)Storyboard.InstantiateViewController ("careProgramController"); // This is the menu..
var leftMenuController = (LeftMenuController)Storyboard.InstantiateViewController ("leftMenuController"); // This is the initial ViewController
NavController = new NavigationController ();
NavController.PushViewController (programController, false);
LeftSidebarController = new SidebarController (this, NavController, leftMenuController) {
HasShadowing = false,
MenuWidth = 280,
MenuLocation = MenuLocations.Left
};
}
}
BaseViewController.cs
public class BaseViewController : UIViewController
{
// provide access to the sidebar controller to all inheriting controllers
protected SidebarNavigation.SidebarController SidebarController {
get {
return (UIApplication.SharedApplication.Delegate as AppDelegate).RootViewController.LeftSidebarController;
}
}
// provide access to the navigation controller to all inheriting controllers
protected NavigationController NavController {
get {
return (UIApplication.SharedApplication.Delegate as AppDelegate).RootViewController.NavController;
}
}
// provide access to the storyboard to all inheriting controllers
public override UIStoryboard Storyboard {
get {
return (UIApplication.SharedApplication.Delegate as AppDelegate).RootViewController.Storyboard;
}
}
public BaseViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
SetupNavigationBar ();
SetDrawerToogle ();
}
void SetupNavigationBar ()
{
if (NavController != null) {
NavController.NavigationBar.BarTintColor = AZConstants.PrimaryColor;
NavController.NavigationBar.Translucent = false;
NavController.NavigationBar.TintColor = UIColor.White;
NavController.NavigationBar.BarStyle = UIBarStyle.Black;
}
}
void SetDrawerToogle ()
{
NavigationItem.SetLeftBarButtonItem (
new UIBarButtonItem (UIImage.FromFile ("ic_menu_white.png").ImageWithRenderingMode (UIImageRenderingMode.AlwaysOriginal)
, UIBarButtonItemStyle.Plain
, (sender, args) => {
SidebarController.ToggleMenu ();
}), true);
}
}
Now all the ViewControllers in my app extends from BaseViewController so it'll have a navigation drawer
AppDelegate.cs
public class AppDelegate : UIApplicationDelegate
{
// class-level declarations
public RootViewController RootViewController { get { return Window.RootViewController as RootViewController; } }
public override UIWindow Window
{
get;
set;
}
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
// Override point for customization after application launch.
// If not required for your application you can safely delete this method
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, true);
GotoRootViewController();
return true;
}
public void GotoRootViewController()
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
//Window.RootViewController.DismissViewController(true, null);
//If you have defined a root view controller, set it here:
Window.RootViewController = new RootViewController();
// make the window visible
Window.MakeKeyAndVisible();
}
I managed to fix the strange behavior by adding the row below in ViewDidLoad() from RootView.cs:
View.Frame = new CGRect(0, 0, 0, 0);
For some reason it looks like the view from RootViewController was above the other two and this is the only way I managed to make it work. If anyone have a better solution, please add your answer and I'll try it. What I've done looks more like a workaround.
#HeisenBerg There is one difference between our projects, I'm using MVVMCross, which changes a bit more the flow between screens. But thank you for your help!