Xamarin iOS: Unable to start new ViewController by tapping on remote notifications - ios

I am working on iOS, implemented FCM remote notifications. When i am tapping notification(when app open) it is not starting another screen. See below code i am using.
Here i am opening ChildrenViewController when app start by clicking on it
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent;
var cvc = new ChildrenViewController();
cvc.childs = SISConst.Mychildren;
this.navCntl = new UINavigationController(cvc);
this.navCntl.NavigationBarHidden = true;
Window.MakeKeyAndVisible();
SetRootViewController(this.navCntl, false);
}
public void SetRootViewController(UIViewController rootViewController, bool animate)
{
if (animate)
{
var transitionType = UIViewAnimationOptions.TransitionFlipFromRight;
if (Window == null)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
}
Window.RootViewController = rootViewController;
UIView.Transition(Window, 0.5, transitionType,
() => Window.RootViewController = rootViewController,
null);
}
else
{
Window.RootViewController = rootViewController;
}
}
On notifications click i am using separate nested class CustomUNUserNotificationCenterDelegate which is not starting ChildrenViewController with updated data
public class CustomUNUserNotificationCenterDelegate : UNUserNotificationCenterDelegate
{
public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
completionHandler();
var cvc = new ChildrenViewController();
cvc.studentId=studentId;
cvc.moduleName = moduleName;
cvc.isPush = true;
//var nv = new UINavigationController();
var appdelegate=UIApplication.SharedApplication.Delegate as AppDelegate;
//appdelegate.Window.RootViewController = nv;
var nv = appdelegate.Window.RootViewController as UINavigationController;
nv.PushViewController(cvc, false);
}
}
ViewDidLoad() of ChildrenViewController here i am checking whether app start by click or by tapping notification so that i am assigning different data
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.ShowStatusBarWithWhiteBg();
this.DesignScreenTitleAs("");
this.DesignLogoAppTitle();
this.AutomaticallyAdjustsScrollViewInsets = false;
if (isPushNotification)
{
ShowDashboardScreen();
}
else
{
ShowChilds();
}
}

In the delegate method DidReceiveNotificationResponse, take a look at the code,
var nv = new UINavigationController();
nv.PushViewController(cvc, false);
What you're doing here is, you're creating a new Navigation controller and trying to push children view controller to this newly created navigation controller stack. Problem here is, the navigation controller you created here is not set as root view controller of the window.
What you could do is, set this newly create navigation VC as your root view controller if that's what you want to do or get the reference of root view controller of the window which is a navigation controller as I see from your code and try to push the child view controller on it.
Hope this helps.

Related

Navigate to ViewModel From Current View Instead of RootView

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.

MvvmCross Navigating to TabViewController inside custom IosPresenter

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

Sidemenu not closing - Xamarin.iOS

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!

In TabBarViewController, how can I access my children directly?

I want to access a child called SubscriptionsViewController (3rd tab)
This is what I'm doing, but it doesn't work.
var subscriptionsViewController: SubscriptionsViewController? {
get {
let viewControllers = self.childViewControllers
for viewController in viewControllers {
if let vc = viewController as? SubscriptionsViewController {
return vc
}
}
return nil
}
}
Assuming you have an instance of tab bar controller, you can do it as follows:
var subscriptionsViewController: SubscriptionsViewController? {
get {
let viewControllers = tabController.viewControllers //assuming you have a property tabBarController
for viewController in viewControllers {
if viewController is SubscriptionsViewController {
return vc
}
}
return nil
}
}
You can access a child of your tab bar controller with the following :
self.tabBarController.viewControllers[2]

How to refresh UITabBar after changing viewControllers

I'm implementing a user authentication system for an app. If the user is logged in they get one set of available tabs to select. If They're not logged in they get another. Now the issue I'm running into is that after a user logs in (app redirects to safari to some oauth stuff and then returns to the app), I update the tabs from the UITabBarController like so:
private var accessibleViewControllers = [UIViewController]()
func setUpView() {
var viewControllersToSet = [UIViewController]()
if let user = theUser {
for controller in accessibleViewControllers {
if !(controller is LogInViewController) {
viewControllersToSet.append(controller)
}
}
} else {
for controller in accessibleViewControllers {
if controller is LogInViewController || controller is HomeNavigator {
viewControllersToSet.append(controller)
}
}
}
setViewControllers(viewControllersToSet, animated: false)
}
Now the funny thing is, the tab icons don't refresh, but I can still click on the spaces where the new icons would be to link through to the associated view. How do I refresh the tabs so that the right icons appear?
This was a threading issue. I was loading the user data over a background thread and then calling the delegation method setUpView from the same background thread. To fix this I ran it back on the main queue:
dispatch_async(dispatch_get_main_queue()) {
if self.accessibleViewControllers != nil {
var viewControllersToSet = [UIViewController]()
if let user = MediatedUser.shared {
for controller in self.accessibleViewControllers! {
if !(controller is LogInViewController) {
viewControllersToSet.append(controller)
}
}
} else {
for controller in self.accessibleViewControllers! {
if controller is LogInViewController || controller is HomeNavigator {
viewControllersToSet.append(controller)
}
}
}
self.viewControllers = viewControllersToSet
}
}

Resources