Difficulty with TabBarControllers intermixed with NavigationControllers - ios

I have a Xamarin.iOS app where I have the following structure:
NavController (root: Login)
--> TabBarController (Home) - (Search) - (Profile)
------> NavController (root: Home)
------------->TableController
----------------->DetailController
------>NavController (root: Search)
...etc
I am currently having difficulties working with the navigation items, specifically the back button item.
I never want to go back to the login page via the back button, so in my HomeController, I hide the back button by saying
TabBarController.NavigationItem.HidesBackButton = true;
When I go to the next screen (TableController) I want to see the back button that goes back to the HomeController, however my current approach has a back button to the Login Controller
this.TabBarController.NavigationItem.HidesBackButton = false;
Thanks for any help

For the effect you want, I suggest you create an object(I use "RootController" as an example) to manage the app's Window.RootController.
I don't know if you have some experience for change window's RootController, so just follow my step:
At first create a new project, remove the storyboard and Viewcontroller.cs.(Don't forget remove the bundle for storyboard in the info.plist)
Then rewrite your AppDelegate.cs, like this:
[Register ("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
// create a new window instance based on the screen size
window = new UIWindow (UIScreen.MainScreen.Bounds);
window.RootViewController = RootController.Instance.LoginController;
window.MakeKeyAndVisible ();
return true;
}
public override void OnResignActivation (UIApplication application)
{
// Invoked when the application is about to move from active to inactive state.
// This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message)
// or when the user quits the application and it begins the transition to the background state.
// Games should use this method to pause the game.
}
public override void DidEnterBackground (UIApplication application)
{
// Use this method to release shared resources, save user data, invalidate timers and store the application state.
// If your application supports background exection this method is called instead of WillTerminate when the user quits.
}
public override void WillEnterForeground (UIApplication application)
{
// Called as part of the transiton from background to active state.
// Here you can undo many of the changes made on entering the background.
}
public override void OnActivated (UIApplication application)
{
// Restart any tasks that were paused (or not yet started) while the application was inactive.
// If the application was previously in the background, optionally refresh the user interface.
}
public override void WillTerminate (UIApplication application)
{
// Called when the application is about to terminate. Save data, if needed. See also DidEnterBackground.
}
}
And this is RootController.cs which include a UINavigationController and a UITabBarController(Home - Search - Profile):
public class RootController
{
private static RootController instance;
public static RootController Instance {
get {
if (instance == null)
instance = new RootController ();
return instance;
}
}
private static UINavigationController loginController;
public UINavigationController LoginController {
get {
if (loginController == null)
InitLoginController ();
return loginController;
}
}
private void InitLoginController()
{
UIViewController loginViewController = new UIViewController (){ Title = "LoginController" };
loginViewController.View.Frame = UIScreen.MainScreen.Bounds;
loginViewController.View.BackgroundColor = UIColor.Red;
loginViewController.NavigationItem.SetRightBarButtonItem (new UIBarButtonItem ("MainTab",UIBarButtonItemStyle.Done, delegate {
UIApplication.SharedApplication.KeyWindow.RootViewController = RootController.Instance.MainTabController;
}),true);
loginController = new UINavigationController (loginViewController);
}
private static UITabBarController mainTabController;
public UITabBarController MainTabController {
get {
if (mainTabController == null)
InitMainTabController ();
return mainTabController;
}
set {
mainTabController = value;
}
}
private void InitMainTabController ()
{
mainTabController = new UITabBarController ();
mainTabController.ViewControllers = new UIViewController [] {
new UINavigationController(new HomeViewController() {
TabBarItem = new UITabBarItem (UITabBarSystemItem.Favorites,0)
}),
new UINavigationController (new UIViewController ()
{
Title = "SearchController",
TabBarItem = new UITabBarItem (UITabBarSystemItem.Search,1)
}),
new UINavigationController (new UIViewController ()
{
Title = "ProfileController",
TabBarItem = new UITabBarItem (UITabBarSystemItem.More,2)
})
};
mainTabController.SelectedIndex = 0;
}
}
And this is HomeController.cs which can push an UITableViewController as you wish and also has a button to return to LoginController if you needed:
public class HomeViewController : UIViewController
{
public HomeViewController ()
{
Title = "HomeController";
this.View.Frame = UIScreen.MainScreen.Bounds;
this.View.BackgroundColor = UIColor.Green;
//Set a button to return to loginController
this.NavigationItem.SetLeftBarButtonItem (new UIBarButtonItem ("LoginC",UIBarButtonItemStyle.Done, delegate {
UIApplication.SharedApplication.KeyWindow.RootViewController = RootController.Instance.LoginController;
}),true);
//Set a button to go to tableController
UITableViewController tableViewController = new UITableViewController (){ Title = "TableViewController" };
tableViewController.View.Frame = UIScreen.MainScreen.Bounds;
tableViewController.View.BackgroundColor = UIColor.White;
tableViewController.HidesBottomBarWhenPushed = true;//To make tabbar disappear
this.NavigationItem.SetRightBarButtonItem (new UIBarButtonItem ("TableView",UIBarButtonItemStyle.Done, delegate {
this.NavigationController.PushViewController(tableViewController,true);
}),true);
}
}
You can add your own ViewController in RootController.cs instead of my sample controllers.
If you still have some problems, just leave it here, I'll check it latter.
Hope it can help you.

Related

Identify the page when app moves to background or foreground in IOS

I want to identify the page user is on when the app moves to background.
Reason : When the app enters foreground, I need to log it as a page view in telemetry. So, it is important for me to understand which page the user was on when app moved to background.
When the app moves to background, it will trigger OnResignActivation and DidEnterBackground method, what you should do is that find the top viewcontroller in DidEnterBackground in AppDelegate.
String pageName;
public override void DidEnterBackground(UIApplication application)
{
UIViewController controller = UIApplication.SharedApplication.KeyWindow.RootViewController;
while (controller.PresentedViewController != null)
{
controller = controller.PresentedViewController;
}
UIViewController c = findTopViewController(controller);
pageName = c.Class.Name;
}
public UIViewController findTopViewController(UIViewController controller)
{
if (controller is UINavigationController) {
controller = (controller as UINavigationController).TopViewController;
findTopViewController(controller);
}
else if (controller is UITabBarController)
{
controller = (controller as UITabBarController).SelectedViewController;
findTopViewController(controller);
}
return controller;
}
Refer to this answer and comments under it.

Xamarin.IOS go back from CNContactViewController.FromContact to Application

I have an application written with Xamarin for IOS and Android where I have a list of contacts and when I click on one of them I want to open the according contact in the addressbook of the phone. I have an interface for that implement the method on IOS that way:
private void ShowContactDialog(string addressbookId, bool isEdit = false)
{
var contact = new AddressBookService().GetCnContactById(addressbookId);
var view = CNContactViewController.FromContact(contact);
view.Editing = isEdit;
DisplayView(view);
}
private static void DisplayView(CNContactViewController view)
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
if (vc is UINavigationController navController)
{
vc = navController.ViewControllers.Last();
}
vc.PresentViewController(new UINavigationController(view), true, null);
}
That works so far as it opens the contact:
The issue now is, that there is just an edit button, but now Done, cancel or back button. Therefore I have to kill the whole application and start it again to come back.
Is there a way to add a extra button similar to the edit button to dismiss the dialog again?
EDIT: I adjusted the code as suggested in the comments by Kevin Li.
private void ShowContactDialog(string addressbookId, bool isEdit = false)
{
var contact = new AddressBookService().GetCnContactById(addressbookId);
var view = CNContactViewController.FromContact(contact);
view.Editing = isEdit;
GetCurrentViewController().NavigationController.PushViewController(view, true);
}
private static UIViewController GetCurrentViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
if (vc is UINavigationController navController)
{
vc = navController.ViewControllers.Last();
}
return vc;
}
Now it is shown within the Tabs:
Unfortunately is the back button not displayed as well.
I tried to make a new UINavigationController, but that didn't change anything.
EDIT2:
I adjusted the way the tabs are created another time. My MainViewController looks like that:
[MvxRootPresentation(WrapInNavigationController = true)]
public class MainViewController : MvxTabBarViewController<MainViewModel>
{
private bool constructed;
private bool firstTimePresented = true;
public MainViewController()
{
constructed = true;
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override async void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (firstTimePresented)
{
// Initialize the tabs
firstTimePresented = false;
await ViewModel.GoToContactListCommand.ExecuteAsync();
await ViewModel.GoToBirthdayListCommand.ExecuteAsync();
await ViewModel.GoToProfileListCommand.ExecuteAsync();
await ViewModel.GoToSettingsCommand.ExecuteAsync();
}
}
public override void ViewDidLoad()
{
if (!constructed)
return;
base.ViewDidLoad();
Title = "Connect Update";
EdgesForExtendedLayout = UIRectEdge.None;
View.BackgroundColor = UIColor.White;
}
}
On each View who shall be a tab has to be this Attribute:
[MvxTabPresentation(TabIconName = "IconCake", TabName = "Birthdays")]
public partial class BirthdayView : MvxViewController<BirthdayViewModel>
The code for opening the contact is the same as above. Now it makes a new View without the tabs but with the back button, exactly as wished :)
The issue now is, that there is just an edit button, but now Done, cancel or back button.
It is because the value of isEdit at view.Editing = isEdit; is false. So, when you present the CNContactViewController, there's only an Edit button. When you click the Edit button, it will change to the editing mode and the Cancel and Done buttons will appear.
Or you can set the value of isEdit to true, then the Cancel and Done buttons will show when the CNContactViewController presents.
Therefore I have to kill the whole application and start it again to come back.
Is there a way to add a extra button similar to the edit button to dismiss the dialog again?
You don't have to kill and restart the app to dismiss the dialog. You can just implement the DidComplete in the delegate: CNContactViewControllerDelegate to dismiss the dialog when user finishes editing the contact and click the Done button or cancel it with clicking the Cancel button.
Here's the related code snippet:
var view = CNContactViewController.FromContact(contact);
view.Delegate = new MyCNConatactViewControllerDelegate();
public class MyCNConatactViewControllerDelegate : CNContactViewControllerDelegate
{
public override void DidComplete(CNContactViewController viewController, Contacts.CNContact contact)
{
viewController.NavigationController.DismissViewController(true, null);
}
}
Update:
To show the Back button in the CNContactViewController:
Make sure the UI hierarchy like this: Navigation->Viewcontroller(Page)->CNContactViewController. Then change the method from Present to Push to show the CNContactViewController, like this:
this.NavigationController.PushViewController(view, true);

