Check nibName of the previous UIViewController - iphone

I have a navigation based app. In a certain screen, I need to check from which screen the user came. I thought about something like
NSArray *viewControllers = [self.navigationController viewControllers];
int viewControllersSize = [viewControllers count];
if ([[viewControllers objectAtIndex:viewControllersSize-2] nibName] == #"Name") {
...
}
But the problem is that if the user clicks "back" from a certain screen, the view controller will be removed from the array defined above.
My current solution is having a global variable that tells me if the user came from a specific screen, but I suppose there is a more elegant solution, right?

Not sure what you meant with:
But the problem is that if the user
clicks "back" from a certain screen,
the view controller will be removed
from the array defined above.
Not all view controllers use same navigation controller?
If same UINavigationController is used for all UIViewControllers, you can use UIViewController parentViewController for this purpose. If going the opposite way, keeping reference to view controller you came from or maybe just [viewController class] (to string) would do the trick.

Related

Passing data from UIViewController to 1 back UIViewController

So I have this UINavigationController, I'm on the first moving to the next view, than I want to hit the 'back' button and to go back to the first view with the data that I saved into 'strAddress' on the second view. I want to present the data on the first view on 'lblShowStr.text'.
how can I manage to do that? I've searched all the web, found some people that wrote, but couldn't understand what they have been told there.
Thanks!
You can get a reference to the previous viewController in your navigation stack by saying:
NSArray *viewControllers = self.navigationController.viewControllers;
MyViewControllerClass *previousController = [viewControllers objectAtIndex:[viewControllers count] - 2];
You can then set a property on the 'previous' view controller to store your text, or even set the label outlet's text directly like this:
previousController.lblShowStr.text = self.strAddress;
It's not the best way to do it (the best way involves creating a custom delegate protocol or using NSNotificationCenter) but it's the easiest way.
In your first view controller you might have an NSString property called strAddress.
and you put that string into lblShowStr.text every time the view appears.
In your second view controller you might have a property pointing to an instance of view controller one. When you instantiate your second view controller you could assign the property on it to the first view controller.
secondViewController.firstViewController = self;
or
[secondViewController setFirstViewController:self];
Then when the user presses the back button viewDidAppear would get called for the first view and update the string.
I am assuming you don't want to store this data anywhere else e.g. in your model or nsuserdefaults etc.

UINavigationController visibleViewControllers

