UITableView在编辑模式 - 按删除使我的应用程序崩溃 [英] UITableView In Edit Mode - Pressing Delete Makes My App Crash

查看:174
本文介绍了UITableView在编辑模式 - 按删除使我的应用程序崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的UITableView有一个编辑按钮,添加一个插入行(绿色加号)。如果我尝试在编辑模式下删除一行,我的应用程序崩溃与NSRangeException。在不处于编辑模式(使用滑动到删除)的情况下,删除行很好。



我知道这是关于表视图认为有多少行 - 获取一个插入行和滑动到删除两者在同一个表中工作是一个噩梦(对于像我这样的初学者),因为刷卡和按下编辑按钮将表放在编辑模式,但编辑按钮添加



我一直在尝试使用几个Ivars来克服这个问题,但显然我在某个地方出了错。有经验的人能告诉我我该走错什么吗? (我敢肯定我会这样做太复杂的一种方式!)。



编辑 - 根据主要Izzy的要求,我把我的整个未编辑.m文件。

  // 
// ChecklistsViewController.m
//复选框
//
//由Ric于18/03/2011创建。
//版权所有2011 __MyCompanyName__。版权所有。
//

#importChecklistsViewController.h
#importChecklist.h

@interface ChecklistsViewController(private)
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
- (void)addingView;
@end


@implementation ChecklistsViewController

@synthesize类别,managedObjectContext,fetchedResultsController;


- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if(self){
editingFromSwipe = NO;
tableIsEditing = NO;
NSLog(@tableIsEditing = NO(init)\\\
);
NSLog(@不从编辑swipe(init)\\\
);
}
return self;
}

- (void)dealloc
{
[category release];
[managedObjectContext release];
[fetchedResultsController release];
[super dealloc]
}

- (void)didReceiveMemoryWarning
{
//如果没有超级视图,则释放视图。
[super didReceiveMemoryWarning];

//释放所有未使用的缓存数据,图像等。
}

#pragma mark - 查看生命周期

- (void)viewDidLoad
{
[super viewDidLoad];

editingFromSwipe = NO;
tableIsEditing = NO;
NSLog(@tableIsEditing = NO(viewDidLoad)\\\
);
NSLog(@不通过滑动编辑(viewDidLoad)\\\
);

//取消注释以下行以保留演示文稿之间的选择。
// self.clearsSelectionOnViewWillAppear = NO;

self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.tableView.allowsSelectionDuringEditing = YES;
}


- (void)viewDidUnload
{
[super viewDidUnload];
//放弃可以在viewDidLoad或按需重新创建的任何内容的所有权。
//例如:self.myOutlet = nil;
}


- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
//对于支持的方向返回YES
return(interfaceOrientation == UIInterfaceOrientationPortrait);
}


#pragma mark - 表视图数据源

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(@numberOfRowsInSection HAS BEEN CALLED);
id< NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
int rows = [sectionInfo numberOfObjects];

if(self.editing){
if(!editingFromSwipe&& tableIsEditing){
NSLog(@返回额外的行 - 编辑不是从swipe \\\
);
return rows +1;
}
NSLog(@返回正常行 - 从滑动编辑\\\
);
return rows;
}
NSLog(@返回正常行 - 不编辑,并将tableIsEditing设置为NO \\\
);
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];
}

//配置单元格...
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;


NSLog(@should into into if statement here!\\\
);

if(tableView.editing){//
if((indexPath.row == 0)&&(!editingFromSwipe)){
NSLog编辑时添加按钮单元格\\\
);
cell.textLabel.text = @添加新清单;
cell.detailTextLabel.text = nil;
}
else {
NSLog(@编辑时配置其他单元格\ n);
[self configureCell:cell atIndexPath:indexPath];
}

}
else {
NSLog(@通常不编辑时配置单元\ n);
[self configureCell:cell atIndexPath:indexPath];
}


return cell;
}


//覆盖以支持表视图的条件编辑。
/ *
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// return(indexPath.row!
return YES;
}
* /


//覆盖以支持编辑表视图。
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(editingStyle == UITableViewCellEditingStyleDelete)
{
//删除给定索引路径的管理对象
NSManagedObjectContext * context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

//保存上下文。
NSError * error = nil;
if(![context save:& error])
{
/ *
用代码来替换这个实现,以适当地处理错误。

abort()导致应用程序生成崩溃日志并终止。您不应在运送应用程序中使用此功能,但在开发过程中可能很有用。如果无法从错误中恢复,请显示一个警报面板,指示用户通过按主页按钮退出应用程序。
* /
NSLog(@未解析的错误%@,%@,错误,[错误userInfo]);
abort();
}
}
else if(editingStyle == UITableViewCellEditingStyleInsert){
//创建一个相应类的新实例,将其插入数组,并添加一个新行表视图
[self addingView];

}
}


