When I call [tableView reloadData] the function - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath is not fired? Why?
Here's my TableView.m
#implementation TableView
#synthesize managedObjectContext;
#synthesize controller = _controller;
#synthesize tableView = _tableView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(id)init
{
self = [super init];
if (self) {
NSLog(#"%s",__PRETTY_FUNCTION__);
self.del = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = self.del.managedObjectContext;
[self performControllerFetch];
}
return self;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
...
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
...
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
...
}
-(void)reloadTableView
{
NSLog(#"%s",__PRETTY_FUNCTION__);
[self.tableView reloadData];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
NSLog(#"%s",__PRETTY_FUNCTION__);
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray
arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray
arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
#end
TableView.h
#interface TableView : UITableView <UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate>
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) AppDelegate *del;
#property (nonatomic, strong) NSFetchedResultsController *controller;
#property (weak, nonatomic) IBOutlet TableView *tableView;
-(void)reloadTableView;
-(void)performControllerFetch;
#end
It looks like you are never setting the delegate and datasource properties on your tableview, no? You're implementing the methods in those protocols inside tableview.m but not setting the two properties to self hence calling reloadData having no effect.
Does this help?
Edit:
Looks like you have a tableView property set on a subclass of UITableView and hooked up to an interface builder file. This is an odd setup (a tableview within a tableview). Usually you would have something like a UIViewController subclass hooked up to an XIB and set an
#property (nonatomic, strong) IBOutlet UITableView * tableView
in that subclass, or something similar. And then handle the data fetching and tableview delegate/datasource inside that viewcontroller subclass.
But here, because of the nested UITableViews it's not as straightforward. Is there a reason for this design? I recommend moving to something like I describe above to help bring clarity to the setup.
Also, try adding some NSLog statements into your init method to see if the tableview != nil and the delegate and datasource properties have been set. My suspicion is that the connection is not being made.
Also, unrelated to the problem at hand, you no longer have to manually #synthesize ivar = _ivar, it will be done for you automatically using the underscore convention.
I was having the same issue, that the reload was not working. I believe the delegate method I was calling it from was in a background thread which was causing the problem. My solution was to create a new method (below) and call the reload from there instead, while also ensuring to call it from the main thread:
- (void) tocLoad {
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
If you are not able to reload table using
[self.tableView reloadData];
Please make sure you are adding reference to following from the storyboard
#property (weak, nonatomic) IBOutlet TableView *tableView;
How to add reference you can click on following Image Link:
tableView reference outlet
This happened to me because there was a call to
tableView.beginUpdates()
without a call to
tableView.endUpdates()
In my code. Removing tableView.beginUpdates() resolved the issue for me.
I spent days debugging this issue, and found a ton of possible fixes. I starred the one that worked for me but they're all valid.
First, of course, check that your delegate, datasource, IBOutlet, etc are all configured properly.
**Set the All Exceptions breakpoint and see if you are getting an out of bounds exception in your datasource. Without setting the breakpoint there is no crash and the tableview will simply fail to reload.
Other possible fixes: check the tableview's frame to make sure the width or height is not 0. Try executing reloadData on the main thread. Lastly, try only reloading a single section (here).
Gotta love table views.
Inspite of writing this [self.tableView reloadData]; try below, becuase already you have written class so no need to take the outlet again:-
[self reloadData]
Related
I have a UITableView with custom tableview cells. One of my Cell has a UITextField, I'm handling the textfield delegate methods in the custom tableviewCell class. I need to reload the tableview once the user entered a text, I had done the below thing but not worked , Any idea please help me.
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
UITableView *parentTable = (UITableView *)self.superview;
[parentTable reloadData];
}
Register the view controller to receive notifications that the data has been changed, and have it refresh the table when it receives one. Then have the parser send it out.
Registering for it is easy:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadTableView:)
name:#"reloadTableView"
object:nil];
Your refresh method needs to be set up to receive these notifications, along these lines:
- (void)reloadTableView:(NSNotification *)notif {
[self.yourTableName reloadData];
}
And it's important to stop observing in your ViewDidUnload:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Then in the parser you need to simply add this when it's complete:
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadTableView"
object:nil];
The view controller (and anyone else observing the notification with that name) will get the message and perform its task.
Thanks..
CustomCell.h
#property (nonatomic, copy) void(^tapHandler)(NSUInteger tag);
CustomCell.m
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
NSUInteger tag = 10; //Need to change the tag
self.tapHandler(10);
}
Controller.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
// Whatever content was previously there. Add the below line in addition
cell.tapHandler = ^(NSUInteger tag){
[tableView reloadData];
};
return cell
}
Hope it helps!
first , the superview of the textfiled is not UITableview, it is tableviewcell or tableviewcell.contentview (depends on your code)
then, you just need to set the tableview as your view controller's member ,or property,
then
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[self.tableview reloadData];
}
Try this,
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[self.parentTable reloadData];
}
It's better to keep a reference of table view in CustomCell
// interface
UITableView *tableView;
// propery
#property (nonatomic)UITableView * tableView;
Then
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//your stuff
cell.tableView= tableView;
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
UITableView *parentTable = (UITableView *)textField.superview.superview;
[parentTable reloadData];
}
Don't reload whole table view for that small operation. Just reload the Cell in which the text field is. Also didnot forget to update your data source as table view cell fetch the data from the data source while the cell is loading.
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[tableViewDataSource addObject:textField.text];//this will fill the data source with data of textfield
CustomCell *cell = (CustomCell *)textField.superview.superview;
NSIndexPath *indexPath = [parentTable indexPathForCell:cell];
[parentTable reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
By (UITableView *)self.superview; you will not get the table view. Try below code :
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
YourCell *cell = (YourCell *)[[textField superview] superview]; // you will get cell
UITableView *table = cell.superview; // you will get table view
[table reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
tableReload++;
NSString *CellIdentifier = [NSString stringWithFormat:#"cell %d%d",indexPath.row,tableReload];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.text = #"Hello";
}
// Configure the cell.
return cell;
}
Try this,
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[self.yourTableName reloadData];
[self.yourTableName reloadInputViews];
}
I want to expand the cell when selected to show a row of buttons like the image above. I have a xib file which shows a cell that is 320 wide x 140 tall. I have subclassed that UITableViewCell. Additionally I have another xib file that has the row of buttons as shown in blue ink in the image above.
I am able to load the row of buttons using initWithCoder using this answer from a really smart guy!
However, it overwrites all my other views inside the MyCustomCell.
How can I load the xib just when the cell expands so that it is positioned in the lower half of the 140pt tall cell?
After doing a little more research, I found a different way to do something like what you want using just one cell. In this method, you have just the taller cell, but return a height that only lets you see the top half of the cell unless its selected. You have to do two things in the design of your cell to make this work. First select the box for "clip subviews" in IB, so the buttons won't show outside their view (the cell). Second, make the constraints such that the buttons are all lined up vertically with each other and give one of them a vertical spacing constraint to one of the UI elements in the top of the cell. Make sure none of the buttons has a constraint to the bottom of the cell. By doing this, the buttons will maintain a fixed distance with the top elements (which should have a fixed space to the top of the cell), which will cause them to be hidden when the cell is short. To animate the change in height, all you have to do is call beginUpdates followed by endUpdates on the table view.
#import "TableController.h"
#import "TallCell.h"
#interface TableController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSIndexPath *selectedPath;
#end
#implementation TableController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"TallCell" bundle:nil] forCellReuseIdentifier:#"TallCell"];
self.theData = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine"];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.theData.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:self.selectedPath]) {
return 88;
}else{
return 44;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TallCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TallCell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([self.selectedPath isEqual:indexPath]) {
self.selectedPath = nil;
}else{
self.selectedPath = indexPath;
}
[tableView beginUpdates];
[tableView endUpdates];
}
This, I think gives you the desired animation when you pick the first cell, but you get a somewhat different animation if you then pick a cell below the currently selected one. I don't know of any way around that.
I'm not sure what you have in your two xib files, but the way I would do it is to have one xib file for the shorter cell, and one for the taller cell (that has everything in it that you show in your drawing). I did it like this, with two xib files like I mention above:
#import "TableController.h"
#import "ShortCell.h"
#import "TallCell.h"
#interface TableController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSIndexPath *selectedPath;
#end
#implementation TableController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"ShortCell" bundle:nil] forCellReuseIdentifier:#"ShortCell"];
[self.tableView registerNib:[UINib nibWithNibName:#"TallCell" bundle:nil] forCellReuseIdentifier:#"TallCell"];
self.theData = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine"];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.theData.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:self.selectedPath]) {
return 88;
}else{
return 44;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (! [self.selectedPath isEqual:indexPath]) {
NSLog(#"returning short");
ShortCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ShortCell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row];
return cell;
}else{
NSLog(#"returning long");
TallCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TallCell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row];
return cell;
}
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if ([self.selectedPath isEqual:indexPath]) {
self.selectedPath = nil;
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
return;
}
NSIndexPath *lastSelectedPath = self.selectedPath;
self.selectedPath = indexPath;
if (lastSelectedPath != nil) {
[self.tableView reloadRowsAtIndexPaths:#[indexPath,lastSelectedPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}else{
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
I have a UITabbar with multiple controllers in it. One of the controllers is used to add Events to Core Data, while another controller is used to display events as in a UITableView using NSFetchedResultsController.
Here's the behaviour that I would like to achieve:
Upon disappearing, the UITableView stops updating, and when the user comes back, the entire table view is reloaded. Otherwise, inserting events from the other controller takes longer, as new rows are created in the UITableView, even thought it is not visible.
I'm wondering how I can achieve this behavior, as it doesn't seem to work as I expect it would be:
I have set the delegate of the NSFetchedResultsController to nil in viewWillDisappear, and restore it in viewWillAppear, along with a call to [UITableView reloadData];
Somehow, I do not see the new data, and suspect this is due to the way NSFetchedResultsController stops fetching if it does not have a delegate.
How can I properly "suspend" updates to UITableView when it disappears, but still able to see the entire dataset when the controller reappears?
Try sending performFetch: to the NSFetchedResultsController in viewWillAppear: after you have set its delegate back to self.
I think you do not have to "suspend" the table view updates. A UITableView will anyway only request data from the NSFetchedResultsController for visible cells. If the the table view is not visible, no updates will be fired.
Did you test if inserting events from another controller really takes longer? I doubt it. What does Instruments say?
If your delegate methods are fired, you could still check if the table view is visible before doing any updates.
After that, you do exactly as suggested by rob: do a performFetch: in viewWillAppear:.
Instead of setting the delegate of the NSFetchedResultsController to nil in viewWillDisappear, try setting an object of NSFetchedResultsController to nil
What about this crude approach? Not tested it.
#property (nonatomic) BOOL bruteForceReload;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.bruteForceReload = NO;
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
self.bruteForceReload = YES;
}
-(void)setBruteForceReload:(BOOL)bruteForceReload {
_bruteForceReload = bruteForceReload;
if (_bruteForceReload) {
[self.tableView reloadData];
}
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
if (!self.bruteForceReload) {
[self.tableView beginUpdates];
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
if (!self.bruteForceReload) {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
return;
}
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
if (!self.bruteForceReload) {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if (!self.bruteForceReload) {
[self.tableView endUpdates];
} else {
[self.tableView reloadData];
}
}
I went through all the examples online and could not figure out how to properly add a cell to a tableview with animation. Let's say I have one section with one cell and I want to add another cell once the user clicks on the first cell's accessory.
My "add" method does this:
- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {
if (switch1.on) {
NSArray *appleComputers = [NSArray arrayWithObjects:#"WWWWW" ,#"XXXX", #"YYYY", #"ZZZZ", nil];
NSDictionary *appleComputersDict = [NSDictionary dictionaryWithObject:appleComputers forKey:#"Computers"];
[listOfItems replaceObjectAtIndex:0 withObject:appleComputersDict];
[tblSimpleTable reloadData];
}
Which is working but there is no animation. I understand that in order to add animation, I need to use insertRowsAtIndexPaths:withRowAnimation, so I tried tons of options but it always crashes when executing the insertRowsAtIndexPaths:withRowAnimation method.
My recent try was by doing this:
- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {
if (switch1.on) {
NSIndexPath *path1 = [NSIndexPath indexPathForRow:1 inSection:0]; //ALSO TRIED WITH indexPathRow:0
NSArray *indexArray = [NSArray arrayWithObjects:path1,nil];
[tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
}
}
What am I doing wrong? How can I make this happen easily? I dont understand this whole indexPathForRow thing...I also dont understand how with this method I can add a label name to the new cell. Please help...thanks!!
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
Then insert your row:
[tblSimpleTable beginUpdates];
[tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
[tblSimpleTable endUpdates];
Simply inserting or deleting a row doesn't change your data source; you have to do that yourself.
The important thing to keep in mind when using insertRowsAtIndexPaths is that your UITableViewDataSource needs to match what the insert is telling it to do. If you add a row to the tableview, make sure the backing data is already updated to match.
First of all, you should update your data model just before update table itself.
Also you can use:
[tableView beginUpdates];
// do all row insertion/delete here
[tableView endUpdates];
And table will produce all changed at once with animation (if you specify it)
The insertRowsAtIndexPaths:withRowAnimation: AND the changes to your data model both need to occur in-between beginUpdates and endUpates
I've created a simple example that should work on its own. I spent a week fiddling around trying to figure this out since I couldn't find any simple examples, so I hope this saves someone time and headache!
#interface MyTableViewController ()
#property (nonatomic, strong) NSMutableArray *expandableArray;
#property (nonatomic, strong) NSMutableArray *indexPaths;
#property (nonatomic, strong) UITableView *myTableView;
#end
#implementation MyTableViewController
- (void)viewDidLoad
{
[self setupArray];
}
- (void)setupArray
{
self.expandableArray = #[#"One", #"Two", #"Three", #"Four", #"Five"].mutableCopy;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.expandableArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//here you should create a cell that displays information from self.expandableArray, and return it
}
//call this method if your button/cell/whatever is tapped
- (void)didTapTriggerToChangeTableView
{
if (/*some condition occurs that makes you want to expand the tableView*/) {
[self expandArray]
}else if (/*some other condition occurs that makes you want to retract the tableView*/){
[self retractArray]
}
}
//this example adds 1 item
- (void)expandArray
{
//create an array of indexPaths
self.indexPaths = [[NSMutableArray alloc] init];
for (int i = theFirstIndexWhereYouWantToInsertYourAdditionalCells; i < theTotalNumberOfAdditionalCellsToInsert + theFirstIndexWhereYouWantToInsertYourAdditionalCells; i++) {
[self.indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
//HERE IS WHERE YOU NEED TO ALTER self.expandableArray to have the additional/new data values, eg:
[self.expandableArray addObject:#"Six"];
[self.myTableView insertRowsAtIndexPaths:self.indexPaths withRowAnimation:(UITableViewRowAnimationFade)]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
//this example removes all but the first 3 items
- (void)retractArray
{
NSRange range;
range.location = 3;
range.length = self.expandableArray.count - 3;
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
[self.expandableArray removeObjectsInRange:range];
[self.myTableView deleteRowsAtIndexPaths:self.indexPaths withRowAnimation:UITableViewRowAnimationFade]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
#end
For the swift users
// have inserted new item into data source
// update
self.tableView.beginUpdates()
var ip = NSIndexPath(forRow:find(self.yourDataSource, theNewObject)!, inSection: 0)
self.tableView.insertRowsAtIndexPaths([ip], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
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.