Keeping values between view controllers
I have an ItemAddViewController, which presents itself as a modal view. One of the fields pushes a new view controller, CategorySelectionViewController, which allows the user to select a single category.
ItemAddViewController.h
@property (nonatomic, retain) Category *category;
CategorySelectionViewController.h
@property (nonatomic, retain) Category *category;
CategorySelectionViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *currentCategory = category;
if (currentCategory != nil) {
NSInteger index = [categories indexOfObject:currentCategory];
NSIndexPath *selectionIndexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *checkedCell = [tableView cellForRowAtIndexPath:selectionIndexPath];
checkedCell.accessoryType = UITableViewCellAccessoryNone;
}
//set the checkmark accessory
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
//update the category
category =[categories objectAtIndex:indexPath.row];
NSLog(@"%@", category);
// Deselect row
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
ItemAddViewController.m
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"%@", category);
}
Category is set on CategorySelectionViewController creation. When category is selected on the category selection 开发者_运维百科screen, NSLog reports the correct object. When it gets back to ItemAddViewController, it's null again. The two should be the same object, so I'm not sure what I'm doing wrong.
Basically, I need a good method to pass data between two view controllers.
To follow up on what's already been said, one approach commonly taken in similar problems is to make the ItemViewController
(parent) the delegate of CategorySelectionViewController
(child), and when tableView:didSelectRowAtIndexPath:
fires in the CategorySelectionViewController
, send a message to the delegate callback in ItemAddViewController
- passing in the selected category as a parameter.
This concept could be implemented similar to the following:
@protocol CategorySelectionViewControllerDelegate;
// in CategorySelectionViewController.h
@interface CategorySelectionViewController : UITableViewController {
id<CategorySelectionViewControllerDelegate> delegate;
}
@property (nonatomic, assign) id<CategorySelectionViewControllerDelegate> delegate;
@end
@protocol CategorySelectionViewControllerDelegate
// delegate callback skeleton
-(void)userDidSelectCategory:(Category *)categorySelected;
@end
// in CategorySelectionViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *currentCategory = category;
if (currentCategory != nil) {
NSInteger index = [categories indexOfObject:currentCategory];
NSIndexPath *selectionIndexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *checkedCell = [tableView cellForRowAtIndexPath:selectionIndexPath];
checkedCell.accessoryType = UITableViewCellAccessoryNone;
}
//set the checkmark accessory
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
// here's where you message the delegate callback
[self.delegate userDidSelectCategory:[categories objectAtIndex:indexPath.row]];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
ItemAddViewController's skeleton would then be modified to conform to the CategorySelectionViewControllerDelegate
protocol:
// in ItemAddViewController.h
@protocol CategorySelectionViewControllerDelegate;
@interface ItemAddViewController : UITableViewController <CategorySelectionViewControllerDelegate>
{ /* etc.... */ }
@property (nonatomic, retain) Category *category;
// delegate callback
-(void)userDidSelectCategory:(Category *)categorySelected
// in ItemAddViewController.m
// set the CategorySelectionViewController delegate as this ItemViewController when you instantiate it
-(void)showCategorySelectionViewController {
CategorySelectionViewController *myChild = [[CategorySelectionViewController alloc] init];
myChild.delegate = self;
[self presentModalViewController:myChild animated:YES];
}
// implement the delegate callback
-(void)userDidSelectCateogry:(Category *)categorySelected {
self.category = categorySelected;
// other handling code as needed...
}
In regard to doing this by calling [self parentViewController]
in CategorySelectionViewController
, the catch is that ItemAddViewController
inherits from UITableView
, so when you send the message [self parentViewController]
, the compiler thinks you're talking to a UITableView
, not an ItemAddViewController
, unless you cast it explicitly. Therefore, it does not know that self.parentViewController
has a property called category
. You can fix this by adding the type cast:
ItemAddViewController *itemAddViewControllerParent = (ItemAddViewController *)[self parentViewController];
itemAddViewControllerParent.category = [categories objectAtIndex:indexPath.row];
Hope this helps.
The parentViewController
method of the UIViewController
class should give you a pointer to the view controller that's "managing" the current one. Once you've got that, you can set the category
property on it.
That said, I haven't done much with view controllers on iOS yet myself, so I'm not sure what the semantics of "what parentViewController
should point to for a given view" is... but I'd venture that your ItemAddViewController
instance should probably be the parent for your CategorySelectionViewController
.
Here's an example of how you might do it:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *currentCategory = category;
if (currentCategory != nil) {
NSInteger index = [categories indexOfObject:currentCategory];
NSIndexPath *selectionIndexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *checkedCell = [tableView cellForRowAtIndexPath:selectionIndexPath];
checkedCell.accessoryType = UITableViewCellAccessoryNone;
}
//set the checkmark accessory
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
//update the category
[self parentViewController].category = [categories objectAtIndex:indexPath.row];
NSLog(@"%@", category);
// Deselect row
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
EDIT: The documentation says this for the parentViewController
method:
Parent view controllers are relevant in navigation, tab bar, and modal view controller hierarchies. In each of these hierarchies, the parent is the object responsible for displaying the current view controller.
I'd take this to mean that the parentViewController
for your modal view's controller points to whatever view controller received the message presentModalViewController:animated:
.
@David's is a good answer, but that would keep the data in the parentViewController
. If you want the data to be local to the ItemAddViewController (the child controller), then you can create a local iVar in the second view and assign a value to it before displaying it or pushing it onto the navigation controller. See my answer to a previous SO question here to see how it is done.
精彩评论