I am developing an ToDoList iOS app that uses table view. Cell contents(UITableCellView) are filled by a instance variable of nsmutablearray.
When I add a new ToDoItem, the content is being displayed twice for each row I add. When I put a breakpoint and debug the code I found that cellForRowAtIndexPath method is called twice for every row I add. Why is that happening. How to handle this case or is there any other alternative method to return UITableCellView cell for display.
Method is shown below
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:#"ListPrototypeCell" forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"ListPrototypeCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
ToDoItem *item = [self.toDoItems objectAtIndex:indexPath.row];
cell.textLabel.text = item.itemName;
return cell;
}
This method is called when user adds new item
-(IBAction)unwindToList:(UIStoryboardSegue *)segue{
AddToDoItemViewController *ITEM = [segue sourceViewController];
ToDoItem *tItem = ITEM.toDoItem;
if (tItem!=nil) {
[self.toDoItems addObject:tItem];
[self.tableView reloadData];
[self setItems:self.toDoItems];
}
}
EDIT:
-(void)setItems:(NSMutableArray *)item{
if (item!=nil) {
tDItems = item;
}
}
tDItems is just another instance variable I am using
Thanks in advance
just check your array if duplicate items are added to the array cause even if cellForRowAtIndexPath is called twice it will not create duplicate cells if items in array are not duplicate. cellForRowAtIndexPath might get called twice because of multiple reloads
i think problem is with the reloading the table data ..try to reload data like this...may be it works
-(IBAction)unwindToList:(UIStoryboardSegue *)segue{
AddToDoItemViewController *ITEM = [segue sourceViewController];
ToDoItem *tItem = ITEM.toDoItem;
if (tItem!=nil) {
[self.toDoItems addObject:tItem];
[self setItems:self.toDoItems];
}
[self.tableView reloadData];
}
Use insertRowsAtIndexPaths with [ToDoItem addObject:[NSIndexPath indexPathForRow:i inSection:0]];
where i= indexPath for where you want to insert Element.
` [tableView beginUpdates];
[insertRowAtIndexArray removeAllObjects];
[insertRowAtIndexArray addObject:[NSIndexPath indexPathForRow:i inSection:0]];
[toDoItems addObject:[NSIndexPath indexPathForRow:i inSection:0]];
i++;
[tableView insertRowsAtIndexPaths:insertRowAtIndexArray withRowAnimation:(UITableViewRowAnimationAutomatic)];
[tableView endUpdates];`
I did a small fix and it worked. Below is the code
-(IBAction)unwindToList:(UIStoryboardSegue *)segue{
AddToDoItemViewController *ITEM = [segue sourceViewController];
ToDoItem *tItem = ITEM.toDoItem;
if (tItem!=nil) {
if ([self.toDoItems count] == 0) { // This is the fix made
[self.toDoItems addObject:tItem];
}
[self.tableView reloadData];
[self setItems:self.toDoItems];
}
}
But I am not sure how the items are getting added from the second item onwards.addObject is working only for the first item and there are no duplicates in the view. But if i remove that if condition then duplicates can be seen. Now I am debugging to see why only it is working for first item.
Related
I am having one number of section base tableview in that plus button which will add new item in tableview.
While i try add or delete the section from tableview and reload that tableview it will blink old record many times and then add or delete the section. Here is the code.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SymbolTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectIndexPath containsObject:[NSNumber numberWithInt:(int)indexPath.section]])
{
[selectSymbol removeObject:cell.lblsymbol.text];
[selectIndexPath removeObject:[NSNumber numberWithInt:(int)indexPath.section]];
}
else
{
NSString *strr = cell.lblsymbol.text;
[selectSymbol addObject:strr];
[selectIndexPath addObject:[NSNumber numberWithInt:(int)indexPath.section]];
}
[tblDetail reloadData];
}
what is the issue?
try following code it may helps you
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath
{
SymbolTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectIndexPath containsObject:[NSNumber numberWithInt:(int)indexPath.section]])
{
[tblDetail beginUpdates];
[selectSymbol removeObject:cell.lblsymbol.text];
[selectIndexPath removeObject:[NSNumber numberWithInt:(int)indexPath.section]];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
else
{
NSString *strr = cell.lblsymbol.text;
[tblDetail beginUpdates];
[selectSymbol addObject:strr];
[selectIndexPath addObject:[NSNumber numberWithInt:(int)indexPath.section]];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
}
You can reload cell which you removed/added
You will not reload all table.
Try the following code please
[self.tableView deleteRowsAtIndexPaths:#[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
or
[self.tableView insertRowsAtIndexPaths:#[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
for inserting row in tableview
- (void)insertNewObject:(id)sender
{
if (!arrayForTableContent) {
arrayForTableContent = [[NSMutableArray alloc] init];
}
NSInteger count = [arrayForTableContent count];
NSString *strTobeAdded = [NSString stringWithFormat:#"%d",count+1];
[arrayForTableContent insertObject:strTobeAdded atIndex:count];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:count inSection:0];
[self.tableViewForScreen insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I'm not writing all the code nor test it in the debugger, but I will give you an advice of how to proper implement this. Let's say you have a datasource, like this:
NSMutableArray *data = #[#"First", #"Second", #"Third"];
Datasource: In numberOfRowsInSection, you return data.count; in cellForRowAtIndexPath, you will configure the cell with the respective index from the array;
Delegate: In didSelectRowAtIndexPath, remove the object from the data array, say you will need something like this: [data removeObjectAtIndex(indexPath.row)]; then call reloadData on the table view. The table view will smoothly return now just two items.
I would like to create cell by expand and collapse. To do this i animate the cell by reloading. When i expand it works fine. But when i collapse it crashes and the reason is Attempt to create two animations for cell. I know Ive given same array paths and it wont reload two cell at the time. Is there a way to fix this.
I am new to coding, so i would be happy to get a simple solution.
Code:
The reason am using lastSelIPath is when first cell is expanded and touch the second cell the first cell would collapse and the second cell would expand.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath* lastSelIPath = [ NSIndexPath indexPathForRow:selectedIndex_ inSection:0 ];
[ tableView deselectRowAtIndexPath:indexPath animated:YES ];
if (selectedIndex_ == indexPath.row)
{
selectedIndex_ = -1;
}
else
{
selectedIndex_ = indexPath.row;
}
NSIndexPath* ipath = [ NSIndexPath indexPathForRow:indexPath.row inSection:0 ];
NSArray* indexPathArr = [ NSArray arrayWithObjects:lastSelIPath, ipath, nil ];
[ tableView beginUpdates ];
[ tableView reloadRowsAtIndexPaths:indexPathArr withRowAnimation:UITableViewRowAnimationAutomatic ];
[ tableView endUpdates ];
}
So what i did now I set a condition and reload the one i wanted. Is it a correct method to follow please help.
Modified code:
if ( lastSelIPath.row == ipath.row )
{
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else
{
[tableView reloadRowsAtIndexPaths:#[lastSelIPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
Crash is right, you are trying to attempt two animation. Your indexPathArr contains two objects. Do not create this array and try this:
[tableView reloadRowsAtIndexPaths:#[lastSelIPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
Let me know, if it helps
[tableView reloadRowsAtIndexPaths:#[newIndexPath, oldIndexPath] withRowAnimation:UITableViewRowAnimationNone];
If newIndexPath is same as oldIndexPath, it will crash in iOS8, but this is fixed in iOS9 and later.
You can use third party library for expanding cell.
https://github.com/bennyguitar/CollapseClick
& implement there delegate method as
-(int)numberOfCellsForCollapseClick
{
}
-(NSString *)titleForCollapseClickAtIndex:(int)index {
}
-(UIView *)viewForCollapseClickContentViewAtIndex:(int)index {
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (selectedTag==1) {
selectedIndex=indexPath.row;
[self performSelector:#selector(Write Your Action) withObject:nil];
}
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandedIndexPath]) {
[modifiedRows addObject:self.expandedIndexPath];
self.expandedIndexPath = nil;
}
else {
if (self.expandedIndexPath)
[modifiedRows addObject:self.expandedIndexPath];
self.expandedIndexPath = indexPath;
[modifiedRows addObject:indexPath];
}
// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Using Selected Tag write the condition of your cell expand action. may be it helps.
hey i have created a table view with multiple selection and checkmark accessory which users can tap on and select relevant rows in table... retailerid and retailernaem of individual selection will be stored in NSMutableDictionary and all dictionaries in return will be stored in NSMutableArray... i have done this till now
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectedCell accessoryType] == UITableViewCellAccessoryNone) {
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
NSMutableDictionary *theDictionary = [[NSMutableDictionary alloc] init];
[theDictionary setObject:[[BrandsArray valueForKey:#"RetailerID"] objectAtIndex:indexPath.row] forKey:#"id"];
[theDictionary setObject:[[BrandsArray valueForKey:#"RetailerName"] objectAtIndex:indexPath.row] forKey:#"name"];
[selectedIndexes addObject:theDictionary];
}
else {
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
NSLog(#"this is deselected row %#",[selectedIndexes objectAtIndex:indexPath.row]);
NSMutableDictionary *dictionary = [selectedIndexes objectAtIndex:indexPath.row];
[selectedIndexes removeObject:dictionary];
dictionary = nil;
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
now problem is in the else block... its not removing objects from randomly selected row...
for example if i select first row and then deselect the same it works fine but when i select the last row and then deselect the same app crash..
Since my other answer dealt with multiple sections, I will provide a simpler, single section answer here
First, declare a property for your selection state -
#property (strong,nonatomic) NSMutableIndexSet *selectStates;
Initialise it in viewDidLoad
self.selectStates=[NSMutableIndexSet new];
The use it in didSelectRowAtIndexPath -
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([self.selectStates containsIndex:[indexPath.row]) {
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
[self.selectStates removeIndex:indexPath.row];
} else {
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
[self.selectStates addIndex:indexPath.row];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
You will also need to check the selectedStates in cellForRowAtIndexPath to set the correct accessory when the cell is scrolled back into view. Just add the snippet -
if ([self.selectStates containsIndex:[indexPath.row]) {
[cell setAccessoryType:UITableViewCellAccessoryNone];
} else {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
If you need the ability to easily insert and delete rows then you can use an NSMutableSet instead of an NSIndexSet - just store the data source object. If you do allow deletion you need to make sure that the object is removed from the set, if necessary, when the row is deleted.
First, declare a property for your selection state -
#property (strong,nonatomic) NSMutableSet *selectStates;
Initialise it in viewDidLoad
self.selectStates=[NSMutableSet new];
The use it in didSelectRowAtIndexPath -
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
Brand *brand=(Brand *)[BrandsArray objectAtIndex:indexPath.row]; // Change "Brand" to the appropriate object class
if ([self.selectStates containsObject:brand) {
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
[self.selectStates removeObject:brand];
} else {
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
[self.selectStates addObject:brand];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
You will also need to check the selectedStates in cellForRowAtIndexPath to set the correct accessory when the cell is scrolled back into view. Just add the snippet -
if ([self.selectStates containsObject:brand) {
[cell setAccessoryType:UITableViewCellAccessoryNone];
} else {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
The problem is the way you use the array.
Let's say you select the last element from a table view with 10 cells. Then the array size is 1, but when you select again the last row, [selectedIndexes objectAtIndex:indexPath.row]; is trying to access the element with index 9 in the array, which does not exist at the moment.
Try to implement something like this:
NSInteger ARRAY_INITIAL_SIZE = 100;
NSMutableArray *selectedIndexes = [NSMutableArray arrayWithCapacity:ARRAY_INITIAL_SIZE];
for (NSInteger i = 0; i < ARRAY_INITIAL_SIZE; ++i) {
selectedIndexes[i] = [NSNull null];
}
Make selectedIndexes to be a property in your View Controller class, and whenever you use it, put self.selectedIndexes instead.
In the didSelectRow: atIndexPath: method, the first line is recommended to be:
[tableView deselectRowAtIndexPath:indexPath animated:NO];
Then, write the code:
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectedCell accessoryType] == UITableViewCellAccessoryNone) {
selectedCell.accessoryType = UITableViewCellAccessoryCheckmark;
NSMutableDictionary *theDictionary = [NSMutableDictionary dictionary];
[theDictionary setObject:[[BrandsArray valueForKey:#"RetailerID"] objectAtIndex:indexPath.row] forKey:#"id"];
[theDictionary setObject:[[BrandsArray valueForKey:#"RetailerName"] objectAtIndex:indexPath.row] forKey:#"name"];
[selectedIndexes replaceObjectAtIndex:indexPath.row withObject:theDictionary];
} else {
selectedCell.accessoryType = UITableViewCellAccessoryNone;
[selectedIndexes replaceObjectAtIndex:indexPath.row withObject:[NSNull null]];
}
I hope it helps you. Let me know.
I have more than 20 cells in my custom table view, in execution time 6 cells will be visible. Now i select the 4 th cell means, that 4th cell have to come in first position and 5th cell in 2nd position and so on. how to do this process, i tried like this.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MACalendarCustomCell *cell = (MACalendarCustomCell*) [tableView dequeueReusableCellWithIdentifier:[MACalendarCustomCell reuseIdentifier]];
if(cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"MACalendarCustomCell" owner:self options:nil];
cell = customCell;
customCell = nil;
}
cell.lbl_CalendarTitle.text = [arr_CalendarTitle objectAtIndex:indexPath.row];
cell.lbl_CalendarSubTitle.text = [arr_CalendarSubTitle objectAtIndex:indexPath.row];
cell.lbl_calendarEventTime.text = [arr_CalendarEventTime objectAtIndex:indexPath.row];
cell.lbl_calendarDate.text = [arr_CalendarDate objectAtIndex:indexPath.row];
cell.lbl_CalendarMonth.text = [arr_CalendarMonth objectAtIndex:indexPath.row];
cell.lbl_CalendarYear.text = [arr_CalendarYear objectAtIndex:indexPath.row];
cell.img_BackGround.image = [UIImage imageNamed:#"calendar_Cell_Up.png"];
//here click event is occurred.
cell.btn_CollapseExpand.tag = indexPath.row;
[cell.btn_CollapseExpand addTarget:self action:#selector(method_Expand:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
ButtonPressed event calls
- (void)method_Expand:(UIButton*)sender
{
UITableViewCell *cell = (UITableViewCell *)sender.superview;
NSIndexPath *indexpath = [tbl_CalendarList indexPathForCell:cell];
[tbl_CalendarList moveRowAtIndexPath:[NSIndexPath indexPathForItem:indexpath.row inSection:indexpath.section] toIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexpath.section]];
int_SelectedIndex = sender.tag;
NSLog(#"Selected Button : %ld",(long)int_SelectedIndex);
if ( int_TempSelectedIndex != int_SelectedIndex)
{
int_TempSelectedIndex = int_SelectedIndex;
}
else
{
int_TempSelectedIndex = -1;
}
[tbl_CalendarList reloadData];
}
Resizing the cell
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == int_TempSelectedIndex )
{
cellSize = 300;
isRowSelected[indexPath.row] = YES;
}
else
{
cellSize = 100;
isRowSelected[indexPath.row] = NO;
}
return cellSize;
}
Now i got Like this in simulator.
when i pressed it comes like this.
This selected cell should come to first position.
You can scroll your table view to that cell and you can specify that you want to scroll it on top when you select the cell:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
Hope this is what you are after.
In the method_Expand method after fetching the selected row you have to remove the object at the selected index and insert the removed object at 0 index.
Also you want to move the move next item to the second position
For that you have to increment the selected index and check if that index is with in the array bounds then remove that item and add it to the index 1;
- (void)method_Expand:(UIButton*)sender
UITableViewCell *cell = (UITableViewCell *)sender.superview;
NSIndexPath *indexpath = [tbl_CalendarList indexPathForCell:cell];
int nextIndex=indexpath.row+1;
// first remove the object
NSString *str=[arr_CalendarTitle objectAtIndex];
[arr_CalendarTitle removeObjectAtIndex:indexpath.row];
[arr_CalendarTitle insertObject:str atIndex:0];
//do the same to arr_CalendarEventTime,arr_CalendarDate,arr_CalendarMont etc
if([arr_CalendarTitle count]-1<nextIndex)// check if nextIndex within bounds to avoid crash
{
// remove next object and addit to the index 1 to all array
}
[tbl_CalendarList reloadData];
}
As i wrote in the comments:
Upon selection, move selected arr_CalendarTitle entry to the top of array and call reloadData() on tableView. Table view displays data as is sorted in arr_CalendarTitle.
moveRowAtIndexPath is not enough, must resort the array too.
So, before reloadData() call (in button click method), do this:
id object = [[[arr_CalendarTitle objectAtIndex:int_SelectedIndex] retain] autorelease];
[arr_CalendarTitle removeObjectAtIndex:int_SelectedIndex];
[arr_CalendarTitle insertObject:object atIndex:0];
For ARC you can use :
__autoreleasing id object = [arr_CalendarTitle objectAtIndex:int_SelectedIndex];
[arr_CalendarTitle removeObjectAtIndex:int_SelectedIndex];
[arr_CalendarTitle insertObject:object atIndex:0];
Since you have more than one array that holds data (only noticed that now) you must do this for every array thats holds data for tableView cells.
Use below method to scroll your tableview.
[tableview setContentOffset:CGPointMake(0, button.tag*tableview.rowHeight) animated:YES];
This method will make tableview scroll to specified point.
Change value of Y in CGPointMake according to your code.
I'm trying to create a simple chat application for iOS. It currently looks like this:
I want to change the order that the messages is displayed, i.e. display the latest message over the older messages. My current implementation looks like this:
// Datasource for the tableView
messages = [[NSMutableArray alloc] init];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[...]
// messageTextView is a contentView subView.
messageTextView.text = [messages objectAtIndex:indexPath.row];
[...]
- (IBAction)sendMessage:(id)sender
{
[messages addObject:messageField.text];
messageField.text = #"";
[self.tableView reloadData];
/*
I have tried the implementation below, but I always got an exception.
[self.tableView beginUpdates];
NSIndexPath *path1 = [NSIndexPath indexPathForRow:1 inSection:0];
NSArray * indexArray = [NSArray arrayWithObjects:path1,nil];
[self.tableView insertRowsAtIndexPaths:indexArray
withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
*/
}
Any tips on how to do this would be great.
Thanks.
Simple you need to insert the new UITableViewCell at the 0 index. Also modify your Datasource otherwise your app will crash. Below I show how to modify your UITableView. Modifying your datasource is easy.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
What you are essentially doing here is inserting the new chat message cell at the 0th index position. You could use a nice animation effect to make it appear or fade in.
There are various animations that you can use here-
UITableViewRowAnimationBottom
UITableViewRowAnimationFade
UITableViewRowAnimationMiddle
UITableViewRowAnimationNone
UITableViewRowAnimationRight
UITableViewRowAnimationTop
You can try.
[messages insertObject:messageField.text atIndex:0];
instead of [messages addObject:messageField.text];.
Swift Solution
Define array like this
var arrMessage = [AnyObject]()
and Button Action
#IBAction func btnSendMessageTapped(sender: AnyObject) {
arrMessage.insert(txtTypeMessage.text!, atIndex: 0)
txtTypeMessage.text = ""
self.tblMessage.reloadData()
}