Mixing a CocosSharp game with UIViewControllers

I'm creating a CocosSharp game with several additional native screens which I'm going to implement using native iOS UIViewController. The flow is the following:
On app startup I'm creating UINavigationViewController and initial view with my custom main menu:
public override void FinishedLaunching(UIApplication app)
{
MainWindow = new UIWindow(UIScreen.MainScreen.Bounds);
var rootViewStoryboard = UIStoryboard.FromName("GameMenu", null);
var rootView = rootViewStoryboard.InstantiateViewController("GameMenu");
var navigationController = new UINavigationController(rootView);
MainWindow.RootViewController = navigationController;
MainWindow.MakeKeyAndVisible();
}
public partial class GameMenu : UIViewController
{
public GameMenu(IntPtr handle)
: base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
btnStartGame.TouchUpInside += (sender, e) => NavigationController.PresentViewController(new GameScene(), false);
}
}
One of the actions leads to another UIViewController (modal) where CocosSharp game is created:
public partial class GameScene : UIViewController
{
public GameScene()
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
_application = new CCApplication();
_application.ApplicationDelegate = new GameAppDelegate();
_application.StartGame();
_application.MainWindow.DisplayStats = true;
OnStopGame();
}
}
Finally on certain event (in my case in 5 sec) I'm trying to finish a game and return back to the main menu:
public async Task OnStopGame()
{
await Task.Delay(5000);
_application.ExitGame();
NavigationController.DismissViewController(false, null);
}
As a result I get empty black screen, background music continue to run, no main menu is displayed.
How should I properly navigate from CocosSharp game to a native controller and back? Can they live in parallel?

