dequeueReusableCellWithIdentifier returns wrong cell - iphone

I have a UITableView that uses custom cells, loaded from nibs, and hooked up to a controller (using initWithNibName:bundle:). Basically, I've noticed that dequeueReusableCellWithIdentifier returns a cell that has already been loaded, but when it shouldn't.
Here are my delegate methods:
- (UITableViewCell *) tableView: (UITableView *) tv cellForRowAtIndexPath: (NSIndexPath *) indexPath {
return [[self controllerForTableView: tv cellForRowAtIndexPath: indexPath] cell];
}
- (TableViewCellBaseController *) controllerForTableView: (UITableView *) tv cellForRowAtIndexPath: (NSIndexPath *) indexPath {
[self checkValidTableView: tv];
UIViewController *controller;
Class class;
Event *event;
int row = [indexPath row];
DLOG(#"at index path row: %i", row);
if (row == [currentEvents count]) {
controller = [self tableCellContainerFromNibName: nibName
tableView: tv
atIndex: row
withClass: class];
} else {
class = [EventFeaturedTableViewCell class]; // TODO: OR class = [EventNonFeaturedTableViewCell class];
event = [self findEventFromIndex: row];
lastSelectedEvent = event;
DLOG(#"event: %#", event);
controller = [self tableCellContainerFromNibName: NSStringFromClass(class)
tableView: tv
atIndex: row
withClass: class
perform: #selector(initTableCellWithController:)
on: self];
}
return controller;
}
- (TableViewCellBaseController *) tableCellContainerFromNibName: (NSString *) nibName
tableView: (UITableView *) tableView
atIndex: (int) index
withClass: (Class) class
perform: (SEL) selector
on: obj {
CustomTableViewCell *cell = (CustomTableViewCell *) [tableView dequeueReusableCellWithIdentifier: nibName];
TableViewCellBaseController *controller;
DLOG(#"Cell: %#", cell);
DLOG(#"Cell Identifier: %#", nibName);
if (cell == nil) {
controller = [[class alloc] initWithNibName: nibName bundle: nil];
if (obj) {
[obj performSelector: selector withObject: controller];
}
// run this after setting data as controller.view eager loads the view
cell = controller.cell = controller.view;
cell.controller = controller;
} else {
controller = cell.controller;
if (obj) {
[obj performSelector: selector withObject: controller];
}
}
return controller;
}
- (void) initTableCellWithController: (EventsIndexTableViewCell *) controller {
controller.event = lastSelectedEvent;
}
The Custom Cells have been hooked up in IB to the controller using the controller's view property as well as a 'cell' property. The Custom Cells have a unique identifier set directly in the nib.
I've already verified that the data in the controller is correct.
Here's the effect I'm seeing in the simulator:
http://screencast.com/t/NI2Tpc7GKEi
Notice that the event 'Atlantic Bay' shows up first in the table, then later on in the table. When scrolling back up, it no longer shows up as the first entry in the table!

To continue viggio24's line, I agree that I don't see a place that you're properly reconfiguring the cell. When you -dequeue... you're going to get a cell that already has "stuff" in it. It's your job to reset everything visible.
You should also read carefully Loading Custom Table-View Cells From Nib Files. Typically you use -loadNibNamed:owner:options: for this, rather than -initWithNibName:bundle:.

Related

How do I retrieve UITableView row number of a UISwitch?

I have tried several approaches posted here, but I cannot get my table full of switches to return an index value for the cell of the changed switch. I am creating the view containing the table programmatically (no xib).
TableSandboxAppDelegate.m I instantiate the view controller in didFinishLaunchingWithOptions: with:
...
TableSandboxViewController *sandboxViewController = [[TableSandboxViewController alloc]
init];
[[self window] setRootViewController:sandboxViewController];
...
TableViewController.h file reads:
#interface TableSandboxViewController : UITableViewController
{
NSMutableArray *_questionOrder;
NSMutableArray *switchStates;
}
#end
TableViewController.m cellForRowAtIndexPath: reads:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
UISwitch *theSwitch = nil;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"MainCell"];
theSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
theSwitch.tag = 100;
[theSwitch addTarget:self action:#selector(switchChanged:)
forControlEvents:UIControlEventValueChanged];
[cell.contentView addSubview:theSwitch];
} else {
theSwitch = [cell.contentView viewWithTag:100];
}
if ([[switchStates objectAtIndex:indexPath.row] isEqualToString:#"ON"]) {
theSwitch.on = YES;
} else {
theSwitch.on = NO;
}
return cell;
TableViewController.m -(IBAction)switchChanged:(UISwitch *)sender reads:
UITableViewCell *theParentCell = [[sender superview] superview];
NSIndexPath *indexPathOfSwitch = [self.tableView indexPathForCell:theParentCell];
NSLog(#"Switch changed at index: %d", indexPathOfSwitch.row);
My log result is always "Switch changed at index: 0". I feel like the problem is in that CGPoint line where I've tried combinations of replacements for "sender" ([sender superview], [[sender superview]superview], etc). I don't feel like that line is pointing to the view that displays the table.
What am I doing wrong?
Note added 10/9, 9:15 EDT: my goal is to be able to handle about 100 yes/no questions in the table, so reuse is a key. I want to scroll and have the table the state of each switch, as well as be able to retrieve them when leaving the view.
Tags is an okay solution, but a little clumsy because the cells - and therefore their subviews - are continually being reused, changing their rows - and therefore the tags they need.
Instead, I generally keep one of these around:
- (NSIndexPath *)indexPathWithSubview:(UIView *)subview {
while (![subview isKindOfClass:[UITableViewCell self]] && subview) {
subview = subview.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)subview];
}
Then when I get an IBAction:
- (IBAction)someSubviewAction:(id)sender {
NSIndexPath *indexPath = [self indexPathWithSubview:(UIView *)sender];
// carry on from here
}
You may set switch view tag to row index. Instead of theSwitch.tag = 100;
do
-(UITableViewCell*)tableView:table cellForRowAtIndexPath:indexPth
{
UISwitch *theSwitch = nil;
if (cell == nil) {
...
// as per your example
[cell.contentView addSubview:theSwitch];
} else {
theSwitch = subviewWithClass(cell.contentView, [UISwitch class]);
}
theSwitch.tag = indexPath.row;
...
}
Add this helper function to replace viewWithTag: call
UIView *subviewWithClass(UIView *contentview, Class klass)
{
for (UIView *view in contentview.subviews)
if ([view isKindOfClass:klass])
return view;
return nil;
}
Then retrieve tag, that is a row index now, in your switchChanged function
-(IBAction)switchChanged:(UISwitch *)sender {
NSLog(#"Selected Switch - %d", sender.tag);
...
}
If you use something block-based (like https://github.com/brightsoftdev/iOS-Block-Based-Bindings/blob/master/UISwitch%2BBindings.m), you don't need to worry about getting the row, because you can reference the indexPath that is passed into tableView:cellForRowAtIndexPath: in your block.
Similar to #danh, I've come up with this solution using an extention which I've used multiple times.
#interface UIView (Find)
- (id)findSuperviewOfClass:(Class)class;
- (NSIndexPath *)findIndexPath;
#end
#implementation UIView (Find)
- (id)findSuperviewOfClass:(Class)class
{
return [self isKindOfClass:class] ? self : [self.superview findSuperviewOfClass:class];
}
- (NSIndexPath *)findIndexPath
{
UITableView *tableView = [self findSuperviewOfClass:[UITableView class]];
return [tableView indexPathForCell:[self findSuperviewOfClass:[UITableViewCell class]]];
}
#end
for iOS6+ you could maintain a NSMutableArray queuedSwitches
in -tableView:cellForrowAtIndexPath: you would take a switch, if not empty and places it on the custom cell and assign it to a property. If empty you create a new one.
in -tableView:didEndDisplayingCell:forRowAtIndexPath: you would add it to quededSwitches and remove it from it cell.
This will just allocate enough switches for visible cells and reuse them.
the switches are all wired up to one action.
-(void)switchAction:(UISwitch *)switch
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:[switch superView]];
//…
}
You could create a subclass of UISwitch and add an indexPath property, then just set the indexPath in cellForRowAtIndexPath:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SwitchCell *returnCell = [tableView dequeueReusableCellWithIdentifier:#"SwitchCell" forIndexPath:indexPath];
returnCell.switch.indexPath = indexPath;
return returnCell;
}

How to use pushViewController and popViewController

I want to A viewcontroller switch another B viewcontroller,First time,I push(press GuestBook Button)and TableView get information ok,and press back Button ok, push(press GuestBook Button) again,JSON data NSLog have message,but tableview data not appear!
what's happen?
PS:i use storyboard!Have a custom cell xib!
A.m
- (IBAction)toHostGuestBook:(id)sender {
hgbvc = [self.storyboard instantiateViewControllerWithIdentifier:#"ToHostGuestBookSegue"];
hgbvc.eventidStr = eventidStr;
NSLog(#"hgbvc.eventidStr:%#",hgbvc.eventidStr);
[self.navigationController pushViewController:hgbvc animated:YES];
}
B.m
- (IBAction)backToHostMainEventDetail:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
B.m
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *GuestBookCellIdentifier = #"GuestBookCellIdentifier";
static BOOL nibsRegistered = NO;
if (!nibsRegistered) {
UINib *nib = [UINib nibWithNibName:#"GuestBookCell" bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:GuestBookCellIdentifier];
nibsRegistered = YES;
}
gbcell = [tableView dequeueReusableCellWithIdentifier:GuestBookCellIdentifier];
if (gbcell == nil) {
gbcell = [[GuestBookCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:GuestBookCellIdentifier];
}
NSDictionary *dict = [messageRows objectAtIndex: indexPath.row];
gbcell.nicknameStr = [dict objectForKey:#"vusr"];
gbcell.dateStr = [dict objectForKey:#"date"];
gbcell.messageStr = [dict objectForKey:#"message"];
return gbcell;
}
Add this and Try
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData]; //your table view name
}
try to reload the table view data on below the code of popViewController this is work what you want
hgbvc the B controller is retained by the A controller. so, it doesn't released when the B controller poped out. When the B controller pushed again, the viewDidLoad method not called.
solution: extract the tableview load data method. call this method when you push B.
in B controller:
- (void)showDataWithEventId:(NSString *)eventId
{
//retrive data
//table view reload
}

How to get cell indexpath in uitextfield Delegate Methods?

I have two textfields in a custom cell how to get the indexpath value of Tableview cell in textfield delegate methods I want to get the input value from user and save it to the relavent object. the user can add more cells by clicking button(Add More) in cell..
Thanks in Advance...
Update to iOS7!
With new features in iOS7 now code should be :
UITableViewCell *textFieldRowCell;
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
// Load resources for iOS 6.1 or earlier
textFieldRowCell = (UITableViewCell *) textField.superview.superview;
} else {
// Load resources for iOS 7 or later
textFieldRowCell = (UITableViewCell *) textField.superview.superview.superview;
// TextField -> UITableVieCellContentView -> (in iOS 7!)ScrollView -> Whoola!
}
NSIndexPath *indexPath = [self.tableView indexPathForCell:textFieldRowCell];
A more dynamic solution (no hardcoded superview levels and same code for different iOS versions).
Further, indexPathForCell: will not work if the cell is not visible, therefore I use indexPathForRowAtPoint: as workaround.
//find the UITableViewcell superview
UIView *cell = textField;
while (cell && ![cell isKindOfClass:[UITableViewCell class]])
cell = cell.superview;
//use the UITableViewcell superview to get the NSIndexPath
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center];
This is how I have been doing it and have been having better luck. I grab the origin of the textField frame. Convert that to a point. Then convert the point to an index path.
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
CGPoint origin = textField.frame.origin;
CGPoint point = [textField.superview convertPoint:origin toView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:point];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
Try this method to get textfield dynamically anywhere from your tableview controller
#pragma mark - Get textfield indexpath
- (NSIndexPath *)TextFieldIndexpath:(UITextField *)textField
{
CGPoint point = [textField.superview convertPoint:textField.frame.origin toView:self.TblView];
NSIndexPath * indexPath = [self.TblView indexPathForRowAtPoint:point];
NSLog(#"Indexpath = %#", indexPath);
return indexPath;
}
To get The indexPath try the following code.
UIView *contentView = (UIVIew *)[textfield superview];
UITableViewCell *cell = (UITableViewCell *)[contentView superview];
if(IS_IOS7_OR_GREATER) {
cell = (UITableViewCell *)[[contentView superview] superview];
}
NSIndexPath *indexPath = [self.tableview indexPathForCell:cell];
Tats it you are done.
To be simple,
NSIndexPath *indexPath = [self.tableview indexPathForCell:(UITableViewCell *)[(UIVIew *)[textfield superview] superview]];
if(IS_IOS7_OR_GREATER) {
cell = (UITableViewCell *)[[[textfield superview] superview] superview];
}
Check the updated answer.
set cell indexpath value to UITextField tag property and you can access the indexpath in delegate methods like textfield.tag
You can set the tags of textfields in cellForRowAtIndexPath: such that it stores info of both cell and text field
For example : If it is cell in 4th row, tag of 1st and 2nd textfields can be 41 and 42 respectively. Similarly, tags of textfields should be 51 and 52 for 5th row and so on...
Then in textfield delegate method, you can get textfield.tag to identify active textfield.
This can be done in the Objective-C runtime for any instance (doesn't have to be UITextField), with any associated object (doesn't have to be NSIndexPath).
For this question, we could create a category UIView+RepresentingIndexPath.
Our interface allows us to set and retrieve an NSIndexPath:
#interface UIView (RepresentingIndexPath)
- (void)representIndexPath:(NSIndexPath *)indexPath;
- (NSIndexPath *)representedIndexPath;
#end
Our implementation uses Objective-C associated objects to set and retrieve an index path on a view:
#import "UIView+RepresentingIndexPath.h"
#import <objc/runtime.h>
static char IndexPathKey;
#implementation UIView (RepresentingIndexPath)
- (void)representIndexPath:(NSIndexPath *)indexPath
{
objc_setAssociatedObject(self, &IndexPathKey, indexPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSIndexPath *)representedIndexPath
{
return objc_getAssociatedObject(self, &IndexPathKey);
}
#end
In action:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TextFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TextFieldCell" forIndexPath:indexPath];
[cell.textField addTarget:self action:#selector(textFieldTextChanged:) forControlEvents:UIControlEventEditingChanged];
[cell.textField representIndexPath:indexPath];
return cell;
}
- (void)textFieldTextChanged:(UITextField *)sender
{
NSIndexPath *indexPath = [sender representedIndexPath];
NSLog(#"%#", indexPath);
}
💣
One final note! Messing around in the runtime should really be avoided if you can achieve what you're trying to do without doing so. Just thought I'd add another solution!
I find this answer searching how can I find the index path of a cell with inside a UITextField.
So, thanks to the answer above, I put my code here, hoping might be usefull.
- (void)searchSelectedIndexPath:(UIView*)view {
// This allow to find selected index path for a table view cell with a text field inside.
for (UIView* subview in view.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
if ([view isFirstResponder]) {
UIView *cell = view;
while (cell && ![cell isKindOfClass:[UITableViewCell class]]) {
cell = cell.superview;
}
self.selectedIndexPath = [self.tableView indexPathForRowAtPoint:cell.center];
return;
}
}
[self searchSelectedIndexPath:subview];
}
}
In this way, when keyboard notification will be raise:
- (void)keyboardDidShow:(NSNotification*)notification {
[self searchSelectedIndexPath:self.tableView];
}
In case somebody like me needs #Kerkness' answer (this one really worked for me) in swift:
func textFieldDidBeginEditing(_ textField: UITextField) {
let origin: CGPoint = textField.frame.origin
let point: CGPoint? = textField.superview?.convert(origin, to: tableView)
let indexPath: IndexPath? = tableView.indexPathForRow(at: point ?? CGPoint.zero)
tableView.scrollToRow(at: indexPath!, at: .middle, animated: true)
}
It should be straight forward enough: you get the point then you get the indexPath and do whatever you need with it!
Thanks, #Luka, it works in a great way.
Here is the swift 4 solution,
var selectedIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowHide(_ :)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowHide(_ :)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func searchSelectedIndexPath(view: UIView) {
view.subviews.forEach { (subview) in
if view is UITextView, view.isFirstResponder == true {
var cell:UIView? = view;
while cell != nil && !(cell is UITableViewCell) {
cell = cell?.superview;
}
if cell != nil {
self.selectedIndexPath = self.dashBoardTableView.indexPathForRow(at: (cell?.center)!)
return
}
}
self.searchSelectedIndexPath(view: subview)
}
}
// Keyboard notification observer menthod
#objc fileprivate func keyboardWillShowHide(_ notification: NSNotification){
if notification.name == NSNotification.Name.UIKeyboardWillShow {
UIView.animate(withDuration: duration, animations: { () -> Void in
self.selectedIndexPath = nil;
self.searchSelectedIndexPath(view: self.tableView)
if let indexpath = self.selectedIndexPath {
self.tableView.scrollToRow(at: indexpath, at: .top, animated: false)
} else{
self.bottomContriant.constant = keyboardHeight
self.view.layoutSubviews()
}
})
} else {
self.bottomContriant.constant = 15
UIView.animate(withDuration: duration, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}

uitableView reloadData doesn't work after setting delegate, datasource and file's owner connection

I have googled and done lot of research from my side to find out why the reloadData method on tableview wouldn't work. I checked all the possible solutions like the datasource is set, delegate is set, the tableview is connected to the file's owner.
After all these, when I am trying to reload the tableview, the no. of rows method gets called, but the cell for rowAtIndexPath doesn't get called. Below is the code that I have written. Please let me know, where I am going wrong
- (void)onReservationListSuccess:(NSArray *)rData
{
if ( rData != nil )
{
resList = [[NSArray alloc] initWithArray:rData];
if([resList count] > 0)
{
[self.tripsTableView reloadData];
//[self.tripsTableView beginUpdates];
//[self.tripsTableView reloadSections:[NSIndexSet indexSetWithIndex:0]
// withRowAnimation:UITableViewRowAnimationNone];
//[self.tripsTableView endUpdates];
}
else
{
[tripsTableView reloadData];
[tripsTableView setHidden:YES];
[noTripsLabel setHidden:NO];
}
}
if(fsnNeedsRefresh == YES)
{
[[NSNotificationCenter defaultCenter] postNotificationName:UpdateFSNList object:nil];
fsnNeedsRefresh = NO;
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int temp=[resList count];
NSLog(#"The no. of rows are %d", temp);
NSLog(#"Testing Purpose");
NSLog(#"The pnr details of the object is:%#",((TripData *)[resList objectAtIndex:0]).pnrDescription);
return 1;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"The cell for the row at indexpath is getting called");
static NSString *CellIdentifier = #"TripCellIdentifier";
TripCell *cell = (TripCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"TripCell" owner:self options:nil];
for(id oneObject in nib)
if([oneObject isKindOfClass:[TripCell class]])
cell = (TripCell *)oneObject;
}
// Set up the cell...
TripData *tripData = (TripData *)[resList objectAtIndex:indexPath.row];
cell.pnrLabel.text = tripData.pnr;
NSLog(#"The cell text is %#",tripData.pnr);
cell.pnrDescriptionLabel.text = tripData.pnrDescription;
NSLog(#"The cell text is %#",tripData.pnrDescription);
cell.pnrTypeLabel.text = tripData.pnrType;
NSLog(#"The cell text is %#",tripData.pnrType);
if(checkInAllowed)
{
cell.checkInButton.tag = indexPath.row;
[cell.checkInButton addTarget:self action:#selector(checkIn:) forControlEvents:UIControlEventTouchUpInside];
}
else
{
[cell.checkInButton setEnabled:NO];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller
TripData *tripData = (TripData *)[resList objectAtIndex:indexPath.row];
NSLog(#"%#", tripData.pnr);
if(tripData != nil)
{
TripOverviewViewController *tripOverviewViewController = [[TripOverviewViewController alloc] initWithTrip:tripData];
[self.navigationController pushViewController:tripOverviewViewController animated:YES];
[tripOverviewViewController release];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
From this part of code I cannot say exactly why it does not work but I'll try to explain how reloadData works.
First, how UITableView works: basically, it's a scrollview. When it is drawn, it checks how many rows it has, then checks their height and from its size and scroll position it decides which rows are currently displayed. Then it asks the delegate to return a UITableViewCell for every displayed row.
When the table is scrolled, it removes the hidden cells from the view hierarchy and adds the cells that have appeared.
And now the tricky part - what does reloadData do? It just removes all the UITableViewCells from the table hierarchy. Nothing more. The actual update is done when the table is drawn for the first time after reloadData.
So, my suggestion is - check that your table is not hidden and check its frame. Also, I see that you are accessing both a property getter self.tripsTableView and an ivar tripsTableView. This is confusing. Do they both return the same?

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.