/ *
//覆盖以支持重新排列表视图。
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
* /
b $ b / *
//覆盖以支持表视图的有条件重新排列。
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
//如果不希望项目可重新排序,返回NO。
return YES;
}
* /

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
int row = indexPath.row;

if(self.editing&& row == 0){
if(!editingFromSwipe&& tableIsEditing){
NSLog(@Not from swipe so创建第一行样式插入按钮\\\
);
return UITableViewCellEditingStyleInsert;
}
else if(editingFromSwipe){
NSLog(@从滑动,使第一行删除而不是插入\\\
);
return UITableViewCellEditingStyleDelete;
}

}
NSLog(@不编辑或不是第一行,因此使单元格样式正常\\\
);
return UITableViewCellEditingStyleDelete;
}


- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
editingFromSwipe = YES;
// NSLog(@编辑从刷卡(刚刚刷过)\\\
);
[super tableView:tableView willBeginEditingRowAtIndexPath:indexPath];
}

- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
[super tableView:tableView didEndEditingRowAtIndexPath:indexPath];
editingFromSwipe = NO;
// NSLog(@不从滑动编辑(刚解脱)\\\
);
}


- (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){
NSLog(@Swipe off时调用编辑,因此添加插入行\\\
);
tableIsEditing = YES;
NSLog(@tableIsEditing = YES(setEditing:Animated :));
[self.tableView insertRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
else {
[self.tableView deleteRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
}
[self.tableView endUpdates];
}


#pragma mark - 表视图委托

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row!= 0){
//导航逻辑可以在这里。创建并推送另一个视图控制器。
/ *
<#DetailViewController#> * detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@< #Nib name#> bundle:nil];
// ...
//将所选对象传递给新的视图控制器。
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
* /
}
}


#pragma mark - Data


- (void)configureCell :(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Checklist * aChecklist = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = aChecklist.name; // description];
cell.detailTextLabel.text = aChecklist.category.name; // description];
}


- (void)addingView //:(id)sender
{
//关闭编辑模式。
// if(self.editing){
// [self.navigationController setEditing:NO animated:YES];
//}

//为导航控制器创建根视图控制器
AddingViewController * viewController = [[AddingViewController alloc] initWithNibName:@AddingViewControllerbundle:nil];

viewController.delegate = self;
viewController.title = @添加清单;

//创建导航控制器并以模态方式呈现
UINavigationController * navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[self presentModalViewController:navigationController animated:YES];

viewController.textLabel.text = @输入新清单名称;

[navigationController release];
[viewController release];
}


#pragma mark - AddingViewDelegate


- (void)addingViewController:(AddingViewController *)addsViewController didAdd:(NSString * )itemAdded
{
if(itemAdded!= nil){

//关闭编辑模式。
if(self.editing)[self.navigationController setEditing:NO animated:NO];

//将类别名称添加到我们的模型和表视图中。

//创建由提取结果控制器管理的实体的新实例。
NSManagedObjectContext * context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription * entity = [[self.fetchedResultsController fetchRequest] entity];
清单* newChecklist = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];

[category addChecklistsObject:newChecklist];

newChecklist.name = itemAdded;
// [newChecklist setDateStamp:[NSDate date]];

//保存上下文。
NSError * error = nil;
if(![context save:& error])
{
/ *
用代码来替换这个实现,以适当地处理错误。

abort()导致应用程序生成崩溃日志并终止。您不应在运送应用程序中使用此功能,但在开发过程中可能很有用。如果无法从错误中恢复,请显示一个警报面板,指示用户通过按主页按钮退出应用程序。
* /
NSLog(@未解析的错误%@,%@,错误,[错误userInfo]);
abort();
}


}

[self dismissModalViewControllerAnimated:YES];
}


#pragma mark - 获取结果控制器

