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

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);
}
}

Related

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);

Difficulty with TabBarControllers intermixed with NavigationControllers

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.

this.NavigationController is null (xamarin ios stoaryboard)

when I'm moving from one story board to another storyboard via click on signIn button, this.NavigationController is showing null. so I'm not able to PushViewController.
I've one stoaryboard with two views and that two views have separate UIViewControllers.
You must explicitly create a UINavigationController and place your initial view controller inside of it. Doing this will automatically set the NavigationController property of any view controller contained within that Navigation controller.
// create the view controller for your initial view - using storyboard, code, etc
var first = new UIViewController(...);
// wrap your VC inside a Nav controller
var nav = new UINavigationController(first);
Just to add to Jason's answer. By default the RootViewcontroller is the initial view controller.
What I did was to override the that initial view controller with the a navigation view controller:
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public override UIWindow Window
{
get;
set;
}
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
Window.RootViewController = new UINavigationController(Window.RootViewController);
return true;
}
This is the line I added:
Window.RootViewController = new UINavigationController(Window.RootViewController);
Window.RootViewController: is the ViewController, e.g MainViewController
and I'm overriding this initial controller, that's set by default, with the navigation controller.
This worked perfectly for me.

FlyoutNavigation sub viewcontroller can't perform segue on main controller?

Using flyout navigation on monotouch by James Clancey. Can't get a subview controller to elegantly perform a segue on the main containing controller?
NavigationRoot = new RootElement ("Navigation") {
new Section ("Menu") {
new StringElement ("Mapview Controller"),
new StringElement ("Another View Controller"),
}
},
ViewControllers = new [] {
// mapview is a view inside this viewcontroller and able to perform segues
new UIViewController { View = mapView },
// this is a seperate viewcontroller but is embedded inside the flyoutNavigation
// AnotherViewController cant seem to call the root navigation controller (<-- actually a uiNav inside a tab bar nav) so i can perform a segue movement just like the above mapViewController??
this.Storyboard.InstantiateViewController("AnotherViewController") as AnotherViewController,
},
// sub viewcontroller i.e. AnotherViewController trying to move mainViewController to perform a segue..
// throws error: Receiver (<FlyoutNavigation.FlyoutNavigationController: 0x14360600>) has no segue with identifier 'segueTest'
this.ParentViewController.PerformSegue("segueTest", this);
* Update:
I gave up on segue. Instead of using additional view controllers, I'm relying on views but one of my views is a static-celled tableview and not sure how to add it to above code liek so:
ViewControllers = new [] {
new UIViewController { View = mapView },
new UIViewController { View = myTableView } // doesnt work - tried with 'new UITableViewController' which also fails
What do I do?
You can use navigation for that..
ViewControllers = new [] {
new UINavigationController (this.Storyboard.InstantiateViewController("profileViewController") as ProfileViewController),
}

MonoTouch Instantiating a ViewController programmatically for ContainerView

I am trying to work with a container view in MonoTouch and I am following some tutorials online. They talk about adding and removing view controller programmatically from the container. I created a viewcontroller and view in the storyboard of my project and attached a few outlets and one action (for labels and buttons respectively). I created an overloaded construc
Here is the code in the view controller that I am trying to add viewControllers into the container view.
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
_controllerOne = new IngredientsController("Perishables");
_controllerTwo = new IngredientsController("Spices");
AddChildViewController(_controllerOne);
ContainerView.AddSubview(_controllerOne.View);
_controllerOne.DidMoveToParentViewController(this)
}
When I add the subview for _controllerOne I get an error because the elements on my controller are marked null. Is MonoTouch incapable of having view controllers being programmatically created if the controller was made in Interface Builder? Below are the two constructors for the Ingredient Controller. When the segue is used then all of the UI controls are initialized properly. Do I need to create the controller programmatically and then instantiate it that way? Any help would be appreciated.
//This ctor does not work
public IngredientsController (string title) : base(NSObjectFlag.Empty)
{
_ingredientTitle = title;
}
//This ctor works
public IngredientsController (IntPtr handle) : base (handle)
{
}
Try to swap the AddSubView() and DidMoveToParentViewController() methods like below:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
_controllerOne = new IngredientsController("Perishables");
_controllerTwo = new IngredientsController("Spices");
this.AddChildViewController(_controllerOne); // Root child controller.
_controllerOne.DidMoveToParentViewController(this); // Confirm the rooting.
ContainerView.AddSubview(_controllerOne.View); // Access the view.
}
Try instantiating the view controller like this:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
this.ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
var newController = this.Storyboard.InstantiateViewController("IngredientsController");
this.AddChildViewController (newController);
this.ContainerView.AddSubview (mapController.View);
}
Make sure you set the Storyboard Id in the properties panel for the ViewController

Resources