A more detailed explanation of my app layout: clicking on any of the rows of the table view will take the user to the next screen with a back button pointing back to home and a segmented control on the top. A logic in the code determines which of the segments of the segmented control will be preselected. The user can click on any of the indexes of the segmented control to change the content displayed on the screen.
The way I have implemented the above layout is through a navigation controller. The content of each of the pages corresponding to “first, second & third" of the segmented control are all separate view controllers. The reason I have it this way is because each of these pages have significant functionality and controls for the user to interact with. Keeping them each as a separate view controller helps software code organization and data integrity. The home screen is at index zero of the stack of navigation controllers, view controller corresponding to first at index one of the navigation controller and so on. Let's say the user is currently on the second screen with “first”selected in the segmented control. If the user now clicks on "third", two view controllers are pushed onto the stack and vice versa for popping controllers out of the navigation stack.
Two questions:
• any comments on the way I have implemented? Are there suggestions for any better implementations? One specific implementation that I did consider is the possibility of having one view controller with three separate views (one each for first, second & third)? Any comments on this approach?
• I seem to have an extremely hard time controlling the behavior of the “back button”of the navigation controller. When the user has selected “second”in the segmented control, I would still like to have the back button saying “Home” instead of “first” which is the default behavior of the navigation controller. I have figured out how I can customize the text of the back button. However, I can't seem to figure out how to customize the behavior of the button. What I mean by that is, when the user is on "third”, and clicks on the “home button”I'd like to pop three view controllers and land the user on the home screen.
On SO, I saw and tried various techniques with no success:
approach 1: viewwillDisappear(): determine if this function is being called as part of a back button press and implement the logic of popping additional view controllers beyond the standard one view controller pop. For a moment, this logic does indeed pop back to the home page however it immediately crashes with the following message which I don't seem to understand:
approach 2: didPopItem(): I put the following code in this function
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item {
NSLog(#"%s",__FUNCTION__);
[[self navigationController] popViewControllerAnimated:YES];
//ViewControllerAnimated:YES];
NSLog(#"navcount%d",self.navigationController.viewControllers.count);
if (self.navigationController.viewControllers.count > 1) {
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
}
}
any comments on the above will be much appreciated! Thanks in advance for your help.
Since the three view controllers are really equal to each other in your view hierarchy, I would suggest replacing the top view controller instead of pushing multiple view controllers when you switch between your segments so that you can go "Back" from any of the three view controllers and you will end up where you want to be.
Something like this should work for you:
- (void)replaceTopViewControllerWith:(UIViewController *)vc {
NSMutableArray *vcs = [[self.navigationController viewControllers] mutableCopy];
[vcs removeLastObject];
[vcs addObject:vc];
[self.navigationController setViewControllers:vcs animated:YES];
}
Related
I am answering my own question in this post.
Requirement: I want to have a tab view controller as the top parent. This will have 5 tabs. In the first tab, I want to have a segment control at the top which I want to switch the views within this first tab with information based off of which segment index is selected.
Reading a few suggestions online were to-
Either use containment view controllers where the first tab holds strong references to the child view controllers and switches them based off of which index is selected in the segment control. Problem with this is that each of the view controller will be eating up memory as we are keeping them in strong reference.
Second idea I read online was to put everything on a single view controller in the first tab - based on which index segment control is selected, just hide everything else. Problem with this was super messy code with too much stuff on the same view controller plus storyboard would get messy with things on top of each other.
Solution I came up was to embed another tabbarcontroller inside the first tab's view controller. Hide this second tab bar. This second tab bar will have the 3 child view controllers - each for each segmentcontrol's index. Whenever the segment is changed, I change the tab.
So in the second tabbarcontroller (self is the second tabbarcontroller)
-(void)segmentChanged:(UISegmentedControl*)sender{
NSLog(#"New value: %d",sender.selectedSegmentIndex);
[self setSelectedIndex:sender.selectedSegmentIndex];
}
This way iOS will put the view controllers in memory only when the segments are switched and not from the very beginning. Also iOS UIKit will handle the memory management for the tabs as mentioned here.
Plus we don't have to deal with messy code and storyboard shenanigans. Each segment control's index logic is separate in it's own view controller from the second tab.
I am using Storyboards for an iOS 7 App. The root controller is a menu. For almost every view, I have a Menu button which brings the App back to that menu using [self.navigationController popToRootController:TRUE]. However, there are two UIView elements where I would like to clear the Navigation Controller view list (as happens when you pop back to the root controller), but then immediately go to another UIView without having the user see the root controller's view. Once at this new view, if the user presses the back button, I want them to go to the menu view.
I've tried to put a performSegue in the viewWillAppear, but it really messes up the Navigation Controller and views. I've tried putting a performSegue in the viewDidAppear, but the user sees first the Menu view flash in, then out on it's way to the correct view.
I hope I've explained this well enough. I hope this can be done.
Thanks.
Your best bet is to build the navigation controller stack yourself, and then use - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated: to replace the current stack.
So you could do something like
...
UIViewController *vcToPush = ...;
NSArray *newVCStack = #[[self.navigationController.viewControllers firstObject], vcToPush];
[self.navigationController setViewControllers:newVCStack animated:YES];
This will add your new controller to the stack using the standard push animation (or not if you so choose), and after the animation is complete, set the view stack to that of the array.
I am currently working on a project for the iPad using Storyboards for the 1st time and I am wondering if my approach is the correct way to do this.
The first ViewController in this example is actually a split view controller.
Currently within the iPad app when a user clicks on the Export Features Button I am conditionally requesting the segue based on some code / checks I am running
[self performSegueWithIdentifier:#"subscribe" sender:self];
[self performSegueWithIdentifier:#"filterOptions" sender:self];
[self performSegueWithIdentifier:#"showExportedDoc" sender:self];
However I am not sure if I should have 3 navigation controllers and also when a user clicks on the Buy button in the subscribe View Controller it pushes to the Filter Options View which is actually nested in another Navigation Controller.
Any help / advice on this would be great as I mention I am just not sure if I am following the best approach with this.
Thanks
... and
also when a user clicks on the Buy button in the subscribe View
Controller it pushes to the Filter Options View which is actually
nested in another Navigation Controller.
Well, I think you have some misunderstanding here. The fact that the filter options view controller is embedded within a navigation controller in your storyboard doesn't mean that it will be instantiated with the UINavigationController when you're pushing it within the current navigation controller (It will be so though if you connect the segue to the UINavigationController that it's embedded into).
To answer your original question, I don't see right and wrong approach here. It all depends on the structure you would like to have. For me, I guess I would prefer to have only one UINavigationController that manages everything (set it as an initial view controller and embed your home view controller within it). This would provide a more consistent navigation experience to the user, plus more consistent look (navigation bar would be shown from the beginning).
I'm interested to know conceptually how to reset tabs based on the actions in a "main tab" the app is a writing app in which one can work on multiple projects at a time.
The first tab will show a table view showing all the projects currently available, when one selects the a project, I would like all the other tabs to "reset" back to their home screen (some will have navigation where you can drill down)
Any thoughts appreciated, thainks.
From the controller with the table (in the "main tab"), you can access all of the tab bar controller's view controllers with self.tabBarController.viewControllers. You could loop through that array, and check if the object is a navigation controller -- if it is, call popToRootViewControllerAnimated: to go back to the initial content controller.
To reset all tabs we can write the following code
- (void)resetAllTabs{
for (id controller in self.tabBarController.viewControllers) {
if ([controller isMemberOfClass:[UINavigationController class]]) {
[controller popToRootViewControllerAnimated:NO];
}
}
I am a newbie to IOS programming and currently i have a tab bar application with two tabs. I have two questions.
The first tab shows a map, imagine it with some pushpins. There is a button on the navigation bar and when this is clicked i want the map view to to move out and a list view to come in. You can see the UI from the image. The button is called list.
Now when i click list I want this view to go away and the list view to come in. So here are my questions ?
1) How do i do this ? I tried the navigation model but i don't want that because I do not want a back button. I tried to make two different views and just dragged the button to that view but the app crashes. I just want the list button on the nab bar and when clicked the view changes to the list view and the button text changes to map. So now if I click the button again it should go back to the map view and the button changes to list.
2) How do i achieve the animations for this ? Ive seen some app where the page flips around and I've seen some options like reducing the opacity etc but I want to achieve the flip animation.
Thank You for any help I get. I really appreciate it.
Interface Builder can do most of this. Hold down the control key and drag from your map View Controller's UIBarButtonItem titled "List" to your list View Controller, then choose the Action Segue "modal". An arrow appears representing the segue; click on it and use the Attributes Inspector to change the Transition to "Flip Horizontal". Here's a video
Or, you could do this programmatically with presentViewController:animated:completion.
Now to get back to the map from the list, I believe that must be done programatically. Create a method that calls dismissViewControllerAnimated:completion: and make your list View Controller's UIBarButtonItem titled "Map" trigger it.
After reading your comments, I am wondering... if the structure of your app is logically a tabbed app structure (as indeed you refer to it as a 'tab bar application'), shouldn't you consider using the UITabViewController instead of a NavigationController? That is what it is designed to do, after all.
If you do use a TabViewController you should reconsider your desire for flip animation, as that doesn't really make UI-sense for tabs. If you can dispense with the flip animation, TabViewController could be a good way to go and you should at least experiment with that before dismissing the idea. It is also designed to grow... you can incorporate any number of tabs in a tab bar. Check out the apple docs (with pictures!)
You will notice that tabs are at the foot of the screen, whereas your 'tab' navController buttons are in a navbar at the top of the screen. This also helps as your app grows, as it is straightforward - from a UI design point of view and programmatically - to incorporate navControllers as navigation tools within individual tabs. For example, if your map/list flip routine does indeed make sense for this part of you app, you can keep this as a single tab (in it's own navigationController) and add other tabs for other parts of the app...
update
From your comment, you are saying that you are interested in the navController-inside-tabBarController setup. In this case here are some ways to get flip transitions AND no back button..
(1) modal presentation
The easiest way to get what you want is to set up one of your viewControllers (say the map view) to present the other one (the list view) modally.
If in the storyboard:
embed your mapViewController in a navController with a navbar button for navigation to the listView as in your picture
add your listViewController to the storyboard and embed it in it's own navContoller (not the mapViewController's navController). Drag a barButtonItem to this navController and wire it up to an IBAction in listViewController
CTRL-drag from mapViewController's 'list' button to the listViewController to create a segue. Select the segue and in the attributes inspector set the segue type to 'modal', with transition 'flips horizontal' and 'animated' checked. Give it a name in case you want to refer to it in code.
in the listViewController's IBAction add this:
[[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];
That should achieve your result. You can use the completion block to send information back from the list view to the map view, and/or set the map view as the listView's delegate.
If you are not using the storyboard check this apple guide
Presenting View Controllers from Other View Controllers
especially "Presenting a View Controller and Choosing a Transition Style".
There is one catch with this approach - when the presented view flips onto the screen, the entire previous view, including the tab bar, is flipped out of the way. The idea is that this is a modal view which the user is required to dismiss before doing anything else in the app.
(2) push/pop in a single navController If this does not suit your intent, you can navigate using a single NavigationController with push and popping of views, and you can hide the back button ... but you really would need to keep the back button functionality as you do want to go back to the mapView, not on to a new map view.
To hide the back button try:
self.navigationItem.hidesBackButton = YES
in the uppermost viewControllers' viewDidLoad
Then you can add a barButtonItem in the xib/storyboard, with this kind of IBAction:
[self popViewControllerAnimated:NO]
or
[self popToRootViewControllerAnimated:NO]
You would have to construct the flip animation in code as it is not supported as a built-in with UINavigationController (best left as an exercise for the reader!)
(3) swapping views in a single viewController As ghettopia has suggested, you could use a single viewController inside a navController (or with a manually place navBar) and swap two views around using the UIView class methods
transitionFromView:toView:duration:options:animations:completion
transitionWithView:duration:options:animations:completion.
This could be a good simplifying solution as your list and map are essentially two views of the same data model.