I'm trying to make my table add a cell at the bottom of the table for adding cells, my table is on delete mode, so people can delete, but then then i need it to add a cell with insert that will insert a cell. i get errors saying my app isn't consistent with the nsarray. how could i make this work?
Step 1 override setEditing on your tableViewController to insert or delete the add row
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
BOOL prevEditing = self.editing;
[super setEditing:editing animated:animate];
[tableView setEditing:editing animated:animate];
if (editing && !prevEditing) {
// started editing
[self.tableView insertRowsAtIndexPaths:....] withRowAnimation:UITableViewRowAnimationFade];
} else if (!editing && prevEditing) {
// stopped editing
[self.tableView deleteRowsAtIndexPaths:....] withRowAnimation:UITableViewRowAnimationFade];
}
}
Then ensure you are returning the right number of rows
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger numberOfRows = xxxxxx;
if (self.editing) {
numberOfRows++;
}
return numberOfRows;
}
Related
I have a table view with custom cells (all configured in a subclass using auto layout).
The cells load fine, display fine, everything is fine.
The issue is when I am inserting more rows (at the bottom). The table view is representing a feed for posts, so when the user scrolls to the bottom, before reaching the last cell, I load new posts, and then insert them into the table.
When I do this, I get this weird glitchy effect where the cells randomly come down (behind the previous cells) into place, the table view scrolls up a bit, messy.
CODE AT BOTTOM
I've uploaded a clip of me scrolling. When you see the activity indicator,
I stop scrolling. The rest of the movement is from the glitchy behavior.
Is the reason for the glitch because the cells are being drawn with auto-layout?
I would hope not, but idk..I'm not sure what to do regarding a solution. If anyone has any ideas please let me know.
FYI:
I have this (of course, since the cells are all using auto layout)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
I've tried setting the estimated height to an "average" of the expected cell heights, around 65. No difference.
Update
Here's some code:
HomeViewController.m --> viewDidLoad
...
self.tableView = [KATableView.alloc initWithFrame:CGRectZero style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.refreshDelegate = self;
self.tableView.estimatedRowHeight = 75;
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.view addSubview:self.tableView];
// Constrains to all 4 sides of self.view
[SSLayerEffects constrainView:self.tableView toAllSidesOfView:self.view];
my table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (!self.dataManager.didFinishFetchingData) return 4;
if (self.contentObjects.count == 0) return 1;
if (self.dataManager.moreToLoad) return self.contentObjects.count + 1;
return self.contentObjects.count + 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MYObject *object = self.contentObjects[indexPath.row];
SomeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:object.documentID];
if (!cell) {
cell = [SomeTableViewCell.alloc initWithStyle:UITableViewCellStyleDefault reuseIdentifier:object.documentID];
cell.delegate = self;
} else [cell startListeningForChanges];
return cell;
}
Here is how I am loading more data and adding it to the table view..
- (void)getHomeFeedData:(nullable void(^)(BOOL finished))completed {
[self.dataManager fetchHomeFeedDataForFeedOption:self.homeNavController.feedFilterOption completion:^(NSError * _Nullable error, NSArray<__kindof KAObject *> * _Nullable feedObjects) {
if (error != nil) {
NSLog(#"something went wrong: %#", error.localizedDescription);
if (completed) completed(NO);
return;
}
NSInteger originalCount = self.contentObjects.count;
if (self.dataManager.isFirstTimeLoading) self.contentObjects = feedObjects.mutableCopy;
else {
if (self.dataManager.isGettingNew) for (MYObject *obj in feedObjects) [self.contentObjects insertObject:obj atIndex:0];
else if (feedObjects.count > 0) [self.contentObjects addObjectsFromArray:feedObjects];
}
if (feedObjects.count > 0) {
if (self.dataManager.isFirstTimeLoading) [self.tableView reloadData];
else {
[self.tableView insertCells:feedObjects forSection:0 startingIndex:self.dataManager.isGettingNew? 0 : originalCount];
}
} else if (self.dataManager.isFirstTimeLoading) [self.tableView reloadData];
if (completed) completed(YES);
}];
}
NOTE:
[self.tableView insertCells:feedObjects forSection:0 startingIndex:self.dataManager.isGettingNew? 0 : originalCount];
is simply this:
- (void)insertCells:(nullable NSArray *)cells forSection:(NSInteger)section startingIndex:(NSInteger)start {
if (!cells) return;
NSMutableArray *indexPaths = #[].mutableCopy;
for (id obj in cells) {
NSInteger index = [cells indexOfObject:obj] + start;
[indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:section]];
}
[self insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}
Update 2
My UITableViewCell subclass content is hidden ATM (too much difficulty in editing all my post content for the purpose of this post). I just have the subviews of each cell set to alpha = 0.f. It's just an image view, some labels, and some buttons.
No constraint issues in console, cells render perfectly when calling [self.tableView reloadData] so maybe there is something I'm doing wrong when inserting the cells?...
When you dealing with UITableView glitches:
Make sure you call UIKit API's on a main thread - turn on Main Thread checker
In your case, there might be an issue that fetchHomeFeedDataForFeedOption:completion: completion block is called not on a main thread.
Your insert is definitely wrong - all delete/insert/update/move calls for UITableView should be wrapped in beginUpdates/endUpdates
Your "load more" component at the bottom might be an issue. You need to address how it's managing contentSize/contentOffset/contentInset of table view. If it does anything but manipulating contentInset - it does wrong job.
While it's hard without debugging the whole solution, I bet options 2 & 3 are the key problems out there.
I have created expanded tableview using plist, I have populated all the values and images into my tableview. Here I want to make animation for my particular cell when user click that cell.
For example, first time images will be default, next time if I click that cell, the particular cell get expanded. Here I need to make my image has hide; again if user press it will show the image. How to do this?
Every time the user taps a cell, a UITableViewDelegate method gets called, and in this method you can reload the cell:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
When a cell is reloaded, the method below is called (between the others), and the only thing you have to do is to return a different height according with the current state of the cell:
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return (/* cell state */) ? 100.0 : 50.0;
}
You have multiple ways to store your cell's state, you can do it in its model for example.
There are other ways also, this is only a solution. But the key point is that you have to reload the cell, and return a different height according with the conditions you want to consider.
You can check my full solution in the following github
Here is some of my implementation.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
movies = [[NSArray alloc] initWithObjects:
(NSArray*)[[Section alloc ] init:#"Istanbul" movieNames:#[#"Uskudar", #"Sariyer"] isExpanded:false],
(NSArray*)[[Section alloc ] init:#"Bursa" movieNames:#[#"Osmangazi", #"Mudanya", #"Nilufer"]
isExpanded:false],
(NSArray*)[[Section alloc ] init:#"Antalya" movieNames:#[#"Alanya", #"Kas"] isExpanded:false], nil
];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return movies.count;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return ((Section*)movies[section]).movies.count ;
}
-(void)toggleSection:(ExpandableHeaderFoorterView *)headerView withSection:(int)section
{
((Section*)movies[section]).expanded = !((Section*)movies[section]).expanded;
[expandableTableView beginUpdates];
for (int i= 0; i< ((Section*)movies[section]).movies.count; i++)
{
NSArray* rowsToReload = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:i inSection:section], nil];
[expandableTableView reloadRowsAtIndexPaths:rowsToReload
withRowAnimation:UITableViewRowAnimationFade];
}
[expandableTableView endUpdates];
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
ExpandableHeaderFoorterView* headerView = [[ExpandableHeaderFoorterView alloc] initWithCustom:((Section*)movies[section]).genre withSection:(int)section withDelegate:self];
return (ExpandableHeaderFoorterView*)headerView;
}
http://www.iostute.com/2015/04/expandable-and-collapsable-tableview.html
You can see this tutorial. It's a very good tutorial based on expandable tableview cell.
I am implementing swipe to delete feature on one of my table that uses custom table view cells. The issue I am facing is when I tap on "Delete" button, I see a weird transition while cell is being removed.
Below is my code in "commitEditingStyle" and also see the attached screenshot I captured while row is being removed.
PS: I have tried with all types of row removal animation styles but no luck.
- (void)tableView:(UITableView *)iTableView commitEditingStyle:(UITableViewCellEditingStyle)iEditingStyle forRowAtIndexPath:(NSIndexPath *)iIndexPath {
if (iEditingStyle == UITableViewCellEditingStyleDelete && iTableView == self.temporaryCartTable) {
if (self.temporaryCartTable.frame.size.height == kMyAppCartTableViewExpandedHeight) {
[self.temporaryCartTable beginUpdates];
[self.temporaryCartTable deleteRowsAtIndexPaths:[NSArray arrayWithObjects: iIndexPath, nil] withRowAnimation:UITableViewRowAnimationAutomatic];
MyAppCartInfo *aMyAppCartInfo = [self cart];
if (([[aMyAppCartInfo.tempProducts allKeys] count] > iIndexPath.row) && [aMyAppCartInfo.tempProducts containsObjectForKey:[[aMyAppCartInfo.tempProducts allKeys] objectAtIndex:iIndexPath.row]]) {
[aMyAppCartInfo.tempProducts removeObjectForKey:[[aMyAppCartInfo.tempProducts allKeys] objectAtIndex:iIndexPath.row]];
}
[self.temporaryCartTable endUpdates];
}
}
}
- (BOOL)tableView:(UITableView *)iTableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return (iTableView == self.temporaryCartTable) ? YES : NO;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)iTableView editingStyleForRowAtIndexPath:(NSIndexPath *)iIndexPath {
return UITableViewCellEditingStyleDelete;
}
- (CGFloat)tableView:(UITableView *)iTableView heightForRowAtIndexPath:(NSIndexPath *)iIndexPath {
if (iTableView == self.temporaryCartTable) {
if (self.isTempCartCell)
return 92.0;
else
return 58.0;
} else {
return 58.0;
}
}
Add the following method implementation to your UITableViewDelegate
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
Use [self.temporaryCartTable setRowHeight:xxx] instead of using the delegate rowHeightAtIndexPath. After the row is deleted and there is no other rows in the table, the delegate will not called and your table view's row height will be retrieved by its property (hence the default height). Plus, using this method from the delegate affects the performance.
If anyone else is having this, the problem in my case was heightForRowAtIndexPath: When cells have dynamic size delete animation behaves strange. In my case the solution was to use a different approach and not spend time investigating. But you can test it and see results. Assign a fixed height for cells:
self.tableView.rowHeight = Constant;
comment out heightForRowAtIndexPath:. And animations should be ok now.
I know this is not the solution but may still help someone.
I have a table view whose cells contain a UISwitch. For one of them, when the switch is turned on, I'd like to show an additional row in the table view. I'm doing this now by setting conditionals in my tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: methods and calling reloadData in the switch's event handler. This works fine. However, I'd like to animate the table cell in or out, and I can't figure out how to work this into the existing table view insert/remove animation API.
The code I have:
- (IBAction)didToggleContractProperties:(id)sender {
UISwitch *toggle = (UISwitch *)sender;
switch (toggle.tag) {
case 100: // Packaging requested
self.contract.isPackingRequired = toggle.on;
[self.tableView reloadData];
break;
case 101: // On-campus pickup
self.contract.isOnCampus = toggle.on;
break;
case 102: // Insurance requested
self.contract.isInsuranceRequested = toggle.on;
break;
default:
break;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (section == 0) {
if (self.contract.isPackingRequired)
return 4;
else
return 3;
}
else if (section == 1)
return 1;
else
return 1;
}
Thanks!
This is how you might do it for the isPackRequired switch, for example:
- (IBAction)didToggleContractProperties:(id)sender {
UISwitch *toggle = (UISwitch *)sender;
switch (toggle.tag) {
case 100: // Packaging requested
if (self.contract.isPackingRequired != toggle.on)
{
self.contract.isPackingRequired = toggle.on;
if (toggle.on)
{
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:3 inSection:0]]
withRowAnimation:UITableViewRowAnimationMiddle];
}
else
{
[self.tableView deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:3 inSection:0]]
withRowAnimation:UITableViewRowAnimationMiddle];
}
}
break;
case 101: // On-campus pickup
...
default:
break;
}
}
Obviously, change the row and section to be the appropriate values for the insertRowsAtIndexPaths and dleeteRowsAtIndexPaths calls, to reflect where you want this row inserted/deleted.
The way to add table cells animated is this
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationRight];
where tableView is your table view, and indexPaths is a mutable array containing the index paths for the new rows.
When your switch gets on then just add up your array with addition items or condition in your case and then just call this method to intended your section. This will give in/out effect to display less and more items to tableview.
[mytv reloadSections: [NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
I'm adding a row to a UITableView section when the user switches the table to editing mode with this code:
- (void) setEditing:(BOOL) editing animated:(BOOL) animated {
[super setEditing:editing animated:animated];
if (editing) {
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:SECTION_SUPPLIERS]] withRowAnimation:UITableViewRowAnimationFade];
}
}
The object being to create a "Add new supplier ..." row. This works, however when the table wants to get the editing style so it can add a plus or minus icon, I have a problem.
- (UITableViewCellEditingStyle) tableView:(UITableView *) tableView editingStyleForRowAtIndexPath:(NSIndexPath *) indexPath {
return ([self.tableView isEditing] && indexPath.row == 0) ? UITableViewCellEditingStyleInsert : UITableViewCellEditingStyleDelete;
}
The sequence seems to go like this:
The table switches to editing. There is only one supplier row at this time so it queries for a editing style with row == 0.
The insert of the new row is done which then triggers another query for an editing style again with a row == 0.
So if I use this sequence and use the row number and tableView isEditing to decide on the editing style icon I end up with both the Add new and current supplier rows with plus icons on them.
What is the best way to insert a row and assign the appropriate editing style - plus for the add new row, and minus for any other row?
Have you tried putting your "Add new..." row at the bottom instead of at the top? Instead of checking for [indexPath row] == 0 you instead check for [indexPath row] == [items count], and your tableView:numberOfRowsInSection: implementation looks like this:
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
int numberOfRows = [items count];
if ([self isEditing]) {
numberOfRows++;
}
return numberOfRows;
}
This is how the example in iPhone Programming: The Big Nerd Ranch Guide works.