Sending data from my detail view back to my UITableView
First of all, thank God for Stack Overflow. I am new at Objective-C/Cocoa/iPhone development, and this site is an amazing aid.
I have a window, that has a tab bar, that has a navigation controller, that loads a UITableViewController. Clicking an "Add" button on the navigation bar (created programatically), pushes a UITableViewController like so:
InvoiceAddViewController *addController = [[InvoiceAddViewController alloc] initWithNibName:@"InvoiceAddViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:addController animated:YES];
This table view controller pushes it's own detail view:
UITableViewCell *targetCell = [self.tableView cellForRowAtIndexPath:indexPath];
GenericTextFieldDetailViewController *dvController = [[GenericTextFieldDetailViewController alloc] initWithNibName:@"GenericTextFieldDetailViewController" bundle:[NSBundle mainBundle]];
dvController.fieldName = targetCell.textLabel.text;
dvController.fieldValue = targetCell.detailTextLabel.text;
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
The concept being, you click on a cell in the table view controller such as "Notes". This pushes "GenericTextFieldDetailViewController" with the name of the "field" you clicked, and the value (if one already exists). This allows me to reuse my detail view rather than creating one ad nauseum for every field.
In order to push data back, I created a method on the "Add" UITableViewController:
- (void) updateField:(NSString*) fieldName value:(NSString*) fieldValue
{
UITableViewCell *targetCell;
if([fieldName isEqualToString:@"Invoice ID"])
{
NSUInteger indexArr[] = {1,1};
targetCell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
targetCell.detailTextLabel.text = fieldValue;
}
else if([fieldName isEqualToString:@"P.O. Number"])
{
NSUInteger indexArr[] = {1,2};
targetCell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
targetCell.detailTextLabel.text = fieldValue;
}
else if([fieldName isEqualToString:@"Add Note"])
{
NSUInteger indexArr[] = {3,0};
targetCell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
targetCell.detailTextLabel.text = fieldValue;
}
}
This method is designed to receive the data I push with this method in "Generic":
- (IBAction)saveField:(id)sender
{
self.fiel开发者_运维技巧dValue = theTextField.text;
InvoiceAddViewController *parentController = (InvoiceAddViewController*)self.view.superview;
[parentController updateField:self.fieldName value:self.fieldValue];
}
Which brings us to the problem: When the save method fires off, it throws an invalid selector error because self.view.superview is not the UITableView that pushed the "Generic" detail view.
I have tried the following combinations (from GDB):
(gdb) po [[self view] superview]
<UIViewControllerWrapperView: 0x4b6d4d0; frame = (0 64; 320 367); autoresize = W+H; layer = <CALayer: 0x4b6c090>>
(gdb) po [[self navigationController] parentViewController]
<UITabBarController: 0x4d2fa90>
(gdb) po [self parentViewController]
<UINavigationController: 0x4d2fdc0>
I feel like I'm landing all around the UITableView I want to invoke, but can't find it.
What am I doing wrong?
refrains from pulling more hair out
The problem is that you are confusing the view hierarchy for the navigation stack. Your detail view controller wants to send a message to the controller that pushed it on the stack, which is the second to last object in the navigation controller's viewControllers array.
Try changing your saveField: method to:
- (IBAction)saveField:(id)sender
{
self.fieldValue = theTextField.text;
NSArray *navigationStack = self.navigationController.viewControllers;
InvoiceAddViewController *parentController = (InvoiceAddViewController*)[navigationStack objectAtIndex:navigationSTack.count - 2];
[parentController updateField:self.fieldName value:self.fieldValue];
}
Edit: I should note this design is very brittle. A better way is to apply Model-View-Controller and create an object that represents a field and title value. Then your InvoiceAddViewController can pass instances of these objects to your detail controller, and as your detail controller changes them, these changes can be easily reflected in your other controllers.
Edit 2: Here is a hint of how it will work.
UITableViewCell *targetCell = [self.tableView cellForRowAtIndexPath:indexPath];
GenericTextFieldDetailViewController *dvController = [[GenericTextFieldDetailViewController alloc] initWithNibName:@"GenericTextFieldDetailViewController" bundle:[NSBundle mainBundle]];
dvController.dataObject = [self dataObjectForIndexPath:indexPath];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
I'm assuming you've implemented a dataObjectForIndexPath: method. Presumably tableView:cellForRowAtIndexPath: would also use this method to configure its cells.
Now, you can eliminate both the saveField: and updateField: methods. In your InvoiceAddViewController, viewWillAppear: could be used to refresh your view like this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData]
}
There is a whole world of possibilities here. reloadData is a very heavy handed one. Experiment with stuff like KVO to make this more automatic.
In your detail controller, of course, don't forget to update the object, say in view will disappear to do something like
self.dataObject.fieldValue = theTextField.text;
This is just to get you started. There are a lot of possibilities and details to consider. You should really look at a lot of sample code, this pattern gets used a lot. The CoreDataBooks example on the developer portal uses a similar pattern, there are almost certainly others.
You should be using notification, KVO or delegation. Use the design patterns that fit passing data around on this platform. Even if you can make the call directly using one of these patterns will most certainly prove to be a better way of achieving this in an encapsulated manner.
This approach is just screaming for delegation. Create a protocol on your detail view controller, declare a method like
- (void)genericTextFieldDetailViewController:(GenericTextFieldDetailViewController *)controller didUpdateValue:(NSString *)value forField:(NSString *)field
Now when you're ready to send the data back you would just send this message to your delegate (the pushing view controller).
[self.delegate genericTextFieldDetailViewController:self didUpdateValue:newValue forField:passedInField]
Make sure that when you create and push the detail view controller, you assign yourself as it's delegate and implement that method to handle the incoming values. I think you'll find this approach way more flexible.
I had the same problem, but not with a table view. I wanted to change a value from the last view controller. I'm using Xcode 5:
{
NSArray *navigationStack = self.navigationController.viewControllers;
ViewController *control = [navigationStack objectAtIndex:([navigationStack count] -2)];
control.pagecont.currentPage = self.currIndex;
CGRect frame;
frame.origin.x = control.scroll.frame.size.width * control.pagecont.currentPage;
frame.origin.y = 50;
frame.size = control.scroll.frame.size;
[control.scroll scrollRectToVisible:frame animated:YES];
[self.navigationController popViewControllerAnimated:YES];
}
精彩评论