UIViewController to know if it got pushed or popped? - objective-c

I have a main UITableView, when cell is pressed it goes to another UITableView and when a cell is pressed there it goes to a DetailView of that cell.
I want the middle UITableView to behave differently depending on if the detailView got popped or the UITableView itself got pushed. If the view got pushed on from the main table I want to scroll to the top, if it is shown after a DetailView got popped I want it to stay at the same position.
Any suggestions?

you could call a scrollToTop method on the DetailViewController after you have pushed it to the navigationController.
Something like that:
if (!detailViewController) {
detailViewController = [[DetailViewController alloc] initWithNibName:nil bundle:nil];
}
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController scrollToTop];
// or use the tableView directly:
// [detailViewController.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];

In your Middle View Controller, examine which view is next-to-display directly from the UINavigationController stack:
- (void)viewWillDisappear:(BOOL)animated
{
if ([self.navigationController.topViewController isEqual:(UITableViewController *)tvcDetailView]) {
// Detail view has been pushed onto the UINavigationController stack
}
else {
// Middle view has been popped from the UINavigationController stack
}
}

Create a BOOL #property on your middle UIViewController property called wasPushed or something similar, and when you initialise it from UIViewController 1, set the property on the new instance, push it onto the nav stack and you can then use your property in your middle view controller's loadView, viewDidLoad, viewWill/DidAppear methods.
As soon as you've used it, set it back to FALSE or NO (or whatever) and when you end up coming back to it due to popping off your 3rd view controller you'll have it as FALSE/NO in your loadView, viewDidLoad etc.. methods.

Related

Calling reloadData on same UITableView, but it seems like it's calling on a different one

