How to insert a row to a UITableView? - iphone

I want to use insertRowsAtIndexPaths method to insert a row to a UITableView, but I don't know how to make "indexPaths" property for this method.
Please help me!

You can create single NSIndexPath using
+ (NSIndexPath *)indexPathForRow:(NSUInteger)row inSection:(NSUInteger)section; // defined in UITableView.h
Your code to create and fill indexPaths paramater may look like:
NSMutableArray* indexPaths = [NSMutableArray arrayWithCapacity:10];
for (...) // iterate to get all indexes and section you need
{
[indexPaths addObject:[NSIndexPath indexPathForRow:someRow inSection:someSection]];
}
//indexPaths is ready to use

- (NSIndexPath *) indexPathForRow:(NSUInteger)row andSection:(NSUInteger)section {
NSUInteger _path[2] = {section, row};
NSIndexPath *_indexPath = [[NSIndexPath alloc] initWithIndexes:_path length:2];
return [_indexPath autorelease];
}

Related

How to get indexPath from table view?

So in my app, which is based around UITableViews, I am getting the indexPath for the row for methods like didSelectRowAtIndexPath: like so:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
// Extract Data
CTCoreMessage *message = [[self allMessages] objectAtIndex:row];
}
My question is, what if I wanted to receive the indexPath in an IBAction? How would I do that since there is no NSIndexPath declaration in the header of the method?
Thanks!
But you don't really want the index path, you want the row, yes? You need to configure the sender (some UIControl) of the action to know your row.
Option 1: set the senders tag property (quick and simple).
Option 2: subclass the sender and add a property to store the row (technically correct).
Where you configure the sender depends on how and where you create it and how it relates to the table view, probably in -tableView:cellForRowAtIndexPath:.
I have solved the problem with this code, thank you to everyone that helped!
NSUInteger nSections = [self.messagesTable numberOfSections];
NSUInteger nRows = [self.messagesTable numberOfRowsInSection:nSections];
NSInteger lastSection = nSections - 1;
NSInteger lastRow = nRows - 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRow inSection:lastSection];
Thanks again!

UITableView Insert More Rows While Keeping The Already Fetched Ones

