What is the pattern of navigating between UIViewControllers in iOS? - xcode

I have a bunch of UIViewControllers subclasses (let's call them MainForm, DetailForm, MoreMinorDetails). Basically the idea is that AppDelegate class instantiates MainForm, user presses some type of button on MainForm and DetailForm comes up. Then on a button on the DetailForm launches MoreMinorDetails. And of course, I should be able to go back down to the MainForm.
Note that there aren't any UINavigationController objects anywhere in sight.
What is the accepted pattern to move between UIViewControllers in a manner described above?
Or am I going about it in the wrong way?
I'll be happy with either XCode or MonoTouch based explanation.

You can use a UINavigationController and hide the navigation bar:
self.navigationController.navigationBar.hidden = YES;
Then in your button's action just push the next view controller:
-(void)buttonAction:(id)sender
{
NextViewController *nextViewController = [[NextViewController alloc] init];
[self.navigationController pushViewController:nextViewController animated:YES];
}
To go back, use
-(void)goBack
{
[self.navigationController popViewControllerAnimated:YES];
}
To go to a certain view controller (you have to know exactly when it was pushed onto the navigation controller's stack):
-(void)goToViewController
{
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
}
Or to pop to your root view controller
[self.navigationController popToRootViewControllerAnimated:YES];
This way, you will obtain the UINavigationController's functionality and keep all the space in the view.

AngryHacker,
My simple suggestion is to follow zoul one. I think the simplest way to achieve what you want it' to create a UINavigationController and use it as a containment controller for other controllers.
So, the way could be create a UINavigationController in the AppDelegate and set it as the rootViewController for your window. When you create a UINavigationController you can pass to it a root controller (in this case MainForm).
In MT it looks like the following (do not trust the code because I've written it by hand)
private UINavigationController navController;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
navController = new UINavigationController(new MainForm());
window.RootViewController = navController;
window.MakeKeyAndVisible ();
return true;
}
Now, when you launch the app you will see the MainForm's view and will able to allow navigation among different controllers.
For example, within MainForm you can go to DetailForm like:
this.NavigationController.PushViewController(new DetailForm(), true);
The same applies within DetailForm to MoreMinorDetails.
To go one step back, for example from MoreMinorDetails to DetailForm use
this.NavigationController.PopViewControllerAnimated(false);
To go to the the root controller (MainForm) within DetailForm or MoreMinorDetails use
this.NavigationController.PopToRootViewControllerAnimated(false);
About the space, it's not a problem. I guess you have two ways. The first is to move the bar items you have created within the navigation controller bar. In each controller you can decide what buttons make visible or not. The second is to hide completely the navigation bar and use the button you've already created.
In both ways you can attach actions to these buttons and allow the navigation between controllers. Furthermore, if you choose the first you can also hide the back button for your navigation bar.
A simple note to take in mind is the following:
Since the navigation bar is unique for a UINavigationController, the bar will maintains its state for all the controllers you push in the navigation controller. To explain the concept suppose you have two controllers, say A and B. You first push A and in its ViewWillAppear method you hide a button. When you push B, the button still remains not visible. If you want to unhide the button in B, you can play with its ViewWillAppear method (like before) and so on...
If you don't want to play with UINavigationController you should take a look to new view controller containment functionality provided by UIViewController class. This applies only from iOS >= 5. You can obtain the same effect of UINavigationController mechanism but it could be more difficult to achieve.
Hope that helps.

Related

OS X storyboards: using "show" segue without allowing duplicate new windows to show?

Right now I have an OS X storyboard app that has a main window, and a button on it that triggers a "show" segue for another view controller. Right now I've got the segue set up as modal because if I don't, the user could click the same button again and end up with two copies of the same window.
Is there a way for me to accomplish this without having to restructure the storyboard to embed these view controllers in a separate window controller (which would seem to defeat the purpose of the flexibility the storyboards offer)?
Edit: While the answer below does work, it is definitely not the best way. In your storyboard, select the view controller for the destination view then go to the attributes inspector and change Presentation from Multiple to Single. That's it, no code required.
Not sure this is the best way but in the NSViewController that is pushing the segue, you could add a property for the destination NSViewController and, in your prepareForSegue:sender: method, assign the destination view controller. Finally, in the shouldPerformSegueWithIdentifier:sender: method, check to see if the destination view controller is assigned, and, if so, bring its window to the front and return NO meaning don't perform the segue, otherwise, return YES. Here's a quick example (to be included in the NSViewController with the button to initiate the segue):
#interface ViewController ()
#property (weak) NSViewController *pushedViewController;
#end
#implementation ViewController
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if (self.pushedViewController) {
[self.pushedViewController.view.window makeKeyAndOrderFront:self];
return NO;
}
return YES;
}
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
self.pushedViewController = segue.destinationController;
}
#end
When you close the window containing the destination view controller, this will set the pushedViewController property of the original view controller to nil so the segue will perform if the window is not already opened. Again, there may be a better way to do this. Hope this helps.
Jon