StoryBoard.InstantiateViewController giving "Unknown class testController in Interface Builder file"

I am working on a Xamarin IOS/monotouch project.
I have been stuck with this error since some time. These are the lines of code
var prfc = this.Storyboard.InstantiateViewController ("testController") as testController;
if (prfc != null) {
this.PresentViewController(prfc,false,null);}
I tried renaming viewcontroller, cleaning and building solution, deleting viewcontroller and adding a new one with another name
I have ensured that all spellings are correct, i have same Viewcontroller name, StoryBoardID, and Restoration ID. This is how I have registered my viewcontroller in Designer partial class
[Register ("testController")]
partial class testController
Here is my appdelegate.cs code `[Register ("AppDelegate")] public partial class AppDelegate : UIApplicationDelegate { // class-level declarations UIWindow window; public static UIStoryboard Storyboard = UIStoryboard.FromName ("MainStoryboard", null); public static UIViewController initialViewController; public override UIWindow Window { get; set; }
`[Register ("AppDelegate")] public partial class AppDelegate : UIApplicationDelegate { // class-level declarations UIWindow window; public static UIStoryboard Storyboard = UIStoryboard.FromName ("MainStoryboard", null); public static UIViewController initialViewController; public override UIWindow Window { get; set; }
// This method is invoked when the application is about to move from active to inactive state.
// OpenGL applications should use this method to pause.
public override void OnResignActivation (UIApplication application)
{
}
// This method should be used to release shared resources and it should store the application state.
// If your application supports background exection this method is called instead of WillTerminate
// when the user quits.
public override void DidEnterBackground (UIApplication application)
{
}
// This method is called as part of the transiton from background to active state.
public override void WillEnterForeground (UIApplication application)
{
}
// This method is called when the application is about to terminate. Save data, if needed.
public override void WillTerminate (UIApplication application)
{
}
public override void FinishedLaunching (UIApplication application)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
initialViewController = Storyboard.InstantiateInitialViewController () as UIViewController;
window.RootViewController = initialViewController;
window.MakeKeyAndVisible ();
return ;
}
}`
I have tried out every possibility but can't fix this. This error is occuring for every new viewcontroller that I add to Storyboard. I am getting a null when I call InstantiateViewController. I have tried cleaning and rebuilding. It works on simulator, but not on device
The issue was as pointed out by #orkenstein. I was targeting 7.1 whereas was testing on a 7.0 device

MonoTouch for IPad: How to show another UIViewController in a UIPopoverController?

As title said, I want to show another UIViewController from an existing UIViewController which is hosted in UIPopoverController. I tried the following method:
_secondViewController = new SecondViewController();
this.ModalPresentationStyle = UIModelPresentationStyle.CurrentContext;
this.ModelInPopover = true;
this.PresentModelViewController(_secondViewController, true);
However, the secondViewController is shown in the main view controller, instead of the popover controller.
In this post somebody mentions that it cannot be done and it violates the HIG. However, I have seen this in other apps (e.g. Yahoo! Email) if I'm not mistaken.
I'm also thinking about another approach: If I could create a UINavigationController within the popover context, it might work by just adding new ViewController to the NavigationController. But how?
Remember that UINavigationController derives from UIViewController.
So, you can use the controller contained within UIPopover just like any other container... in this case it's best to use UINavigationController inside UIPopover to display ViewControllers.
Usage:
var _NavController = new NavController();
Popover = new UIPopoverController(_NavController);
Popover.PopoverContentSize = new SizeF(..., ...);
Popover.PresentFromRect(...);
NavController:
public class NavController : UINavigationController
{
UIViewController _FirstViewController;
UIViewController _SecondViewController;
public NavController()
: base()
{
}
public override void LoadView()
{
base.LoadView();
_FirstViewController = new UIViewController();
// Initialize your originating View Controller here.
// Only view related init goes here, do everything else in ViewDidLoad()
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// When a button inside the first ViewController is clicked
// The Second ViewController is shown in the stack.
_FirstViewController.NavButton.TouchUpInside += delegate {
PushSecondViewController();
};
this.PushViewController(_FirstViewController, true);
}
public void PushSecondViewController()
{
_SecondViewController = new UIViewController();
this.PushViewController(_SecondViewController, true);
}
}

Resources