This has been a headache for few hours now and I finally found out what is actually happening, but I don't know how to solve this issue.
I've got List.h with UITableView properly connected from storyboard:
#property (strong, nonatomic) IBOutlet UITableView *tableView;
Then there's List.m where I set delegates and datasource for my UITableView:
// Set tableview datasource and register class for cell reuse
self.tableView.dataSource = self;
[self.tableView registerClass:[TableViewCell class] forCellReuseIdentifier:#"cell"];
// Set tableview delegate
self.tableView.delegate = self;
// Set tableview cells style
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.backgroundColor = [UIColor colorWithWhite:0.1f alpha:1.0f];
// Set tableview frame
self.tableView.frame = CGRectMake(0.0, 35.0, self.tableView.frame.size.width, self.tableView.frame.size.height-35.0);
Then on NSNotification I'm trying to [self.tableView reloadData]:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doUpdateAppBefore:) name:UIApplicationWillEnterForegroundNotification object:nil];
Also I'm reloading data of tableView on every UIApplicationBecomeActive notification.
Before I close my app and open it up from background to find out if it's reloads the data, I'm moving to another UIViewController and going back, which causes that somehow identifier of my self.tableView changes. I'm checking it in:
NSLog(#"Calling reloadData on : %#");
and at the beginning it gives me:
Calling reloadData on <UITableView: 0x9b09400;....
but after I segue back from another UIViewController it gives me:
Calling reloadData on <UITableView: 0x9b4b000;
which causes that it doesn't actually reload the data after I open up the app from the background state.
I've been thinking... when I segue back from another viewcontroller, viewDidLoad fires again, is it possible that it somehow sets tableView.delegate again and changes something? Just thinking...
Thank you very much for your answers.
It sounds like you have a view controller, push a modal view on top of it, and then want to go back to the original view controller when you're done. So, you set up a modal segue in your storyboard, and then a second modal segue to go back. The problem is that your second modal segue doesn't return to the original view controller, but it creates a new instance of that view controller, and now your have the original view controller, the second view controller, and an unwanted third view controller. Instead of creating a segue, which creates the third view controller, you need to dismiss the second view controller, which then gets you back to your original view controller, and therefore also your original table view. So what you want to do is get rid of the second segue and replace it with an IBAction, put something like
- (IBAction)goBack:(id)sender;
in your .h file. Connect that to your button or whatever you're using to trigger the segue now. Then, in your implementation file, dismiss the modal view like so:
- (IBAction)goBack:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
That should get you what you want.
Alternately, and this is probably better, you can use an unwind segue as well. Go to List.h, and create this method:
- (IBAction)unwind:(UIStoryboardSegue *)segue;
Then just implement it, you can leave it blank for now. Then, drag from the button that currently triggers the segue to Exit, and select the Action Segue unwind. That will also get you back.

removeFromParentViewController doesnot update UINavigationbar

I want to remove all the viewcontrollers from UINavigationController. So I am using this code.
for (UIViewController* controller in navigationController.viewControllers) {
[controller removeFromParentViewController];
}
After that I create an new viewController and push it.
UIViewController* newVC=[[UIViewController alloc] init];
[navigationController pushViewController:newVC animated:YES];
Issue is all the viewcontrollers popout perfectly and adding newVC but on pushing newVC the navigationbar is getting a back button and title of newVC. On clicking back button it animates to the navigationbar of oldVC with title of oldVC that I have already removed in above loop;
removeFromParentViewController is a UIViewController method, so it's normal it has nothing to do with UINavigationBar
In the case of a UINavigationController the popViewControllerAnimated: method handles the removeFromParentViewControllerpart for you, along with navigation bar.
you can directly update the whole array of viewControllersof UINavigationController, calling `setViewControllers:animated:
see Replacing rootView in navigationController
[navigationController setViewControllers:[NSArray arrayWithObject:newVC]];

how to display various view controllers (each having its respective navigation controller) using storyboard ID

I'm working on my first app. Here's what I want to accomplish:
There will be a menu with several different options. For simplicity, assume this is comprised of UIButtons with IBAction outlets and the functionality exists to pull up the menu at any time.
Each menu button, when pressed, should display a different navigation controller's content. If the user brings up the menu and makes a different selection, the navigation controller in which he is currently operating should not be affected; the newly selected navigation chain is displayed on top of the old, and through the menu, the user can go back to the view where he left off on the previous navigation chain at any time.
visual illustration (click for higher resolution):
Please note that there are 3 different navigation controllers/chains. The root view controller (which is also the menu in this simplified version) is not part of any of them. It will not suffice to instantiate one of the navigation chains anew when it has been previously instantiated, and here's why: if the user was on screen 3 of option 2 and then selects option 1 from the menu and then selects option 2 (again) from the menu, he should be looking at screen 3 of option 2--right where he left off; the view controller he was viewing when he previously left the navigation chain should be brought back to the top.
I can make a button instantiate and present a view controller from the storyboard if there is NOT a navigation controller:
- (IBAction)buttonPressed:(id)sender {
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
[self presentViewController:controller animated:YES completion:nil];
}
However, I can't figure out how to make those two methods work with a navigation controller involved. Moreover, I'm not sure those two methods are the right choice, because I won't always want to instantiate a new view controller: when a menu button is pressed, a check should be performed to see if the view (navigation?) controller with the corresponding identifier has already been instantiated. If so, it should simply be made the top view controller.
In summary, here are my questions:
1) How should I instantiate and display a view controller that is embedded in a navigation controller, preferably using a storyboard ID? Do you use the storyboard ID of the navigation controller or of the view controller?
2) How should I check whether an instance already exists? Again, should I check for an extant navigation controller or for a view controller, and what's the best method to do so?
3) If the selected navigation chain has already been instantiated and is in the stack of view controllers somewhere, what is the best method for bringing it to the top?
Thank you!!
side note -- it would be nice to know how to paste code snippets with indentation and color formatting preserved :)
As Rob has suggested, a tab bar controller would make a good organising principle for your design.
Add a UITabBarController to your storyboard, give it a storyboard iD. Assign each of your three sets of viewControllers ( with their respective navController) to a tab item in the tabBarController.
UITabBarController
|--> UINavigationController --> VC1 ---> VC2 -->
|--> UINavigationController --> VC1 ---> VC2 -->
|--> UINavigationController --> VC1 ---> VC2 -->
In you app delegate make a strong property to hold your tab bar controller's pointer. As the tab bar controller keeps pointers to all of it's tab items, this will take care of state for each of your sets of viewControllers. You won't have to keep separate pointers for any of them, and you can get references to them via the tabBarController's viewControllers property.
#property (strong, nonatomic) UITabBarController* tabVC;
Initialise it on startup
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard storyBoard =
[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
self.tabVC = [storyBoard instantiateViewControllerWithIdentifier:#"tabVC"];
//hide the tab bar
for (UINavigationController* navController in self.tabVC.viewControllers)
[navController.viewControllers[0] setHidesBottomBarWhenPushed:YES];
return YES;
}
An alternative way to hide the tab bar is to check the "Hides bottom bar on push" box in the Attributes Inspector for each of the (initial) viewControllers. You don't have to do this for subsequent viewControllers, just the first one that will be seen in that tab item.
Then when you need to navigate to one of your navController groups…
- (IBAction)openTab:(UIButton*)sender {
AppDelegate* appDelegate =
(AppDelegate*)[[UIApplication sharedApplication] delegate];
if ([sender.titleLabel.text isEqualToString: #"Option 1"]) {
appDelegate.tabVC.selectedIndex = 0;
}else if ([sender.titleLabel.text isEqualToString: #"Option 2"]){
appDelegate.tabVC.selectedIndex = 1;
}else if ([sender.titleLabel.text isEqualToString: #"Option 3"]){
appDelegate.tabVC.selectedIndex = 2;
}
[self presentViewController:appDelegate.tabVC
animated:YES completion:nil];
}
(this example uses presentViewController, your app design may navigate this in other ways…)
update
If you want to do this without a tab bar controller, you can instantiate an array holding pointers to each of your nav controllers instead:
UINavigationController* ncA =
[storyboard instantiateViewControllerWithIdentifier:#"NCA"];
UINavigationController* ncB =
[storyboard instantiateViewControllerWithIdentifier:#"NCB"];
UINavigationController* ncC =
[storyboard instantiateViewControllerWithIdentifier:#"NCC"];
self.ncArray = #[ncA,ncB,ncC];
Which has the benefit of not having a tab bar to hide…
Then your selection looks like…
- (IBAction)openNav:(UIButton*)sender {
AppDelegate* appDelegate =
(AppDelegate*)[[UIApplication sharedApplication] delegate];
int idx = 0;
if ([sender.titleLabel.text isEqualToString: #"option 1"]) {
idx = 0;
}else if ([sender.titleLabel.text isEqualToString: #"option 2"]){
idx = 1;
}else if ([sender.titleLabel.text isEqualToString: #"option 3"]){
idx = 2;
}
[self presentViewController:appDelegate.ncArray[idx]
animated:YES completion:nil];
}
1 / You can instantiate a viewController in your viewDidLoad method of your main viewController, so it will be instantiate 1 time only.
Now if you want display your controller, you would better push it :
- (IBAction)buttonPressed:(id)sender {
// Declare your controller in your .h file and do :
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
// Note you can move this line in the viewDidLoad method to be called only 1 time
// Then do not use :
// [self presentViewController:controller animated:YES completion:nil];
// Better to use :
[self.navigationController pushViewController:controller animated:YES];
}
2 / I'm not sure, but if you want to check if an instance already exist just check :
if (controller) {
// Some stuff here
} // I think this checks if controller is initiated.
3 / I know it's not a good advice but I would tell you to not worry about checking if your controller already exist, because I think it's easier to access your viewController by using the 2 lines again :
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
[self.navigationController pushViewController:controller animated:YES];
4 / I'm not sure if colors can be used here because of a specific style sheets.
I'm not sure to really have the good answer to your question but I hope this will help you.

I lose access to my IBOutlets when making view controller new detail view controller

I have an iPad master-detail where the main detail controller is a navigation controller and depending on the table row (in the master view controller) selected, I may or may not replace the view controller managed by the detail navigation controller.
This seems to work fine, except that I lose access to the new detail view controller's IBOutlets when this move is made.
Here's me switching the navigation controller's view controller:
CustomerDetailViewController *subview = [[CustomerDetailViewController alloc] initWithNibName:#"CustomerDetailViewController" bundle:nil];
[subview setTitle:#"Testing"];
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
app.splitViewController.delegate = subview;
[app.detailNavigationController setViewControllers:[NSArray arrayWithObjects:subview, nil]];
[subview setData:[custData objectForKey:#"name"]];
custData is an NSDictionary containing my view information. Here is the setData command:
- (void)setData:(NSDictionary *)cust {
NSLog(#"%#\n", [cust valueForKey:#"name"]);
self.nameLabel.text = [cust valueForKey:#"name"];
NSLog(#"%#\n", self.nameLabel.text);
}
So what happens is, subview becomes the new view controller but the label does not get changed - however, those two log commands are executed. The label is synthesized and wired up using IB and works if I push subview as a new view controller instead of replace it.
I'd say, the view is not yet initialized. Outlets are first connected in the viewDidLoad method. Try putting a log statement in the viewDidLoad to find out, which one gets called first.
If the viewDidLoad is called after your setData method, you can only set a local variable of the CustomerDetailViewController which is then read by viewDidLoad which sets the label accordingly.

Superview is Nil after Second Launch of View Controller

I've spent hours and I cant figure this out. I have a detail view controller (UITableView) which is launched here:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EventLocationDetailController *newDetailViewController = [[EventLocationDetailController alloc] initWithNibName:#"EventLocationDetailController" bundle:nil];
self.eventDetailController = newDetailViewController;
[self.navigationController pushViewController:self.eventDetailController animated:YES];
[newDetailViewController release];
}
In the detail view controller there is a button method which calls the below method to display a slide-in-slide-out animation confirming the users choice:
-(void)confirmLastActionWithMessage:(NSString *)message {
ConfirmActionViewController *newConfirmActionViewController = [[ConfirmActionViewController alloc] initWithNibName:#"ConfirmActionViewController" bundle:nil];
self.confirmActionViewController = newConfirmActionViewController;
[newConfirmActionViewController release];
[[self.view superview] addSubview:self.confirmActionViewController.view];
}
Protocol method called by the ConfirmActionViewController indicating that the animation is finished.
-(void)didFinishConfirmDisplay:(UIView *)viewToRemoveFromSuperview {
[viewToRemoveFromSuperview removeFromSuperview];
}
This works perfect the first time I press the button. If I pop the detail controller and push it back on to the stack and press the button again, nothing happens and the detail controller's superview is nil every time I invoke the method after that. Superview is not nil in the viewWillAppear method for the detail view, only when It gets to the confirmLastActionWithMessage method. No other user interaction happens in between. How do I get the superview back? I have a similar code that works without animation.
I've also noticed that the detail view controller hasn't called dealloc when popped off the stack. Not sure where the problem is.
Thanks.
EDIT 1
OK. I replaced the addSubview line with this one:
[self.view insertSubview:self.confirmActionViewController.view atIndex:0];
and the animation view appeared underneath one of the table cells. Could one of the table cells steal the superview?
Well I don't really understand why you should add the subview to the superview. Why not add it just to self.view
I may not be able to explain why there is no superview but try either adding the controller view to self.view or
[[[[UIApplication sharedApplication] delegate] window] addSubview:yourview];
This will render the view on top of everything.