Split view controller rotation on iPhone 6 Plus

I have a split view controller in landscape mode with two navigation controllers.
This collapses to a single navigation controller in portrait and the detail view controller is pushed from the master.
If I rotate back to landscape when the detail view controller is pushed in portrait I don't understand how to put the detail view controller back into it's own navigation controller.
You should implement UISplitViewControllerDelegate. Simplest way may be to have your own MySplitViewController class and set itself as a delegate in viewDidLoad:
self.delegate = self;
First, you may want showDetailViewController to look something like:
- (BOOL) splitViewController:(UISplitViewController*)splitViewController showDetailViewController:(UIViewController*)vc sender:(id)sender
{
if (splitViewController.collapsed)
{
[(UINavigationController*)splitViewController.viewControllers[0]) pushViewController:vc animated:YES];
}
else
{
self.viewControllers = #[ self.viewControllers.firstObject, vc ];
}
return YES;
}
That should take care of proper showing of details view in both orientations.
Next, you should implement following delegate method similar to this:
- (UIViewController*) splitViewController:(UISplitViewController*)splitViewController
separateSecondaryViewControllerFromPrimaryViewController:(UIViewController*)primaryViewController
{
UINavigationController* nc = primaryViewController;
UIViewController* detailVC = nc.viewControllers.lastObject;
return detailVC;
}
This method is your chance to take whatever you want from primary controller and return that as detail view controller. The example code above is rather simple one, you may need to traverse through navigation viewControllers and pick all starting from specific view controller (assuming you had pushes from details view).
Anyways, it would really payoff to take some time and read: UISplitViewController class reference and especially UISplitViewControllerDelegate Protocol Reference
This will be much clearer.
If you want a shortcut, take a look at Xcode split view controller template project. That one should also contain hint or exact solution for your problem.
Make the detail have its own navigation controller, like in the Master Detail template. When the split view collapses it calls showViewController on the master navigation controller and when it detects a controller of class UINavigationController it sets allow nested navigation controllers true and hides the navigation bar. This way you get to keep the detail navigation so when you rotate to landscape and separate it can use the existing navigation again.

How do i set a modal segue (programmatically) to a push segue

