【问题标题】:Why Can't I delete the bottom row of my UITableView?为什么我不能删除我的 UITableView 的底行?
【发布时间】:2011-07-23 00:09:21
【问题描述】:

当用户按下 Edit 时,我的 UITableView 在顶部添加一个插入行(带有绿色加号),并将所有其他行置于删除模式(红色减号)。或者,用户可以在不按下编辑按钮的情况下滑动删除。我正在使用几个 Ivars 来跟踪表格是否通过滑动或按下编辑按钮处于编辑模式,并采取相应的行动(例如更新 numberOfRowsInTableView: 按下编辑时使用额外的插入行)。

除了事情之外,一切都完美无缺:当处于编辑模式时(即用户已明确点击编辑按钮,并且插入行已出现在顶部),如果用户尝试删除底行,则下一行向上而是被删除。删除任何其他行都可以。

EDIT --它似乎删除了上面的行,但是如果我立即退出并重新加载应用程序,结果发现最后一行已经消失了。所以我猜我的 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

【问题讨论】:

  • 下次要贴378行代码,只需提供文件链接即可。
  • 我会提醒你不要让你的表格视图变得复杂。我认为尝试在 tableview 单元格中编辑数据几乎总是一个错误。表格视图中也有很多事情可以进行编辑。要创建新行,请使用详细视图。这就是界面语法教给用户的期望。
  • @TechZen 请原谅我对术语不熟悉,但是使用详细视图是什么意思?我将“详细视图”与加载全新视图相关联(例如,当用户选择父项时加载子实体表)。如何在带有详细视图的表中添加一行?

标签: iphone cocoa-touch uitableview core-data nsfetchedresultscontroller


【解决方案1】:

你的设计有错。您不应该在任何地方添加行,而是它们在表中的逻辑位置。

获取结果控制器 (FRC) 的全部意义在于将 tableview 与数据同步。表中的行顺序应反映 fetchedObjects 数组中托管对象的顺序。通过在底部或顶部插入一行并添加一个在逻辑上不一定属于表格顶部或底部的对象,会破坏这种同步。

当您在addingViewController:didAdd: 中添加新的托管对象时,FRC 会提醒它委托尝试重绘表格。你试图弥补这一点,但你真的做不到。你所有的索引都消失了。

而不是使用一行来输入新行。使用 tableview 页眉或页脚视图。这样,您可以冻结 tableview,创建新对象,然后更新表,新对象将显示在表中它的逻辑所属位置。

【讨论】:

  • 谢谢。作为一个新手,我不知道可以用表格标题来做到这一点,但有点谷歌搜索似乎证实我应该能够在那里粘贴一个按钮并以更优雅的方式处理它。我怀疑我之前看到的使用插入行的示例一定不是使用 NSFetchedResultsController,这可能就是我遇到的问题。当我回到我的电脑时我会试试这个,然后我会回到这里告诉你我的进展情况。
  • 效果很好!我在向表格添加按钮时找到了this guide,实现了大约 5 分钟。我还可以通过这种方式自定义按钮,如果我更改表格,它应该可以轻松“缩放”。谢谢!
【解决方案2】:

为了不混淆行,我建议将插入行放在它自己的部分中。由于您显然只使用一个部分,因此您知道发送给 FRC 的部分应始终为 0。代码如下所示:

[context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]];

TechZen 的解决方案也可以使用,因此您选择哪种解决方案完全取决于您喜欢哪种设计。 TechZen 的解决方案不会干扰拥有多个部分,但可以修改此解决方案以支持多个部分。

【讨论】:

  • 非常感谢 Erik 的所有帮助。最后,我还是接受了 TechZen 的建议,但在过去的几天里,我从你那里学到了很多东西,而且我的一般编程知识也更好!
【解决方案3】:

您可以将静态单元格添加到从 NSFetchedResultsController 获取数据的 UITableViews。但要做到这一点,您必须调整在UITableViewDelegateUITableViewDataSourceNSFetchedResultsControllerDelegate 方法之一中使用的几乎所有 NSIndexPaths。

我添加了一些帮助方法,将 tableview 的 indexpath 转换为 fetched resultscontroller 的 indexpath,反之亦然。如果您想在顶部添加一行,可以使用这样的东西:

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

然后您必须更改将索引路径从表传递到 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]);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多