当用户按下Edit时,我的UITableView会在顶部添加一个插入行(带有绿色加号),并将所有其他行放入删除模式(红色减号)。或者,用户可以在不按下编辑按钮的情况下滑动到删除。我正在使用几个Ivars来跟踪表格是否处于编辑模式,从轻扫或按下编辑按钮,并相应地采取行动(例如,在按下编辑时更新numberOfRowsInTableView:使用额外的插入行)。 / p>
除了东西之外,一切都很完美:当处于编辑模式时(即用户明确点击编辑按钮,插入行出现在顶部),如果用户尝试删除底行,则下一行被删除了。删除任何其他行都没问题。
编辑 - 出现以删除上面的行,但如果我立即退出并重新加载应用程序,则结果显示最后一行已经消失。所以我猜我的UITableView与某个地方的NSFetchedResultsController不同步。
这是我正在使用的代码:
#import "ChecklistsViewController.h"
#import "Checklist.h"
@interface ChecklistsViewController (private)
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
- (void)addingView;
@end
@implementation ChecklistsViewController
@synthesize category, managedObjectContext, fetchedResultsController;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
editingFromSwipe = NO;
tableIsEditing = NO;
}
return self;
}
- (void)dealloc
{
[category release];
[managedObjectContext release];
[fetchedResultsController release];
[super dealloc];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
editingFromSwipe = NO;
tableIsEditing = NO;
self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.tableView.allowsSelectionDuringEditing = YES;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
int rows = [sectionInfo numberOfObjects];
if (self.editing) {
if (!editingFromSwipe && tableIsEditing) {
return rows +1;
}
return rows;
}
tableIsEditing = NO;
return rows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSLog(@"Should go into if statement here! \n");
if (tableView.editing) { //
if ((indexPath.row == 0) && (!editingFromSwipe)) {
NSLog(@"Configuring Add Button Cell while editing \n");
cell.textLabel.text = @"Add New Checklist";
cell.detailTextLabel.text = nil;
}
else {
NSLog(@"Configuring other cells while editing \n");
[self configureCell:cell atIndexPath:indexPath];
}
}
else {
NSLog(@"Configuring Cell Normally While Not Editing \n");
[self configureCell:cell atIndexPath:indexPath];
}
return cell;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
int numberOfRows = [self tableView:tableView numberOfRowsInSection:indexPath.section];
int rowBeingDeleted = indexPath.row +1;
if (tableIsEditing && !editingFromSwipe && numberOfRows == rowBeingDeleted) {
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]]];
}
else {
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
}
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
// TO DO: Fix error code.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
[self addingView];
}
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
int row = indexPath.row;
if (self.editing && row == 0) {
if (!editingFromSwipe && tableIsEditing) {
return UITableViewCellEditingStyleInsert;
}
else if (editingFromSwipe) {
return UITableViewCellEditingStyleDelete;
}
}
return UITableViewCellEditingStyleDelete;
}
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
editingFromSwipe = YES;
[super tableView:tableView willBeginEditingRowAtIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
[super tableView:tableView didEndEditingRowAtIndexPath:indexPath];
editingFromSwipe = NO;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
NSArray *addRow = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], nil];
[self.tableView beginUpdates];
if (!editingFromSwipe) {
if (editing) {
tableIsEditing = YES;
[self.tableView insertRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
else {
[self.tableView deleteRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
}
[self.tableView endUpdates];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row != 0) {
TO DO: Code for when row is selected
}
}
#pragma mark - Data
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Checklist *aChecklist = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = aChecklist.name;
cell.detailTextLabel.text = aChecklist.category.name;
}
- (void) addingView// :(id)sender
{
AddingViewController *viewController = [[AddingViewController alloc] initWithNibName:@"AddingViewController" bundle:nil];
viewController.delegate = self;
viewController.title = @"Add Checklist";
// Create the navigation controller and present it modally
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[self presentModalViewController:navigationController animated:YES];
viewController.textLabel.text = @"Enter new checklist name";
[navigationController release];
[viewController release];
}
#pragma mark - AddingViewDelegate
- (void)addingViewController:(AddingViewController *)addingViewController didAdd:(NSString *)itemAdded
{
if (itemAdded != nil) {
// Turn off editing mode.
if (self.editing) [self.navigationController setEditing:NO animated:NO];
// Add the category name to our model and table view.
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Checklist *newChecklist = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[category addChecklistsObject:newChecklist];
newChecklist.name = itemAdded;
// [newChecklist setDateStamp:[NSDate date]];
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
TO DO: fix error code.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Checklist" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set 4* the predicate so we only see checklists for this category.
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:@"category.name = %@", self.category.name];
[fetchRequest setPredicate:requestPredicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
// TO DO: error stuff
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return fetchedResultsController;
}
#pragma mark - Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)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)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
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 configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
@end
答案 0 :(得分:2)
您可以向UITableViews添加静态单元格,以便从NSFetchedResultsController获取数据。但要做到这一点,您必须调整几乎所有在UITableViewDelegate
,UITableViewDataSource
或NSFetchedResultsControllerDelegate
方法中使用的NSIndexPath。
我添加了一些帮助方法,将tableview的索引路径转换为获取的resultcontroller的索引路径,反之亦然。如果你想在顶部添加一行,可以使用这样的东西:
- (NSIndexPath *)tableIndexPathFromNSFRCIndexPath:(NSIndexPath *)ip {
if (editingMode && ip.section == 0) {
NSIndexPath *newIP = [NSIndexPath indexPathForRow:ip.row+1 inSection:ip.section];
return newIP;
}
return ip;
}
- (NSIndexPath *)nsfrcIndexPathFromTableIndexPath:(NSIndexPath *)ip {
if (editingMode && ip.section == 0) {
NSIndexPath *newIP = [NSIndexPath indexPathForRow:ip.row-1 inSection:ip.section];
return newIP;
}
return ip;
}
然后你必须改变将indexpath从表传递给fetchedresultscontroller或从frc传递到表的每个方法。我举两个例子。
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
newIndexPath = [self tableIndexPathFromNSFRCIndexPath:newIndexPath];
indexPath = [self tableIndexPathFromNSFRCIndexPath:indexPath];
switch(type) {
case NSFetchedResultsChangeInsert:
[self.listTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.listTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[self.listTableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[self.listTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.listTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[aTableView deselectRowAtIndexPath:indexPath animated:YES];
if (editingMode && indexPath.section == 0 && indexPath.row == 0) {
// Add New entry...
}
else {
indexPath = [self nsfrcIndexPathFromTableIndexPath:indexPath];
NSManagedObject *selectedObject = [self.fetchedResultsController objectAtIndexPath:indexPath]);
}
}
答案 1 :(得分:1)
你的设计有问题。您不应该在任何地方添加行,而是在逻辑上放在表中的位置。
获取结果控制器(FRC)的整个点是使tableview与数据同步。表中行的顺序应反映fetchedObjects数组中托管对象的顺序。通过在底部或顶部插入一行,同时添加一个逻辑上不属于表格顶部或底部的对象,可以打破同步。
当您在addingViewController:didAdd:
中添加新的托管对象时,FRC会警告它委托尝试重绘表的代理。你试图弥补这一点,但你真的不能。你所有的索引都会消失。
而不是使用行来输入新行。使用tableview页眉或页脚视图。这样,您可以冻结tableview,创建新对象,然后更新表,新对象将显示其逻辑在表中所属的位置。
答案 2 :(得分:1)
为了不让行混淆,我建议将插入行放在自己的部分中。由于您显然只是使用一个部分,因此您知道发送给FRC的部分应始终为0.代码将如下所示:
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]];
TechZen的解决方案也可以使用,因此您选择的解决方案完全取决于您喜欢的设计。 TechZen的解决方案不会影响多个部分,但可以修改此解决方案以支持多个部分。