i have an navigation controller in my storyboard, but for one reason i have to make one segue programmatically, but how do i make a push segue programmatically?
this is my code so far:
- (IBAction)nextviewButton:(id)sender {
HolesViewController *Holes = [self.storyboard instantiateViewControllerWithIdentifier:#"HolesViewController"];
Holes.nameString = self.NameField.text;
[self presentViewController:Holes animated:YES completion:nil];
}
Alternatively, I actually prefer to define a segue right in my storyboard, but rather than originating from the button, I have it originate from the view controller itself, e.g.:
Then, give that segue a "storyboard id", say "Details", and then you can invoke that segue programmatically via:
[self performSegueWithIdentifier:#"Details" sender: ...]; // you can specify either the button or `self` for the `sender
I like this because that way, the storyboard continues to visually represent the flow of the app (as opposed to possibly having scenes floating out there with no segues). And you can use the exact same construct for both push and modal segues (and the view controller that's presenting the next view controller doesn't care which one the storyboard uses).
[self.navigationController pushViewController:Holes animated:YES];
Got it

Keep leftBarButtonItems on new UIViewcontroller when pushed

I have a UIViewController embedded in a UINavigationController. The rootViewController now contains already some buttons as leftBarButtonItems.
Now when I push a new UIViewController on top of the UINavigationController I want the new UIViewController to keep the existing leftBarButtonItems extended with the Back-Button.
Right now the situations is as follows: When I push the new UIViewController then the existing leftBarButtonItems disappear and only the Back-Button is visible.
Each UIViewController has it's own "navigationItem" property, which acts as the navigation bar representation for that viewcontroller. When you add buttons to the navigationItem of a particular UIViewController they are limited in scope to the viewcontroller to which they were added, and they don't persist into other viewcontrollers.
Basically, you'll have to add the buttons to the navigationItem of each viewcontroller as it loads. You can make this simpler by writing adding a method to do this work to a class other than your UIViewControllers. What happens when you touch each button might be viewcontroller specific though, so you'll have to think through how touch actions will be fed back to the relevant viewcontroller. Perhaps introduce some kind of NavigationBarDelegate protocol or something?
I found what seems like a hacky way to get around this when pushing multiple instances of the same view controller on to a detail view controller which I assume would work similarly. Before pushing the new view controller I used this: (browser is my new view controller)
self.browser.navigationItem setLeftBarButtonItem:self.detailViewController.navigationItem.leftBarButtonItem animated:YES]; // Sets popover view controller button.
[self.detailViewController.navigationController pushViewController:self.browser animated:YES];
This probably isn't a good way to do it but it seems to work in my situation.

Hide/Unhide UINavigationbar when the screen is tapped

I'm very new with iOS Development and I have just created one of my first apps, in my .xib file I have a UINavigationBar that I want to hide/show when a part of the screen is tapped by the user (like in the Photo app). I've found some snippets online but I don't know where and how to use those.
I'd appreciate a lot if somebody could give me detailed informations about how to do this.
Add this toggle method anywhere in your UIViewController. This hides on first tap and shows again in second tap.
- (void)toggleNavBar:(UITapGestureRecognizer *)gesture {
BOOL barsHidden = self.navigationController.navigationBar.hidden;
[self.navigationController setNavigationBarHidden:!barsHidden animated:YES];
}
If there is no navigation controller, link the navigation bar with an IBOutlet and replace with
- (void)toggleNavBar:(UITapGestureRecognizer *)gesture {
BOOL barsHidden = self.navBar.hidden;
self.navBar.hidden = !barsHidden;
}
Then add the following in the method -(void)viewDidLoad {}
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(toggleNavBar:)];
[self.view addGestureRecognizer:gesture];
[gesture release];
If the view where you are going to tap is a UIWebViewController, you have to add the protocol to the view controller and set it as delegate gesture.delegate = self; then add the following:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
This is needed because the UIWebViewController already implements its own gesture recognizers.
Ultimately, you want to send the -setHidden: message to your navigation bar. The easiest way to do this is to make an Outlet and an Action in your in your view controller. Then, in your .xib file, connect the navigation bar to the outlet and some button (even a large, full screen one) to the action.
Outlets and Actions are basic techniques used over and over in iOS
(and Mac) programming, so if you don't understand them, best go read
up on them now. Every beginning iOS/Mac programming book covers this
topic as does Apple's own Getting Started guide (pay particular
attention to the Configuring the View section).
Inside your action, send a message to the outlet like so:
-(void)myButtonAction:(id)sender{
[[self myNavigationBarOutlet] setHidden:YES];
}
This will hide the navigation bar whenever your button is tapped.
(This assumes you have a UINavigationBar in your .xib like you say. These directions will be different if you're working with a UINavigationController that manages its own UINavigationBar)

Resources