When creating a new view controller to be pushed onto the stack, what is the correct method to use to populate that view controller with data?
I have a data object that I need to send to the view controller, which will then set it's text fields, etc. with the data.
ItemDetailViewController_iPad *detailViewController = [[ItemDetailViewController_iPad alloc] initWithNibName:#"ItemDetailViewController_iPad" bundle:nil];
[detailViewController populateWithData:_data];
[self.navigationController pushViewController:detailViewController animated:YES];
in ItemDetailViewController_iPad:
-(void) populateWithData:(Item*)_data
{
self.data = _data;
self.navigationItem.title = self.data.title;
self.descriptionText.text = self.data.desc; //the text does not get updated - it's the default text from the nib file
NSLog(#"Desc: %#", self.data.desc); //this logs valid data
}
You can also declare a property in that VC and then set that property before pushing it. Then in the viewDidLoad method for the pushed VC, set the view's title and text field.
The code you've posted should work fine.
There are lots of ways to do this, and it often comes down to what you understand best, feel most comfortable with, and isn't too dull to type in.
Here are some other options.
Related
I have a reusable UIViewController class which has a tableview in it, say Class T. I have a list of things in this to be displayed.
Now when I press on one of the cells I create a new instance (alloc init) of this class and push it to the navigation stack and display new data in the same class T.
The problem comes when I pop the controller it goes to the first instance if the same class but the tableview displays the data which was displayed in the second instance.
I am using an XIB for Class T and not storyboard and segues.
Please help me resolve this issue.
Thanks,
[EDIT - I] Initializing
T *controller = [T alloc] initWithNibName:#"T" bundle:nil];
[self.navigationController pushViewController:controller animation:YES];
Your issue is clearly based on the missing update of the data used in the table view's datasource. I suggest that each controller gets its own object, array or fetched results controller. You can then do something like this:
T *controller = [T alloc] initWithNibName:#"T" bundle:nil];
controller.dataArray = ... // populate the data source
Alternatively, using the same datasource, you have to make sure to check what should be displayed. Maybe you can give your table view controller class a type property that gets checked when displaying the data cell.
if (self.type == HierarchyFirstLevel) {
cell.textLabel.text = ... // populate for first level
}
else if (self.type == HierarchySecondLevel) {
cell.textLabel.text = ... // something else
}
// etc.
I have a simple test project. A UITableViewController (MasterViewController) embedded inside a navigation controller in storyboard. I am NOT segueing using the prepareForSegue to pass data to another view controller (DetailViewController). Instead, didSelectRowAtIndexPath is use to update a label in the detailviewcontroller as below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
NSMutableString *object = thisArray[indexPath.row];
detailViewController.passedData = object;
[self.navigationController pushViewController:detailViewController animated:YES];
}
Everything till this point works fine.
Now i have added another view controller in my storyboard. Made it the initial view controller, added two containers in it, then embedded both MasterViewController and DetailViewContainer in these containers.
Now instead of showing passed data inside the DetailViewController on the right side, its showing the passed data on the left side by replacing the controller view.
If i am not able to clarify what i am trying to say, here is the link to the project https://jumpshare.com/v/UiTFEB6AamIo8qX9sinW , its just for learning purpose.
Thanks
You're getting this problem because you are still doing this:
[self.navigationController pushViewController:detailViewController animated:YES];
The navigation controller you're referencing here is the one your master controller is embedded in, so you create an instance (different than the one that's already on screen) of detailController and push that onto the navigation controller.
What you want to do, is get a reference to the detail controller that's already on screen -- both child view controllers (the ones in the container views) are already instantiated when the app starts. So, you need to do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewController *detailViewController = [self.navigationController.parentViewController childViewControllers][1];
NSMutableString *object = thisArray[indexPath.row];
detailViewController.passedData = object;
}
This will pass the value to the detail controller, but you can't have the code to update the label in viewDidLoad, since that view is already loaded, and won't be called again. Instead, override the setter for passedData, and update the label there (note that I changed the name of the argument to passedInData, so it doesn't conflict with your property passedData):
-(void)setPassedData:(NSString *)passedInData {
passedData = passedInData;
detailDescriptionLabel.text = passedData;
}
Bu the way, unless you're planning on adding other controllers after your master view controller, there's no reason to have it embedded in a navigation controller at all, given this set up. If you take it out, then you need to remove the reference to self.navigationController when you get the reference to the detail controller. It would then just be:
DetailViewController *detailViewController = [self.parentViewController childViewControllers][1];
I am using Storyboard in my app and I want to pass data from one view to another view.
Instead of using segues I am using instantiateViewControllerWithIdentifier. In this case I am instantiate from my first TableViewController to a NavigationController which has a second TableViewController attached because I need the navigation in the second TableViewController. Now I want to pass data from my first TableviewController, depending which row was clicked, to my second TableviewController. In this case newTopViewController would be my NavigationController but my problem is now how to pass data from firstTableViewController to the secondTableviewController.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [NSString stringWithFormat:#"%#Top", [menuArray objectAtIndex:indexPath.row]];
UIViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
}
If you instantiate a navigationController, you can use the viewControllers property to get the inner viewController of the navigation controller.
Something like this:
UINavigationController *navigationController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
MBFancyViewController *viewController = navigationController.viewControllers[0];
// setup "inner" view controller
viewController.foo = bar;
[self presentViewController:navigationController animated:YES completion:nil];
newTopViewController.anyVariableToShow= anyVariableToSend;
I do this pretty often on a few of my apps...
//Create new VC
CookViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CookVC"];
//Set recipe
[detailViewController setRecipe:recipe];
//Pop over VC (can be pushed with a nav controller)
[self presentPopupViewController:detailViewController animationType:MJPopupViewAnimationFade];
If you aren't using a navigation controller or segues, then I think you need to reconsider your app design.
Actually it's not just a data pass problem as this is a program control and data transfer question together.
Even you would have to rethink about your app's concept, as you'd like to use storyboard without the meaning of storyboard, it's up to you and I hope you have good reason to do what you do.
So when you decided not to use segue you lost the new and comfortable way of instantiating a new controller and transferring data with it and you have to do the transfer of control and the data in two distinct steps. When you instantiate another scene in storyboard (like you do with instantiateViewControllerWithIdentifier:) you just instantiated a new controller and transferred the control but not the data. Just think about it as you instantiated a new controller from a xib in an old way (so you have to use initWithCoder: or awakeFromNib in the second view controller as the storyboard will not call initWithName:bundle:), but did not do anything more.
So you will have a new controller (it named in the identity part of the second storyboard) which is hanging in the universe without any relationship or connection with anything else (as the storyboard picture illustrates it nicely) and you could do with it what you'd like.
So you'd like to do something and you need data from the previous storyboard (ViewController). What you need is making available those data to the second storyboard(ViewController), and as you know there are lot of solution for this which were available long time before even storyboard is existed.
So regarding your code, the "data transfer" is depending on your design, whether the two controllers are subclasses of each other or whatsoever...
If you don't like to deal with subclassing and like to decoupling them as much as possible, the best way just make a property of your data in the first controller and refer to them from the second (after importing the first's .h file) and just refer to it in it's viewDidLoad or in initWithCoder: or anywhere where you need them, as
secondViewControllerdata = firstViewControllerdata.thatDataProperty
Of course you can do the same in reverse and make a property of the second controller and refer to it in your first view controller.
You can define some parameter in UIViewController to receive data:
#property (assign) int param1;
#property (retain) NSMutableArray *param2;
and use below to pass the data:
[newTopViewController setParam1:XX];
[newTopViewController setParam2:XX];
I'm seem unable to get any kind of communication going between my Main View Controller and a Table View Controller which is being displayed inside a Popover View (iPad).
I'm setting up the Table View inside a Navigation Controller in the usual way:
// create popover
if (self.popoverController == nil) {
filesViewController = [[[MyTableViewController alloc] initWithFiles:fileList] autorelease];
UINavigationController *navCtrl = [[[UINavigationController alloc] initWithRootViewController:filesViewController] autorelease];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:navCtrl];
self.popoverController.delegate = self;
// resize popover
self.popoverController.popoverContentSize = CGSizeMake(320.0, 44 + [fileList count] * 44);
}
Everything is working fine, and I'm passing an array of file names (fileList) into the Table View, which is held in the Table View as an array called listOfFiles. The Table View displays the filenames, and when one is selected by the user I want to pass that filename back to the Main View Controller. However, I cannot get any communication going back from the Table View's didSelectRowAtIndexPath method to the Main VC. I've tried all sorts of outlets going in various directions, and I've tried creating a new object in didSelectRowAtIndexPath to handle the filename coming from the Table View. I can pass the filename out to the new object, but when I try to send that into the Main VC it is null again. Everything I send to my Main VC while that popover is active comes up as null.
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
NSLog(#"%#", handler.addressForImageFile);
self.popoverController = nil;
[self.popoverController release];
}
Is there some reason why my Main VC won't get anything but null objects from my Table View? I've spent days trying so many different things. I feel like there's some fundamental gap in my knowledge of how popovers work. Surely there is a simple way to send a string back to my Main VC when it is selected from the Table View?
Thanks so much for any help!
There's propably a much better way to do this, but depending on the goal of passing the string, one way could be to use NSUserDefaults.
I am using my app delegate to transition between view controllers. When the delegate decides it no longer needs the view controller, based on messages from the server, it needs to remove the current view and replace it with another one. Currently my code looks like the following:
- (void) showFight: (NSNotification*) notification
{
if(self.window.rootViewController != self.fightViewController)
{
NSDictionary* dict = [notification userInfo];
FightViewController *fightView = [[FightViewController alloc]
initWithNibName:#"FightViewController" bundle:nil];
fightView.userId = _userId;
self.fightViewController = fightView;
[fightView release];
[self.radarViewController.view removeFromSuperview]; // Doesn't work.
self.window.rootViewController = self.fightViewController;
[self.fightViewController showMonster:dict];
}
}
I know my view controller isn't being removed because I can hear sound effects from it in the background.
I want to completely destroy the view controller, as I only want one view controller in memory at any time. I plan to create the view controller each time from scratch, as shown in the code above. Am I doing this improperly?
The problem here seems to be that you are not releasing the view controller. Think about what actually happens in your code at:
[self.radarViewController.view removeFromSuperview];
You remove the view from its super view. The view controller still exists, and what it does is control what should be shown on the view, and in your case apparently playing sound.
Put in an easy way: The view controller is an object. It has a child, the view. That's another object. You remove the view from another view, but the object controlling the removed view still lives (and actually, so does the view object).
If you want to kill the entire view controller, call this after removing the view from its superview:
[self.radarViewController release];
Or, if the view is a retain property (which i assume by looking at your code) you can also use:
self.radarViewContoller = nil;
which automatically releases for you in the synthesized setter.
When the view controller is released, its reference count is subtracted by one. If the reference count reaches zero, then the controller will be deallocated.
So far I understand your problem is to add the new ViewController on server notify and change the current view with new View. First of all you've to add the view controller just like below because the reference won't help to update the view.
[self.window.rootViewController.view addSubview: self.fightViewController.view]
In my opinion you need to tag your Controllers and check before adding the controller that if it's already exist in the memory, otherwise the pool of object will leak. Just Say No to Memory Leaks!!
- (void) showFight: (NSNotification*) notification
{
UIView *fightView = (UIView *)[self.window.rootViewController.view viewWithTag: FIGHT_VIEW_TAG];
if (self.window.rootViewController.view.tag != fightView.tag) {
NSDictionary* dict = [notification userInfo];
FightViewController *fightView = [[FightViewController alloc]
initWithNibName:#"FightViewController" bundle:[NSBundle mainBundle]];
//Remove the current view.
[self.window.rootViewController.view removeFromSuperview]; // If you're adding the fighting View in the root View, then why are you trying to remove current view through radar controller which has already added in the window (root view).
fightView.userId = _userId;
[fightView setTag: FIGHT_VIEW_TAG];
[self.window.rootViewController.view addSubView: self.fightViewController.view];
[self.fightViewController showMonster:dict];
[fightView release];
}
}
You don't need to take them as global until your requirements are different.