I have an application where I have 3 UINavigation Stacks that can be switched between each other via a custom menu. When switching from Stack A to either Stack B or C, it looks like the new section is being pushed onto the current navigation stack, as the RootViewController for Stacks B/C both have back buttons that pop back to the previous stack. User's can either navigate back to Stack A using the custom menu, or by hitting the back button on Stack B/C RootViewController, which brings them back to where they were in Stack A.
The problem I'm having is figuring out the best way to keep track of whether a user is in Stack A. If they are on the 4th drill down in Stack A, switch to Stack B, and then switch back to Stack A, I need to show exactly where they previously were in Stack A's flow.
Should I be using multiple UINavigationControllers, or is there perhaps a way to achieve this without as much hassle (i.e. possibly using UIViewController Containment)?
You could use containment to somehow change the navigation controller, yes, but you certainly don't need to do that. You can grab the whole stack within a UINavigationController before replacing it and keep track of your 3 stacks, in a structure like an array or dictionary.
typedef {
Section1,
Section2,
Section3
} Section;
..
#property (nonatomic, assign) Section currentSection;
#property (nonatomic, strong) NSMutableArray currentStacks; //Initialize this was the base stacks for each section (i.e an NSArray with just 1 controller for each slot)
#property (nonatomic, strong) UINavigationController *navigationController;
..
- (void)setSection:(Section)section
{
self.stacks[self.currentSection] = [self.navigationController.controllers copy];//Save stack for the current section
[self.navigationController setViewController:self.stacks[section] animated:YES];
self.currentSection = section;
}
As long as you don't have any problems with performance, my proposition is to keep all required view controller stacks inside separate UINavigationControl-s. So you would have as many navigation controller objects as items in your menu. In this case you would have rather less problems and your logic would be cleaner.
This approach doesn't require much memory, because the most consumable thing is UIView hierarchy and not UIViewController-s or UINavigationController-s.
Related
I have been looking for tutorials regarding the setup of a UIPageViewController because somebody recommended not using my navigational controller but instead to use the PageView. I am using the swipe gesture left and right as well but I am yet to find a good tutorial online in regards to the setup of the UIPageViewController via storyboard (or at least majority storyboard).
I included a picture of what I am working with at the moment: http://imgur.com/a0iuFNW
Any help in regards to the setup of the PageViewController and then following controllers would be much appreciated!
Thank you for your help in advanced
I'm sure you can find some tutorials out there if you Google it, but this should give you a start trying your own implementation. I created a new project starting from the "Page-Base Application" template. You should study the code that gives you to try to understand what it does (along with reading Apple's documentation on UIPageViewController).
That project creates several objects. The RootViewController creates the page view controller and adds it as a child view controller. For my test app, I didn't change anything in this file other than changing the transition style of the page view controller to UIPageViewControllerTransitionStyleScroll (instead of page curl, but you should try the original value too to see what it looks like).
The ModelController class contains the model, which in your case would be an array of image names (or pieces of the name as I have). This class implements the page view controller data source methods that handle the turning of the pages. I changed the init method in that class to this,
- (id)init {
if (self = [super init]) {
_pageData = #[#"1", #"2", #"3", #"4", #"5", #"6"]; // my images are named img1.JPG, img2.JPG, etc. I will use these numbers to construct the path to my files in the bundle
}
return self;
}
So, you can see that the model is just an array of number strings (one number for each image). You might have to try something different depending on how your images are named. When you swipe between pages, the index into this array is either incremented or decremented, and the value in the array is passed to the DataViewController into its dataObject property.
The DataViewController is the controller that actually shows your content. It's the right controller in the storyboard. I modified its view by adding a navigation bar at the top so we can put in a title and button (to go to your tips view controllers), and replaced the UIView subview with a UIImageView that takes up the whole screen under the navigation bar. I made IBOutlets to the image view and the navigation bar. Here is the changed .h file for that controller,
#interface DataViewController : UIViewController
#property (strong, nonatomic) NSString *dataObject;
Finally, in the .m file, I made it look like this,
#interface DataViewController ()
#property (weak,nonatomic) IBOutlet UINavigationBar *bar;
#property (weak,nonatomic) IBOutlet UIImageView *iv;
#end
#implementation DataViewController
- (void)viewDidLoad{
[super viewDidLoad];
UINavigationItem *titleItem = self.bar.items[0];
titleItem.title = [NSString stringWithFormat:#"Course Guide %#",self.dataObject];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"img%#", self.dataObject] ofType:#"JPG"];
self.iv.image = [UIImage imageWithContentsOfFile:imagePath];
}
-(void)dealloc {
NSLog(#"in dealloc");
}
I'm using initWithContentsOfFile: to create the images (rather than imageNamed: because it doesn't cache the images) based on the number that's passed in to the dataObject. I implemented dealloc to illustrate the fact that as you swipe back and forth through the pages, the controllers are deallocated, thus keeping your memory footprint small. If the transition style is page curl, the controllers are deallocated as soon as they go off screen, so there's never more than one in existence at a time. If the style is scroll, then there are 3 in existence at a time, because the page view controller creates the current one as well as the previous and next ones to make the loading faster.
As you can see, I've made only a few small changes to the code the template gives you, in order to display an array of images which should suit your purpose. A structure such as this for your app will be much more memory efficient than using a stack of controllers in a navigation controller.
I am using ECSlidingViewController for creating "hamburger" menu for user with some options and one of them is login. I edited first example in archive with ECSlidingViewController and it's working and looking good.
Now I just want to extend this to have option after successful login to "redirect" user back to controller from which he taps on "hamburger" and go to login (like on websites). I think that I would need to save somewhere (maybe AppDelegate) from where I was going, is it right?
My AppDelegate looks like this:
#interface MAAppDelegate : UIResponder <UIApplicationDelegate>
#property (nonatomic, strong) ECSlidingViewController *slidingViewController;
#property (strong, nonatomic) UIWindow *window;
#end
This is method which is call after tap on "hamburger" (navigation bar item):
- (IBAction)menuButtonTap:(id)sender
{
if (self.slidingViewController.currentTopViewPosition == ECSlidingViewControllerTopViewPositionCentered) {
[self.slidingViewController anchorTopViewToLeftAnimated:YES];
} else {
[self.slidingViewController resetTopViewAnimated:YES];
}
}
And in my MAMenuViewController (controller with static table for options in side menu) I have sets a few sliding segues for table cells to go to each controllers (main, login and so).
Why not present the login view as a modal. The user isn't going 'back' from the login, they are cancelling, and it is, conceptually, completely separate functionality to the other part of the app. Presenting modally would usually use a different transition style and marks the screen out as something different.
While the modal is presented, you can also reset the sliding controller so your 'under' menu is no longer displayed. Then, when you dismiss the login view you are already ready without further animations.
I think, if i'm right, that what you want to achieve here is " Pushing back " to your previous view controller. So, in that case, you might want to call, in your current view controller
[self.navigationController popViewControllerAnimated:YES];
You can get full answer with more information on this post. On this post, you may found a little more explanation on this post too, on how to identify previous view controller.
I have read Apple's design patterns docs, and a few other guides and there are things I can not understand .
I encounter the problem of passing variables between viewControllers, and I saw the delegate option.
Than i have realize that if you go from viewControlA to viewControlB , and you need to update some mutableArray from B to A , you can post a delegate from B and A will get it .
BUT, if A can hear the delegate, that means that A is still alive after I went to B .
I was thinking that only when you push between views, the previous is still alive, but when the transition is modal, the previous scene is actually dead .
What is the life cycle of each view controller class ? They are always alive ?
If you have two UIViewControllers called A and B, and you want to show B modally, A stays in memory. No one says to A to remove (this is true until some other part of the code will remove it).
So, A can respond to B until the latter (B) remains the presentedViewController of A (presentingViewController).
About the delegate, you could just avoid it. Suppose for example that A and B as a property like
#property (nonatomic, strong) NSMutableArray* myArray;
Before presenting B as the modal controller, you can say
B* b = // alloc init
b.myArray = [self myArray];
// present modally B
Now they will touch the same array. When B is dismissed (it will released from memory if you have no references to it), in myArray (within A) you will find the modifications done in B.
Obviously this is just an example. And this is not an advice to not using delegates.
For further references I would just take a look to Presenting View Controllers from Other View Controllers in Apple Doc.
I have a TableView which describes a book with sections which represents the chapters and rows representing the verses.
A the top of this TableView I have a button in a navigation bar to allow "navigation".
The goal of this navigation button is to allow the user to easily jump to a given chapter/verse without scrolling manually (which can be very long).
When the button is pressed a tableview controller is called displaying all the available chapters of the book and when a chapter is selected another table view is called displaying a list of the available verses in the current chapter. Finally when the line is chosen the tablew view displaying the book should scroll to the given index/row.
So the idea : from the tableview representing the book I call the chapters view as modal and the verses as a push over the chapters view.
My problem is that I don't get the point of managing the delegate and dismissing from the 2nd modal view.
With 1 modal view I do things like that.
In the displayed VC (View Controller) I added the protocol and the delegate
#protocol ChapitresTableViewControllerDelegate <NSObject>
- (void)didDismissPresentedViewController;
#end
#interface ChapitresTableViewController : UITableViewController
#property (nonatomic, weak) id <ChapitresTableViewControllerDelegate> delegate;
#end
I have in the didSelectRow
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.delegate didDismissPresentedViewController];
}
in the displaying VC I add the following line
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ChapitresTableViewController *chapitresTableViewController = segue.destinationViewController;
chapitresTableViewController.delegate = self;
}
and of course
-(void)didDismissPresentedViewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
this would just work fine if I wanted to close after the first modal VC, but what I want is to have the second one being dismissed after I click in the second. Ok I can write the protocol and stuff in the second modal VC but how do I manage to have the delegate being send to the second VC.
Hope my question is clear enough it is not so easy to explain it.
Anyone understand me and can help me ?
NOTE : I know for now I don't pass any parameters back to the delegate, which I will do later to do the scroll. For now I just want to be able to close the second view, then I will add the required stuff to pass the parameters back to the delegate
I'm sure you can do this, but rather than modal view controllers with a navigation bar, wouldn't it be easier to use a navigation controller? Then you can use popToViewController to go back as many levels as you want to a particular view controller. You can either pass the UIViewController* of the various controllers you might want to pop to, or do so programmatically: e.g. How to pop back to specify viewController from navigationController(viewControllers/stack)?
In this scenario previous views controllers are retained. The ones you pop off are released (just like the modal ones you dismiss are released), but the ones that you pushed from are retained (just like the ones you presented from in a modal world are retained).
If the book is large, though, you'll have to be sensitive to memory usage. Thus, you will probably want to handle didReceiveMemoryWarning to release the model data for the previous views in either your modal sequence or push sequence, in which case, on viewDidAppear, you'll want to see if your app had to release the memory in response to didReceiveMemoryWarning and reload it in that case. But that's the desired behavior, either way, gracefully release the pages if needed (and reload them when the particular view reappears), but keep it in memory if you can.
Finally, you might also want to contemplate using UIPageViewController. Given what you've described, I'd like consider UIPageViewController first, UINavigationController and push segues second, and the use of modal segues third.
This may be an easy answer for someone.
I first built an a navigation app that had a table loaded from SQLite. The rootViewController (UITableViewController) is loaded from the mainWindow.xib . I added the search and scope functions, and push a detailed view (UIViewController) from a row selection just fine, and can navigate back and forth to and from the table and filtered results, search, with scoping different searches. All is good, no errors, crashes, or warnings.
Now I have tried to add a tabBar interface on top of the rootViewController...after 2 days I have got the TabBarController to display (I was trying to implement it directly on the RootviewController, but found I had to implement it in the mainWindow) and I can select different views (xib) from the tab bar. I have one tab that has no view assigned to it in IB, and I can see the RootViewController load as it did before as the first screen in this tab view, even though RootViewController is not assigned in the tab.
The problem is I cannot click on a row in the table when it loads this way. Alternatively if I create a tab, calling the RootViewController, I get the search bar on the top, but the table is empty. I feel this is somehow due to my appDelegate loading the rootViewController, and me not knowing how to get it to "load" or "reload" into the tab, or something like this. I tried creating a separate "search.xib" that was identical to the original mainWindow before adding the tab bar, then trying to load that in the TabItem, so it called the appDelegate first, but no cigar: crash. I verified the search.xib works fine, as I put it as the info.plist "Main nib file base name", and this loads fine and works as before this BS of adding a tabBarController...
I would start pasting code, but not sure what someone would need to know what is missing or wrong. I do call [self.tableView reloadData] in -(void)viewDidLoad, in RootViewController.m but it is not helping this problem at all.
Can anyone help?
Mac OS X is version 10.5.8, and I am using XCode 3.1.4.
// Create and configure the main view controller.
RootViewController *rootViewController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:nil];
rootViewController.violinMakers = violinMakers;
[violinMakers release];
// Add create and configure the navigation controller.
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navController = navigationController;
[navigationController release];
// Configure and show the window, Override point for customization after app launch
[window addSubview:[navController view]];
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
Thanks for trying to help!
The last section above appears to be where I should get the RootViewController to be on top of the stack, but not sure how.
// <AppName>AppDelegate.h
#interface <AppName>AppDelegate: NSObject <UIApplicationDelegate,
UITabBarControllerDelegate, UINavigationControllerDelegate>{
UIWindow *window;
UINavigationController *navController;
UITabBarController *tabBarController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navController;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#end
Ben - I do realize what you are saying about the rootViewController being behind the tabBarController. I just don't know how to set the tabBar as the main view, and the navController as a tab of it. I will look at the tutorial you mention and see if that helps explain it. Thanks Ben!
09-10-14 update
Some progress!
I now have the view in the tab with the with the NavBar at the top. I know it is coming from the correct .xib file as the title and the search can now be clicked on, and the scope buttons are the correct names. .... but as before, if I got to this stage or even close, the table information is not loaded into this tab(all cells are blank). How do I get my table to load properly in this cell? I know it is loading on launch, as if there is no view assigned at all to this window, I can see the table n Nav, but cannot click on it.(So close, yet so far away). I now have the tab set up correctly, but the table is not loading properly... rrrr
I have the typical
[self.tableView reloadData];
in the viewController.m in the method:
- (void)viewWillAppear:(BOOL)animated
and in
- (void)viewDidAppear:(BOOL)animated
and in
- (void)viewDidLoad
and tried it some other methods as well, but it is not reloading the table info when I select the tab.
Any ideas on how to reload this table in the TabBar view properly ?
I did find a good tutorial on tabBars that went a bit further than some others in explaining with IB. Twillo Embed Nav in a Tab Tutorial
It would appear that you're adding your tab bar controller on TOP of your table controller. It's not clear where you set up tabBarController, but you should only be adding ONE view to your window (in the third-to-last and second-to-last lines you are adding two).
The basic premise for Navigation and TabBar controllers is that your Tab Bar controller is the primary, and you'll add the Navigation controller as one of its tabs. Then, add the tabBarController.view to your window. Which ever tab is selected will be the visible one.
There's a tutorial posted on the web on this subject.
Basically here is what I have found for those of you with the same problem.
It is not easy, or really suggested by Apple it instantiate a tab bar later in a program. Yes it can be done, but now I would suggest a different method. I found a modal view controller works just a good as a tabBar, abeit smaller, but takes less real-estate, because it can be placed in the navigation bar. If you are planning an app with tabs, start with a tab based app and then customize it, don't try to change the navigation structure later, which is the underlying issue. Secondly I have found Apple's documentation on Interface Builder less than satisfactory. Everyone seams confused and it's implementation limits the actual final product cusomization, not making it easier. Not to mention more confusing on "wiring" all the elements together in it. That really sucks, and I took 2 weeks in trying various methods to make it work. It is not impossible, just incredibly unintuitive, and a mistake on my part to change paddles half way down stream. Look at the alternatives to the way you want to give access to the information, and I think you will find better ways like the modal view to accomplish this, or by simple buttons with IB actions to access different views or further information.
Happy Programming!
Kirk