I need some help with UITableView. I'm looking for the best solution for creating edit table functionality. I have UITableViewController with data and two modes:
Edit mode: All fields (like first name, last name, phone, web page etc...)
View mode: Show only filed rows.
The difficult thing is to animate the rows when a user clicks the edit button.
I want the same animation we have in the address book app on iPhone.
Something like this in simple.
//original position and maybe hidden
someView.frame = CGRect(0,0,0,0);
someView.alpha = 0.0f;
[someView setHidden:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
//what you want it comes out finally, changed position and visible
[someView setHidden:NO];
someView.frame = CGRect(100.0f,100.0f,0,0);
someView.alpha = 1.0f;
[UIView commitAnimations];
Use
[tableView setEditing: YES animated: YES];
to make the UItableView in editing mode.Which give you show and hide the button when you swipe on the row.if your animation is something please let me know.
I have solution!
Help from this article
So you need add something like this (thanks Sandy)
- (void)setEditing:(BOOL)editing animated:(BOOL)animated{
[super setEditing:editing animated:animated];
NSArray *paths = [NSArray arrayWithObject:
[NSIndexPath indexPathForRow:1 inSection:0]];
if (editing) {
[[self tableView] insertRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop];
}else {
[[self tableView] deleteRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop];
}
}
After that
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
will be colled, and you can edit list of cells and their appearance.
in the case below, I have 2 sections, first section contains a series of settings, first row is a default setting, always there and can't be reordered, and second one contains just one row like 'Add...', editing is only for reordering and deleting so in edit mode I remove the first setting, and remove the 2nd section, and it animates smoothly if you club all your insert/delete within a beginUpdates/endUpdates on the tableview. So for you it would be just the opposite, adding more rows/sections when editing
In Normal mode I have :
Countries (<-- 1st section)
- World
- Argentina
- USA
(<-- 2nd section)
- Add Countries...
In Edit mode I have :
Countries (<-- 1st section)
- Argentina = (can be removed/reordered)
- USA = (can be removed/reordered)
Code looks like:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
NSArray *paths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
nil];
[self.tableView beginUpdates]; // Club all updates together
if (editing)
{
if( [[CCPollSettings countryCodes] count] < 2) // No country setting
// Remove complete section
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
else // Remove first default row
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
// Remove 'Add...' Section
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
// .... Do more stuff after
} else {
if( [[CCPollSettings countryCodes] count] < 2) // No country setting yet
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
else // add back default row
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
// Add back 'Add...' Section
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates]; // Club all updates together
}
Related
I have a UITableViewController that is populated dynamically depending on a previous selection by the user. In some cases, the user should be able to select multiple rows in the table but in other cases he should not. I want to automatically select the first row in either case, but I have found that it only works when I have allowsMultipleSelection = YES.
The following code is called during -(void)viewDidLoad
switch(self.field)
{
case people:
self.options = (NSArray *)[data objectForKey:#"People"];
self.tableView.allowsMultipleSelection = YES;
enter code here break;
case place:
self.options = (NSArray *)[data objectForKey:#"Places"];
self.tableView.allowsMultipleSelection = NO;
break;
case reason:
self.options = (NSArray *)[data objectForKey:#"Reasons"];
self.tableView.allowsMultipleSelection = NO;
break;
}
[self.tableView reloadData];
NSIndexPath *zeroPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView selectRowAtIndexPath:zeroPath animated:NO scrollPosition:UITableViewScrollPositionNone];
This works correctly for the "People" case but not for the others. I have tried setting allowMultipleSelection to YES and that solves the problem of the selection, but it is not acceptable in terms of the application logic.
Am I missing something about how these functions work? How can I programmatically select a row in a single selection table?
Fixed with the following from the template:
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = NO;
Still not sure exactly why it didn't work before.
for (NSIndexPath *indexPath in [self.tableView indexPathsForSelectedRows]) {
[self.tableView deselectRowAtIndexPath:indexPath animated:NO];
}
[self.tableView selectRowAtIndexPath:zeroPath animated:NO scrollPosition:UITableViewScrollPositionNone];
I am adding a new item to the bottom of a UITableView and after inserting the item, I want the UITableView to scroll to the very bottom to display the newly inserted item. New items are saved to Core Data and the UITableView is automatically updated using NSFetchedResultsController.
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
switch (type) {
case NSFetchedResultsChangeInsert:
NSLog(#"*** controllerDidChangeObject - NSFetchedResultsChangeInsert");
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
//THIS IS THE CODE THAT DOESN'T WORK
[self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
break;
....
}
This leads to an out of bounds error, I can't seem to make it work. I can scroll to the second to last comment by adjusting the row of the index path, but I can't get to the very last item.
Basically, I'm adding a comment to a table of comments and after a comment is added, I want the table to scroll to the most recent comment.
You need to call endUpdates so that the tableView can calculate its new sections and rows. A simple case would look like this:
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:insertedIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:insertedIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
As you use NSFetchedResultsController, it is a bit more complicated, as the calls do beginUpdates, insertRowsAtIndexPaths:withRowAnimation:, and endUpdates are typically in different delegate methods. What you could do then is
add a property insertedIndexPath to store the inserted index path
after the -insertRowsAtIndexPaths:withRowAnimation: call in -controller:didChangeObject:atIndexPath:, add
self.insertedIndexPath = insertedIndexPath;
after [self.tableView endUpdates] in -controllerDidChangeContent:, add
if (self.insertedIndexPath) {
[self.tableView scrollToRowAtIndexPath:self.insertedIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
self.insertedIndexPath = nil;
}
See if this helps...
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
I want to reload only one section not the full table. Is there any method in UITableView.
[tableView reloadData] is used to load full table.
I want to know how to load only one section, as I have large number of rows in the table.
The reloadSections method bugs me -- as I have to construct a few objects. This is great if you need the flexibility, but sometimes I also just want the simplicity too. It goes like this:
NSRange range = NSMakeRange(0, 1);
NSIndexSet *section = [NSIndexSet indexSetWithIndexesInRange:range];
[self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationNone];
This will reload the first section. I prefer to have a category on UITableView and just call this method:
[self.tableView reloadSectionDU:0 withRowAnimation:UITableViewRowAnimationNone];
My category method looks like this:
#implementation UITableView (DUExtensions)
- (void) reloadSectionDU:(NSInteger)section withRowAnimation:(UITableViewRowAnimation)rowAnimation {
NSRange range = NSMakeRange(section, 1);
NSIndexSet *sectionToReload = [NSIndexSet indexSetWithIndexesInRange:range];
[self reloadSections:sectionToReload withRowAnimation:rowAnimation];
}
Yes, there is:
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
But you can reload only sections which contain same number of rows (or you have to manually add or remove them). Otherwise you will get:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 2. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Which is not required when you use [tableView reloadData].
When you need to reload a section and you have changed number of rows inside it, you could use something like this:
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[self beginUpdates];
[self deleteSections:indexSet withRowAnimation:rowAnimation];
[self insertSections:indexSet withRowAnimation:rowAnimation];
[self endUpdates];
If you put it in a category (like bandejapaisa shows) it could look like this:
- (void)reloadSection:(NSInteger)section withRowAnimation:(UITableViewRowAnimation)rowAnimation {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[self beginUpdates];
[self deleteSections:indexSet withRowAnimation:rowAnimation];
[self insertSections:indexSet withRowAnimation:rowAnimation];
[self endUpdates];
}
For Swift 3, 4 and 5
let sectionToReload = 1
let indexSet: IndexSet = [sectionToReload]
self.tableView.reloadSections(indexSet, with: .automatic)
that the correct way:
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
Based on the accepted answer here, I made a function that will reload all sections in the table using an animation. This could probably be optimized by reloading only visible sections.
[self.tableView reloadData];
NSRange range = NSMakeRange(0, [self numberOfSectionsInTableView:self.tableView]);
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range];
[self.tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationFade];
In my case, I had to force a reloadData before the section animation, because the underlying data for the table had changed. It animates properly however.
You need this... For Reload Row
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
or For Reload section
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
Here is the method, you can pass section details in different ways
[self.tableView reloadSections:[[NSIndexSet alloc] initWithIndex:1] withRowAnimation:NO];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone];
Reloading particular sections improves performance for the table view as well some time it also avoid some issues like floating/moving custom headers-footers in your view. SO try to use reloadSection than relaodData whenever possible
Try to use
[self.tableView beginUpdates];
[self.tableView endUpdates];
Hope this will solve your issue.
But you can reload only sections which contain same number of rows (or you have to manually add or remove them). Otherwise you will get an NSInternalInconsistencyException.
Steps:
calculate which rows to remove and/or insert
generate an IndexPath array from these
call related tableView methods
now you can safely call reloadSections :) Reload section will call update for the rest of the indexes.
Or you can use a library like : https://github.com/onmyway133/DeepDiff
Swift pseodo code:
tableView.deleteRows(at: valueIndexesToRemove, with: .automatic)
tableView.insertRows(at: valueIndexesToInsert, with: .automatic)
tableView.reloadSections(IndexSet([section]), with: .automatic)
If you have custom section view you can add a weak reference to it in your view controller and update it whenever you want. Here is my code for reference:
#property (weak, nonatomic) UILabel *tableHeaderLabel;
....
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UITableViewHeaderFooterView *myHeader = [[UITableViewHeaderFooterView alloc] init];
UILabel *titleLabel = [[UILabel alloc] init];
[titleLabel setFrame:CGRectMake(20, 0, 280, 20)];
[titleLabel setTextAlignment:NSTextAlignmentRight];
[titleLabel setBackgroundColor:[UIColor clearColor]];
[titleLabel setFont:[UIFont systemFontOfSize:12]];
[myHeader addSubview:titleLabel];
self.tableHeaderLabel = titleLabel; //save reference so we can update the header later
return myHeader;
}
Then later on you can update your section like this:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
self.tableHeaderLabel.text = [NSString stringWithFormat:#"Showing row: %ld", indexPath.row];
}
I am having an issue where when i have two sections let say section x and section. Section x has one row and also section y. If i delete a row in section y which is the second one, it will delete the row in section x and keeping the row in section y also it leave a space when i hide the section if their is a way to hide the section which wont make a mess please tell me a way here are some picture that might let u understand my question
and here is the code i use to delete a row
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
int path;
path = indexPath.row;
[data removeObjectAtIndex:path];
if([data count] == 0)
{
UIBarButtonItem *leftbutton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editTable)];
self.navigationItem.rightBarButtonItem = leftbutton;
[leftbutton release];
[self.navigationItem.rightBarButtonItem setEnabled:FALSE];
[mainTableView setHidden:TRUE];
}
else
{
[self.navigationItem.rightBarButtonItem setEnabled:TRUE];
[mainTableView setHidden:FALSE];
}
[self saveData];
[mainTableView reloadData];
}
To remove a section when you have deleted all rows belonging to it, call - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation & return one less from - (NSInteger)numberOfSections.
You haven't added any code that shows how you are deleting a row from the table (removing it from data isn't sufficient). Try something like-
[self.tableView beginUpdates];
[data removeObjectAtIndex:path];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
Well at the beginning i wasn't able to solve the issue but with the help of #Akshay my first problem was while deleting it always delete the section on top the section i was deleting to solve the issue i just had to update the number of rows in a section using
[mainTableView reloadData]; before the return expression
My application have UI similar to Phone.app->Recents: sectioned UITableView and a UISegmentedControl in the navigation bar. What I want to do is display full set of data if first section is selected and display filtered set of data if second section is selected.
When user selects second item in UISegmentedControl I delete specific rows from the table view. Here is the code:
[tableView beginUpdates];
NSMutableArray *indexPaths = [NSMutableArray array];
/// ... fill up indexPaths with row indexes
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
The code above works fine except for one serious issue: performance. Deleting 1500 out of 2200 rows takes about 20 seconds. That is obviously unacceptable. What is the best approach to filtering table view rows with animation?
For large changes to your data source, it is recommended that you use
[tableView reloadData]
instead of
[tableView beginUpdates];
// changes here ....
[tableView endUpdates];
EDIT: I haven't tried this approach myself, but consider altering only those rows that are contained in the collection of visible cells, perhaps with a buffer above and below. You can get the indexPaths of the visible cells by calling
[tableView indexPathsForVisibleRows];
How about using a two arrays? One of them is the full data set and the other one is the filtered dataset.
This way you can have two different tableviews, and depending on what the selected segment is, you could do a fade animation between the two tableviews. For example, lets say you select a segment:
-(void)switchTableViews
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDidStopSelector:#selector(hideTableView)];
switch (segmentedControl.selectedSegmentIndex)
{
case 0:
{
tableView1.alpha = 1.0;
tableView2.alpha = 0.0;
}
break;
case 1:
{
tableView1.alpha = 0.0;
tableView2.alpha = 1.0;
}
break;
default:
break;
}
[UIView commitAnimations];
}
- (void)hideTableView
{
switch (segmentedControl.selectedSegmentIndex)
{
case 0:
{
tableView1.hidden = NO;
tableView2.hidden = YES;
}
break;
case 1:
{
tableView1.hidden = YES;
tableView2.hidden = NO;
}
break;
default:
break;
}
}
Of course, this would mean setting up different code sets for the datasource methods but it's not as difficult as you think. Just use a simple if-else to check for which tableview is being set.
I definitely agree with #nduplessis that you should reload the dataSource rather than manipulating the view. I proposed a solution to a similar question here that would indeed cause your rows to slide up.
The basic idea is to call reloadSections:withRowAnimation: and in your UITableViewDataSource methods switch on the segmented control's selectedSegmentIndex.
Assuming your data is flat (only one section) it would look something like this:
- (IBAction)segmentSwitch:(id)sender
{
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch (self.segmentedControl.selectedSegmentIndex)
{
default:
case 0:
return [self.allRows count];
case 1:
return [self.onlySomeRows count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id data;
switch (self.segmentedControl.selectedSegmentIndex)
{
default:
case 0:
data = [self.allRows objectAtIndex:[indexPath row]];
break;
case 1:
data = [self.onlySomeRows objectAtIndex:[indexPath row]];
break;
}
//TODO: use data to populate and return a UITableViewCell...
}