I have a UITabBarController, and one tab is a UINavigationController. I have a search bar that goes to a certain view within the UINavigationController. The problem is that if the first view is not pushed by the UINavigationController, than it crashes because my search doesn't recognize the visibleViewController from this call:
UINavigationController *navController = [self.MainTab.viewControllers objectAtIndex:1];
FirstViewController *fVC = [navController visibleViewController];
What I don't understand is, before this code, I do this:
self.MainTab.selectedIndex = 1;
This code on its own selects the viewController in that tab, where then the view gets loaded to my knowledge. So shouldn't this be enough for the [navController visibleViewController] to get the current viewController? Thanks.
Try topViewController instead of visibleViewController.
FirstViewController *fVC = [navController topViewController];
From what you explain in your question and comments, I understand that your code tries to access an object of type FirstViewController, supposedly the first view to be pushed on to your UINavigationController, when it has not yet been created.
On the other hand, if you first programmatically select the tab, the view is created and everything works fine. Indeed, that view is created in a viewDidLoad method that is run when the tab is selected.
The solution I would suggest is avoiding accessing the UINavigationController visibleViewController directly from your search tab; instead, let your search code access the model (as in Model-View-Controller) for your app and store there the result; then, from the mentioned viewDidLoad method again access the model to read the search result and update/show the UI.
This is the clean solution, IMO. If you want a sort of workaround to your current design, then check the fVC value you get back from visibleViewController and if it is not what expected, then instantiate the view properly.
I hope this helps.
I know this has been answered, but I found another solution that might be helpful. In my case I was handling rotation differently for some viewControllers within my NavigationController, I did the following:
Subclass UINavigationController, then where needed in your new subclass you can access the current visibleViewController's title like so:
- (BOOL)shouldAutorotate
{
if ([[self visibleViewController].title isEqualToString:#"Special Case"]) {
return NO;
}
return YES;
}
This is not specific to rotation, this is just what I used it for. The only thing you have to do is set your self.title for each of the viewControllers you are checking against in their viewDidLoad, if they are set in IB or are not set they will be nil.

How to optimize performance in view controller navigation with UISegmentedControl and UITabBarController

On a project I'm working on, the design decision was to use a UISegmentControl at the top, with a UITabBarController on the bottom. The UISegmentControl has 3 choices for 3 different views. Currently, my coworker has added all 3 views to an NSArray when that particular tab is selected, and then based on the UISegmentControl, the view selected gets unhidden, and the other two are hidden. It seems to not follow Apple's guidelines of lazy loading and seems expensive since 3 viewDidLoads (where queries are made to a database) are getting all loaded at once. There is some lag because of it when the tab is selected for the first time, loading all 3 viewControllers at once.
Is there a better way to do this? I saw a simple example with just two viewControllers, and a button that would switch between the two views. That makes sense to me since you always know what your previous view was, and you can remove that view from the superview, present your new one, release your old one. But with 3 choices, I do not know how to keep track of my view hierarchy (since I could be on segment 0, showing view 0, and then go to segment 2, showing view 2). I am not sure how to check for the last view that was shown, and even if that's the best method. I'm thinking that if there is a better option to keep track of this, but still using the segment control, might as well do it now before the project gets more complex. Thanks!
I would suggest creating a root view controller whose job it is to manage the segment control and load the proper VC depending on which button in the segmented control is selected. The root VC's view would have a subView where the segmented control's VC views are inserted. Something like:
- (void)segmentAction:(id)sender
{
NSParameterAssert([sender isKindOfClass: [UISegmentedControl class]]);
switch ([sender selectedSegmentIndex]) {
case 0:
MYViewController1 *vc = [[MyViewController1 alloc] init];
self.segmentVC = vc;
self.segmentSubvew = vc.view;
[vc release];
break;
}
}
One thing people tend to get hung up on is that there needs to be only 1 VC per screenfull of content -- while that was originally what was recommended by Apple, they have since changed this recommendation. So, loading your segment specific VCs inside the SegmentManagerVC is perfectly acceptable.
You could further tweak this design for performance. For example, you could initially load the VC for the default selected segment and then lazy load the other two so they are already available when a different segment is selected. If you take this approach, though, be sure to hook up -didReceiveMemoryWarning to release the two VCs that aren't currently being viewed.
You could push/pop views onto the UINavigationControler stack. This would also support a "back" button if you wanted it.
[self.navigationController pushViewController:self.myVC animated:YES];
Link a method up to the SegmentedControl that pushes the appropriate ViewController when the corresponding segment is selected. The VC with your segmented control inside of it would need a reference to each segment's corresponding VC. viewDidLoad() will only be called once, and only when the view is pushed onto the navigation stack for the first time.
When you change views or want to go "back", you can pop the VC off the stack:
[self.navigationController popViewControllerAnimated:YES];
Is this the type of functionality which you were looking for?
Edit for Clarity
UIViewController References:
Each view will need a reference to the other two view's ViewControllers. This can be done like this: (assume that we are in "View1", and we also have "View2" and "View3":
View2Controller v2Controller = [[View2Controller alloc] initWithNibName:#"View2" bundle:nil];
View3Controller v2Controller = [[View3Controller alloc] initWithNibName:#"View3" bundle:nil];
The reference to self.navigationController should be declared in your app's delegate as:
UINavigationController* navigationController;
It can be initialized as:
[navigationController initWithRootViewController: rootViewController];
The RootViewController
rootViewController is the UIViewController that corresponds to your application's root view (whatever loads on startup). It is declared in the delegate as:
RootViewController* rootViewController;
And initialized as:
rootViewController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:nil];

Layout screws up when I push several controllers without animation

So I have a stack of three UITableViewControllers, each of which displays its view correctly underneath the navigation bar when I tap through the UI manually.
However, now I'm working on restoring state across app restart, and so I'm pushing the same two controllers on top of the root view controller, one at a time, in the same method in the main thread. What ends up happening then is that the middle controller's view is laid out too far down, and the top controller's view is too far up (underneath the nav bar).
Relevant code:
for (int i = 0; i < [controllerState count]-1; i++) {
MyViewController* top = (MyViewController*)navigationController.topViewController;
int key = [[controllerState objectAtIndex:i] integerValue];
[top restoreNextViewController:key]; // this calls pushViewController:
// so top will be different in the next iteration
}
I suspect that the problem is that I'm not allowing some important UI refresh process to take place in between the two pushes, because they happen in the same thread. It almost looks as though the automatic view adjustment that's supposed to affect the top controller affects the middle one instead.
Is that right? And if so, what should I do, since I do want all the state-restoration to take place immediately upon app start?
EDIT: looks like I was unclear. restoreNextViewController: is a MyViewController subclass method that restores the controller's state based on a stored key, and then pushes the appropriate child controller with [self.navigationController pushViewController:foo animated:NO]. I'm doing this because my actual app, unlike this simplified case, has up to 6 controllers in the stack, and they're not always the same ones. So I figured this would be a cleaner design than going down the stack checking controllers' classes. Each controller already knows how to push a child controller in response to user input; why not reuse that on app restart?
I'm not having any trouble getting the controllers to show up; they're just being laid out strangely.
Have you considered having each view controller push its child during viewWillAppear:? I would just set an isRestoringState property in your appDelegate and check that during viewWillAppear:, if it's set, run your restore for that view, including pushing any visible child view. Something like:
- (void)viewWillAppear:(BOOL)animated {
if ([[UIApplication sharedApplication] isRestoringState]) {
// restore local state, set myChildController if a child is visible
if (myChildController) {
[self.navigationController pushViewController:myChildController animated:NO];
}
}
}
You can call pushViewController:animated: multiple times. Please post the code for -restoreNextViewController. It's confusing why this code is as complex as it is. Have you read the section of the View Controller Guide on Creating a Nav Controller? They cover pushing view controllers on the stack at startup.
I agree with Rob, I would look at implementing UINavigationController when your application is loaded. Then you would push each of your controllers onto UINavigtionController in the sequence you want them layered.
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
So if you know what state you want to bring your application back to you could load the appropriate ViewController in the series to be latered and push them onto the NavigationController.
UIViewController *myViewController = [[UIViewController alloc] initWithNibName:#"myView" bundle:nil];
[navigationController pushViewController:myViewController animated:NO];
Then if you want to "pop" back to the UIViewController either the user can press the "Back" button on navigationbar for free. Or you can control the navigation with
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
eg [[self navigationController] popViewControllerAnimated:NO];
You can also control whether you want the NavigationBar to show or not if you want to control the push/pop yourself. I hope I've explained this well enough I've just traveled this road recently myself.
I ended up manually resetting each table view's content inset during its controller's viewWillAppear:.

Is parentViewController always a Navigation controller?

I was kind of scratching my head at this a week ago, and now with a little bit more Cocoa experience under my belt I feel like I have an inkling as to what might be going on.
I'm making an application that is driven by a UINavigationController. In the AppDelegate, I create an instance of this class, using "page 1" as the Root View Controller.
UINavigationController *aNavigationController = [[UINavigationController alloc]
initWithRootViewController:page1ViewController];
Now here's where I'm having the problem. From "page 1" I'd like to use a modal view controller that slides over the interface and then disappears once the user has made an edit. I do that using code like this, inside of Page1ViewController:
[self presentModalViewController:myModalViewController animated:YES];
When the Modal View Controller is gone, I want a value on "Page 1" to change based on what the user entered in the Modal View Controller. So, I wrote some code like this, which resides in the Modal View Controller:
[self.parentViewController dismissModalViewControllerAnimated:YES];
[self.parentViewController doSomethingPleaseWithSomeData:someData];
The update to page 1 wasn't happening, and it took me a long time to realize that the "doSomethingPleaseWithSomeData" message was not being sent to Page1ViewController, but the Navigation Controller.
Is this always to be expected when using Navigation Controllers? Did I perhaps configure something improperly? Is there an easy way to get at the View Controller that I want (in this case, Page1ViewController).
I would recommend using the delegation pattern to solve your problem. Create a property
#property (nonatomic, assign) id <MyModalViewDelegate> delegate;
And a corresponding protocol
#protocol MyModalViewDelegate
#optional
- (void)myModalViewControllerDidFinish:(MyModalViewController *)aModalViewController;
#end
When the user finishes with your view (e.g. taps the save button), send this message:
if ([self.delegate respondsToSelector:#selector(myModalViewControllerDidFinish:)])
[self.delegate myModalViewControllerDidFinish:self];
Now, set the delegate to the view controller that should manage the whole thing, and it will be notified when the view controller is finished. Note that you'll need your view controller to dismiss the modal view controller. But, logically, that makes sense, since it was the object that presented the modal view controller in the first place.
This is how Apple solves this problem in, for example, the UIImagePickerController and UIPersonPickerController.
There are a couple of ways you can handle this. The simplest is probably just to add a UIViewController property into myModalViewController and set it to page1Controller before you present it:
myModalViewController.logicalParent = self; //page1Controller
[self presentModalViewController:myModalViewController animated:YES];
Just make sure you add the appropriate instance variable #property, and #synthesize for logicalParent to myModalViewController, then you will have a way to communicate data back to the ViewController that triggered the modal dialog. This is also for passing data back and forth between different levels of navigation before you push and pop them on the stack.
The one important thing to worry about when doing this is that it is easy to get retain loops if you are not careful. Depending on exactly how you structure this you might need to use assign properties.
I just ran into this same problem. It definitely seems that if you put a UIViewController embedded in a NavigationController, then when, from that UIViewController you present another UIViewController modally, the presentee thinks that the presenter is the NavigationController. In other words, parentViewController is incorrect.
I bet this is a bug: either that, or the documentation seems incomplete. I will inquire.
Just ran into the same problem. I believe this is a bug. My scenario is the following:
A navigation hierarchy with A, B and C view controllers in this order. On C there's a button that would open a modal view controller called D. Once D is presented the navigation controller drops C from its hierarchy which is a terrible behavior. Once D gets dismissed, the navigation controller instantiates a new C type view controller and pushes it into its hierarchy to recover the original one. Terrible. My solution is hacking the navigation hierarchy this way (a very bad solution but works well. with a 2 dimension array you could implement stacking modals):
- (void)presentModalViewController:(UIViewController *)c {
[self.navigationHierarchy removeAllObjects];
[self.navigationHierarchy addObjectsFromArray:[navigation viewControllers]];
[navigation setViewControllers:[NSArray array] animated:YES];
[navigation presentModalViewController:c animated:YES];
}
- (void)dismissModalViewController {
[navigation dismissModalViewControllerAnimated:YES];
[navigation setViewControllers:[NSArray arrayWithArray:self.navigationHierarchy] animated:YES];
}
These two methods are defined where I maintain the main navigation hiererchy: the app delegate. navigation and navigationhierarchy are defined this way:
NSMutableArray *navigationHierarchy;
UINavigationController *navigation;