Below is the function that i am using to get and parse different pages of the feed.
- (void)getFeed:(NSInteger) pageNumber
{
collectedData = [[NSMutableData alloc] init];
NSString *urlStr =[NSString stringWithFormat:#"http://sitename.com/feed/?paged=%d", pageNumber];
NSURL *url = [NSURL URLWithString:urlStr];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
}
When user scrolls down to the bottom of tableview, i use the below function to access the second page of feed and so on by incrementing the counter:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height)
{
counter ++;
[self getFeed:counter];
}
}
The issue is when instead of loading it below the already fetched items, it reloads the tableview to show the new page items and old ones disappear. I want to load new page items below the existing ones to have infinite scroll. How can i do that?
It feels bad to answer your own question, but taking help from another forum, i was able to figure out the answer.
The tableview controller was not in sync with the mutable array, so i created another and append both arrays.
Step1: I created a property
#property (nonatomic, readonly) NSMutableArray *moreItems;
Step2: Initialised the array in ViewDidLoad before calling fetchEntries
moreItems = [[NSMutableArray alloc] init];
Step3: In connectionDidFinishLoading, i appended new array 'moreItems' to the previous array 'items'
[moreItems addObjectsFromArray:[channel items]];
Step4: Changed the data source in numberOfRowsInSection from
return [[channel items] count];
to new array
return [moreItems count];
and then in cellForRowAtIndexPath, used the new array
RSSItem *item = [moreItems objectAtIndex:[indexPath row]];
You should use insertRowsAtIndexPaths: withRowAnimation: method of UITableView.
One thing you must take care of is that when you use this method, number rows retuned by tableView:numberOfRowsInSection should be equal to number_of_existing_rows + number_of_newly_inserted_rows
After you finish retrieving the new feed for next page get the objects in an array.
Add these objects from this array to your data source array.
Now insert rows to the table as follows:
[yourTable beginUpdates];
[yourTable insertRowsAtIndexPaths:indexPathsToInsert
withRowAnimation:UITableViewRowAnimationBottom];
[yourTable endUpdates];
where "indexPathsToInsert" is array of NSIndexPath objects starting from the last row in your table and containg indexPaths for your new objects.
Hope this helps.
Hi,
Create a for loop, with the starting index is (number of objects in datasource) and number of objects you want to add (count),create the array of indexpaths and call insertRowsAtIndexpaths:withRowAnimation. I hope the code helps you.
NSInteger statingIndex = [self.tableViewDatasource count];
NSInteger noOfObjects = //Get the no.of objects count from getFeed method.
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for (NSInteger index = statingIndex; index < statingIndex+noOfObjects; index++) {
[_objects addObject:]; // add the object from getFeed method.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[indexPaths addObject:indexPath];
[indexPath release];
}
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
Whenever your array gets a new object added you call this
-(void)insertRowInTableView
{
int row = [YourDataSourceArray count];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[YourTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
}
Here YourDataSourceArray is the array from which you are setting your tableview row count/data.
YourTableView is your tableView object name.
This may be because when ever you are getting your data from the webservice you are adding it in the Array you are using to populate the table, by this you will have only new data, what you have to do is you have to append the new data to the array(Make it Mutable Array and append new data) and just use the following line
[your_Table reloadData];
Hope this will help you.
Instead of using scrolling down to load, better use the last row of tableview as "Load More Items" and when the user selected that row, get the new feed and reload the tableview.
And also I have one doubt in your code, everytime in getfeed method, you are creating array
collectedData. I doubt you missing of appending this data to table datasource data,which can cause to show you only recent feed data in tableview. Please check

How to get a UITableViewCell's subtitle show how many times that specific cell was tapped?

I have a uitableview that displays the values of an array. I would like to know if there is a way to update the subtitle of its table cells, based on how many times each cell was tapped.
Thank you!
First of all, you'll want to use a NSMutableArray so you can change its contents after instantiation. Here's a basic over view of what I just tried to achieve your intended results:
In your interface
#property (strong, nonatomic) NSMutableArray *tapCountArray;
In your implementation
- (void)viewDidLoad
{
[super viewDidLoad];
self.tapCountArray = [NSMutableArray new];
int numberOfRows = 20;
for (int i = 0; i < numberOfRows; i ++) {
[self.tapCountArray addObject:#(0)];
}
}
Then the important part!
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tapCountArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSString *text = [self.tapCountArray[indexPath.row] stringValue];
[cell.textLabel setText:text];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tapCountArray replaceObjectAtIndex:indexPath.row withObject:#([self.tapCountArray[indexPath.row] intValue] + 1)];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
When each cell is tapped the number in its detailTextLabel will be incremented up by one.
You shouldn't create a new array or set, that could lead to problems if the two arrays get out of sync with each other. The way to do it, as you suggested in your comment, is to use dictionaries. The way you said you were doing that is probably not the way, however. You want an array of dictionaries where the values for one key would be whatever your main data is and the value for the other key would be the number of taps. For example, lets call the two keys main and sub, and your main data is a set of names. The array of dictionaries would look like this: ({main:#"Tom",sub:1}, {main:#"Dick", sub:0}, {main:#"Harry",sub:2}, .....). In the tableView:cellForRowAtIndexPath:indexPath method you would provide the data to the cells like this:
cell.textLabel.text = [[array objectAtIndex:indexPath.row] valueForKey:#"main"];
cell.detailTextLabel.text = [[array objectAtIndex:indexPath.row] valueForKey:#"sub"];
I think you can just set up another array of the same length as the one you have now. Then when your didSelectRowAtIndexPath is triggered, increment your indexPath.row entry of the new array and refresh that cell. If you don't expect to shuffle the table, you don't need a dictionary.
You can insert the object into an NSCountedSet, and on your cellForRowAtIndexPath method, you would take the model object for the cell and verify the number of times it has been inserted into the NSCountedSet instance.
Take a look at the NSCountedSet documentation: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSCountedSet_Class/Reference/Reference.html

UITableViewCellAccessory check

I have an array which loads in table view, and if users taps a certain cell it changes to UITableViewCellAccessoryCheckmark.
How can I check what object in array is checked and add all objects that are checked to another array?
If you want a function that actually gets the checked objects at a whim, use the following:
- (NSMutableArray*)checkedObjectsInTable:(UITableView*)tableView
{
NSMutableArray *checkedObjects = [[[NSMutableArray alloc] init] autorelease];
for (int i=0; i<tableDataSource.count; i++)
{
if ([tableView cellForRowAtIndexPath:
[NSIndexPath indexPathForRow:i inSection:0]].accessoryType == UITableViewCellAccessoryCheckmark)
{
[checkedObjects addObject:[tableDataSource objectAtIndex:i]];
}
}
return checkedObjects;
}
That would allow you to get the data on demand. Note that it would be much less efficient than simply using Jasarien's method, yet there are some situations where it is a better solution.
Something like this in your tableView:didSelectRowAtIndexPath: method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//set checkmark accessory on table cell ...
// get object and add to checkedObjects array
NSInteger index = [indexPath row];
MyObject *object = [myArray objectAtIndex:index];
[checkedObjects addObject:object];
}

how to properly use insertRowsAtIndexPaths?

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()