- (NSFetchedResultsController *)fetchedResultsController
{
if(fetchedResultsController != nil)
{
return fetchedResultsController;
}

//设置获取的结果控制器。

//为实体创建获取请求。
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
//根据需要编辑实体名称。
NSEntityDescription * entity = [NSEntityDescription entityForName:@ChecklistinManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//设置4 *谓词,因此我们只看到此类别的检查表。
NSPredicate * requestPredicate = [NSPredicate predicateWithFormat:@category.name =%@,self.category.name];
[fetchRequest setPredicate:requestPredicate];
//将批量大小设置为合适的数量。
[fetchRequest setFetchBatchSize:20];
//根据需要编辑排序键。
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@nameascending:YES];
NSArray * sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//如果适当,编辑段名称键路径和缓存名称。
//节的名称键路径的nil表示没有节。

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])
{
//正确处理错误!
NSLog(@未解析的错误%@,%@,错误,[错误userInfo]);
abort();
}

return fetchedResultsController;
}


#pragma mark - 获取的结果控制器委托


- (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];
}


/ *
//实现上述方法来更新表视图以响应个别更改可能会影响性能,如果大量更改同时进行。如果这被证明是一个问题,你可以改为只是实现controllerDidChangeContent:它通知委托,所有的部分和对象的变化已经处理。

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
//在最简单,最高效的情况下,重新加载表视图。
[self.tableView reloadData];
}
* /




@end









编辑:这里是我的'tableView:commitEditingStyle:forRowAtIndexPath'方法,基于Erik B的建议:

   - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath :(NSIndexPath *)indexPath 
{
if(editingStyle == UITableViewCellEditingStyleDelete)
{
NSManagedObjectContext * context = [self.fetchedResultsController managedObjectContext];

int numberOfRows = [self tableView:tableView numberOfRowsInSection:indexPath.section];
int rowBeingDeleted = indexPath.row +1;

if(tableIsEditing&&!editingFromSwipe&& amp; numberOfRows == rowBeingDeleted){
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]]];
}
else {
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
}

//上下文保存代码
}
}

应用程序不再崩溃,滑动删除仍然很好,但如果在编辑模式下尝试删除最后一行,它会删除倒数第二行。所有其他行都很好。 (我的插入行在顶部,偶然)。

解决方案

所以这是我们知道的:你会得到 NSRangeException line:

  [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]]; 

deleteObject: c $ c> NSRangeException ,因此我们知道

  [self.fetchedResultsController objectAtIndexPath:indexPath] 

导致崩溃。最可能的原因是,您没有考虑表视图的索引路径与添加时的 fetchedResultsController 的索引路径不同插入行。



如果我是对的,只有在删除最后一行时才会崩溃。



这是你如何解决它:

  [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row-1 inSection: indexPath.section]] 

请记住,只应更改索引路径, 可见。



编辑


删除插入行下面的行很好。我可以删除任何具有滑动到删除的行,并使用编辑按钮,我可以删除除最后一行(删除其上面的行)之外的任何行。




  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]];
}

这是导致该错误的原因:

  numberOfRows == rowBeingDeleted 

只有当它是最后一行时才更改索引路径。这是混乱,但也许你不应该改变索引路径呢?尝试用 NO 替换 numberOfRows == rowBeingDeleted ,看看会发生什么。


$ b $

  if(tableIsEditing& amp; ;&!editingFromSwipe){
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]]];
} else {
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
}


My UITableView has an Edit button which adds an "insert row" (with a green plus). If I try to delete a row while in Edit mode, my app crashes with "NSRangeException". Deleting a row while NOT in edit mode (using swipe-to-delete) is fine.

I know this is to do with how many rows the table view thinks it has - getting an insert row and swipe-to-delete both to work in the same table is a nightmare (for a beginner like me) because both swiping and pressing the edit button put the table in "edit mode", but the edit button adds a row, whereas swiping doesn't.

I've been trying to use a couple of Ivars to overcome this, but clearly I've gone wrong somewhere. Can someone more experienced tell me where I'm going wrong? (I'm sure I'm going about this in far too complicated a way!).

EDIT - as requested by Chiefly Izzy, I've put my whole unedited .m file here.

//
//  ChecklistsViewController.m
//  Check Box
//
//  Created by Ric on 18/03/2011.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#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;
        NSLog(@"tableIsEditing = NO (init) \n");
        NSLog(@"Not Editing From Swipe (init) \n");
    }
    return self;
}

- (void)dealloc
{
    [category release];
    [managedObjectContext release];
    [fetchedResultsController release];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];

    editingFromSwipe = NO;
    tableIsEditing = NO;
    NSLog(@"tableIsEditing = NO (viewDidLoad) \n");
    NSLog(@"Not Editing From Swipe (viewDidLoad) \n");

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;

    self.navigationItem.rightBarButtonItem = self.editButtonItem;    
    self.tableView.allowsSelectionDuringEditing = YES;
}


- (void)viewDidUnload
{
    [super viewDidUnload];
    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
    // For example: self.myOutlet = nil;
}


- (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
{
    NSLog(@"numberOfRowsInSection HAS BEEN CALLED");
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    int rows = [sectionInfo numberOfObjects];

    if (self.editing) {
        if (!editingFromSwipe && tableIsEditing) {
            NSLog(@"Returning extra row - editing not from swipe \n");
            return rows +1;
        }
        NSLog(@"Returning normal rows - editing from swipe \n");
        return rows;
    }
    NSLog(@"Returning normal rows - not editing - and setting tableIsEditing back to NO \n"); 
    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;
}


// Override to support conditional editing of the table view.
/*
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    // return (indexPath.row != 0);
    return YES;
}
*/


// Override to support editing the table view.
- (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];        
        [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

        // Save the context.
        NSError *error = nil;
        if (![context save:&error])
        {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }    
    else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        [self addingView];

    }   
}


/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/

/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the item to be re-orderable.
    return YES;
}
*/

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = indexPath.row;

    if (self.editing && row == 0) {
        if (!editingFromSwipe && tableIsEditing) {
            NSLog(@"Not from swipe so making first row style insert button \n");
            return UITableViewCellEditingStyleInsert;
        }
        else if (editingFromSwipe) { 
            NSLog(@"Is from swipe so making first row delete rather than insert \n");
            return UITableViewCellEditingStyleDelete;
        }

    }
    NSLog(@"Not editing or not first row so making cell style normal \n");
    return UITableViewCellEditingStyleDelete;
}


- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
    editingFromSwipe = YES;
    // NSLog(@"Editing From Swipe (just swiped) \n");
    [super tableView:tableView willBeginEditingRowAtIndexPath:indexPath];
}

- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
    [super tableView:tableView didEndEditingRowAtIndexPath:indexPath];
    editingFromSwipe = NO;
    // NSLog(@"Not Editing From Swipe (just unswiped) \n");
}


- (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) {
            NSLog(@"Editing called while swipe off, so adding insert row \n");
            tableIsEditing = YES;
            NSLog(@"tableIsEditing = YES (setEditing:Animated:)");
            [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) {
        // Navigation logic may go here. Create and push another view controller.
        /*
         <#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@"<#Nib name#>" bundle:nil];
         // ...
         // Pass the selected object to the new view controller.
         [self.navigationController pushViewController:detailViewController animated:YES];
         [detailViewController release];
         */
    }
}


#pragma mark - Data


- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    Checklist *aChecklist = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = aChecklist.name; // description];
    cell.detailTextLabel.text = aChecklist.category.name; // description];
}


- (void) addingView// :(id)sender
{
    // Turn off edit mode if it is on.
    //if (self.editing) {
    //    [self.navigationController setEditing:NO animated:YES];
    //}

    //Create the root view controller for the navigation controller
    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])
        {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
             */
            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])
    {
       // handle the error properly!
        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];
}


/*
 // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed. 

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
 {
 // In the simplest, most efficient, case, reload the table view.
 [self.tableView reloadData];
 }
 */




@end



EDIT: Here is the new code in my 'tableView:commitEditingStyle:forRowAtIndexPath' method, based on Erik B's advice:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        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]];
        }

        // context saving code    
    }      
}

The app no longer crashes, and swipe-to-delete is still fine, but if try to delete the last row while in Edit mode, it deletes the second-last row instead. All the other rows are fine. (My insert row is at the top, incidentally).

解决方案

So this is what we know: You get NSRangeException on this line:

[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

deleteObject: doesn't throw NSRangeException, so we know that

[self.fetchedResultsController objectAtIndexPath:indexPath]

is causing the crash. The most probable cause for this is that you don't take into account that the table view's index path isn't the same as the fetchedResultsController's index path when you have added the "insert row".

If I'm right it should only crash when you delete the last row.

This is how you fix it:

[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]]

Keep in mind that you should only change the index path if the "insert row" is visible.

EDIT:

Deleting the row just below the insert row works fine. I can delete any row with swipe-to-delete, and using the edit button I can delete any row except the last row (which deletes the row above it instead).

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]];
}

So this is what's causing that bug:

numberOfRows == rowBeingDeleted

You only change the index path if it's the last row. This is confusing, but maybe you shouldn't change the index path then? Try replacing numberOfRows == rowBeingDeleted with NO and see what happens.

EDIT 2: What happens if you do this instead?

if (tableIsEditing && !editingFromSwipe) {
    [context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]]];
} else {
    [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
}

这篇关于UITableView在编辑模式 - 按删除使我的应用程序崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