TableView有两个NSFetchedResultsController实例 [英] TableView with two instances of NSFetchedResultsController

查看:149
本文介绍了TableView有两个NSFetchedResultsController实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

经过几天的研究和重新编码,我几乎很累。我的目标是让一个测试应用程序运行一个单一的表格填充从两个单独的fetchedResultControllers。



我在购物清单上有一系列的项目,每个项目都有部门和布林值。未收集的项目应按部门列出,后面是包含所有收集项目(不论部门)的单个部分。当用户检查未收集的项目时,他们应该向下移动到已收集部分。如果他/他取消检查收集的项目,它应该移回到其正确的部门。





为了实现第一部分(未收集的项目),我设置了一个简单的fetchedResultsController,其中收集= NO,并按部门划分结果:

   - (NSFetchedResultsController *)firstFRC {
// Set如果需要,则提取读取的结果控制器。
if(firstFRC == nil){

//为实体创建获取请求。
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];

// fetch items
NSEntityDescription * entity = [NSEntityDescription entityForName:@IteminManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];

//如果未收集
NSPredicate * predicate = [NSPredicate predicateWithFormat:@(collected = NO)];
[fetchRequest setPredicate:predicate];

//按名称排序
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@nameascending:YES];
NSArray * sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,nil];

[fetchRequest setSortDescriptors:sortDescriptors];

//获取结果,由部门分区
NSFetchedResultsController * aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@departmentcacheName:nil];
aFetchedResultsController.delegate = self;
self.firstFRC = aFetchedResultsController;
}

return firstFRC;
}

我设置行,节和节头的数量如下: / p>

   - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{
//返回节数。
return firstFRC.sections.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//返回节中的行数。
return [[firstFRC.sections objectAtIndex:section] numberOfObjects];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[[firstFRC sections] objectAtIndex:section] name];
}



我还使用样板控制器WillChangeContent,didChangeObject和controllerDidChangeContent添加/删除单元格作为FRC的变化。



为简洁起见,我不会包括显示单元格的代码,但我基本上拉基于单元格的索引路径正确的项目,设置单元格的文本/字幕,并且根据项目是否被收集来附加两个复选标记图像中的一个。我也将这个图片(在按钮上)从触摸时切换到未选中,然后相应地更新项目。



这个部分都很好。我可以按部门查看我的项目列表,当我将一个项目标记为收集时,我会看到它按预期从列表中删除。



现在我尝试添加底部部分,包含所有收集的项目(在单个部分)。首先我设置了第二个fetchedResultsConroller,这次只获取未收集的项目,并且没有切片。我还必须更新以下内容:

   - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{
//返回节的数量 - 为'collected'节添加一个节
return firstFRC.sections.count + 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//返回
if(section< firstFRC.sections.count){
return [[firstFRC.sections objectAtIndex:section] numberOfObjects];
}
else {
return secondFRC.fetchedObjects.count;
}
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if(section< firstFRC.sections。 count){
return [[[firstFRC sections] objectAtIndex:section] name];
}
else {
return @Collected;
}
}

然后我以类似的方式更新了cellForRowAtIndexPath我检索的项目来自正确的FRC:

  Item * item; 
if(indexPath.section< firstFRC.sections.count){
item = [firstFRC objectAtIndexPath:indexPath];
}
else {
item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
}

[[cell textLabel] setText:[item name]];

...剩余的单元格配置

表格视图完全按预期显示:






$ b

问题(最后)当我选择未收集项目的复选标记时。我期望该项目被从下面列出的部门删除,并移动到收集部分。相反,我得到:


CoreData:错误:严重的应用程序错误。在调用
-controllerDidChangeContent:时,从NSFetchedResultsController的委托捕获到
异常。无效更新:区段0中的行数无效。更新(2)后,现有区段中包含的行数
必须等于
中包含的行数(更新前的区段) 2),加或减从该节插入或删除的行数
(插入1,删除0)和加
或减去移入或移出该节的行数(0移动
in,0移出)。 with userInfo(null)


如果我尝试相反,那么我会收到不同的错误:


*由于未捕获异常NSRangeException而终止应用程序,原因:'* - [__ NSArrayM objectAtIndex:]:index 2超出范围[0 ..
1]'


我怀疑在这两种情况下都存在与节/行的数量一致的问题FRC和tableview当一个项目从一个FRC移动到另一个。虽然第二个错误让我认为可能有一个更简单的问题与我检索的项目有关。



任何方向或想法将不胜感激。我可以提供更多的我的代码,如果它会有所帮助,还创建了一个小的测试应用程序与单一视图来演示的问题。



更新 - 请求的附加代码 p>

根据要求,这是触摸复选标记时会发生的情况:

  - (void)checkButtonTapped:(id)sender event:(id)event 
{
NSSet * touches = [event allTouches];
UITouch * touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:currentTouchPosition];
if(indexPath!= nil)
{
[self tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}
}

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
Item * item;
if(indexPath.section< firstFRC.sections.count){
item = [firstFRC objectAtIndexPath:indexPath];
}
else {
item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
}

UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton * button =(UIButton *)cell.accessoryView;

if(![item collected]){
[item setCollected:YES];
[button setBackgroundImage:[UIImage imageNamed:@checked.png] forState:UIControlStateNormal];
}
else if([item collected]){
[item setCollected:NO];
[button setBackgroundImage:[UIImage imageNamed:@unchecked.png] forState:UIControlStateNormal];
}
NSError * error = nil;
if(![item.managedObjectContext save:& error]){
NSLog(@保存收集的项目时出错);
}
}


解决方案

,我想我可能已经破解了它。



当我访问委托方法controllerDidChangeObject时,我试图插入在indexPath提供的一行(对于checked项)。除了在插入我的附加部分时,这个indexPath没有意识到在它之前有一堆其他部分。所以它接收段0并尝试插入。相反,如果indexPath来自第二个FRC,则我应该将节号递增第一个FRC表中的节数。所以,我替换了:

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

 <$ (NSFetchedResultsChangeType)类型newIndexPath:(NSIndexPath *)newIndexPath {

(NSIndexPath *)newIndexPath:(NSFetchedResultsChangeType) UITableView * tableView = self.tableView;

switch(type){
case NSFetchedResultsChangeInsert:

if([controller.sectionNameKeyPath isEqualToString:@department]){
[tableView insertRowsAtIndexPaths :[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else {
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:newIndexPath.row inSection:firstFRC.sections.count]] withRowAnimation:UITableViewRowAnimationFade]; }
break;

我需要为插入,删除,更新等操作。答案后,我已经验证了这一点,并允许时间其他意见。


After days of research and re-coding I am pretty much stumped. My goal is to get a test app running with a single tableview populated from two separate fetchedResultControllers.

I have a series of items on a shopping list, each with a department and a boolean 'collected' flag. Uncollected items should be listed by department, followed by a single section containing all collected items (regardless of department). As a user checks off uncollected items, they should move down to the 'collected' section. If s/he un-checks a collected item, it should move back into its correct department.

To achieve the first part (uncollected items), I set up a simple fetchedResultsController that fetches all items where collected = NO, and sectioned the results by department:

- (NSFetchedResultsController *)firstFRC {
    // Set up the fetched results controller if needed.
    if (firstFRC == nil) {

        // Create the fetch request for the entity.
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

        // fetch items
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext];
        [fetchRequest setEntity:entity];

        // only if uncollected
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(collected = NO)"];
        [fetchRequest setPredicate:predicate];

        // sort by name
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

        [fetchRequest setSortDescriptors:sortDescriptors];

        // fetch results, sectioned by department
        NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"department" cacheName:nil];
        aFetchedResultsController.delegate = self;
        self.firstFRC = aFetchedResultsController;
    }

    return firstFRC;
} 

I set the number of rows, sections, and section headers as follows:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return firstFRC.sections.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [[firstFRC.sections objectAtIndex:section] numberOfObjects];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [[[firstFRC sections] objectAtIndex:section] name];
}

I also use the boilerplate controllerWillChangeContent, didChangeObject, and controllerDidChangeContent to add/remove cells as the FRC's change.

For brevity, I won't include the code for displaying the cell, but I essentially pull the correct item based on the cell's index path, set the text/subtitle of the cell, and attach one of two checkmark images, depending on whether the item is collected or not. I also wire up this image (which is on a button) to toggle from checked to unchecked when it is touched, and update the item accordingly.

This part all works fine. I can view my list of items by department, and when I mark one as collected, I see it drop off the list as expected.

Now I attempted to add the bottom section, containing all of the collected items (in a single section). First I set up a second fetchedResultsConroller, this time to fetch only uncollected items, and without sectioning. I also had to update the following:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections - add one for 'collected' section
    return firstFRC.sections.count + 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section
    if (section < firstFRC.sections.count) {
        return [[firstFRC.sections objectAtIndex:section] numberOfObjects];
    }
    else {
        return secondFRC.fetchedObjects.count;
    }
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (section < firstFRC.sections.count) {
        return [[[firstFRC sections] objectAtIndex:section] name];
    }
    else{
        return @"Collected";
    }
}

I then updated the cellForRowAtIndexPath in a similar fashion, so that the item I retrieve comes from the right FRC:

    Item *item;
    if (indexPath.section < firstFRC.sections.count) {
        item = [firstFRC objectAtIndexPath:indexPath];
    }
    else {
        item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
    }

    [[cell textLabel] setText:[item name]];

…rest of cell configuration

This works great when I launch. The tableview displays exactly as anticipated:

The Problem (finally)

The (first) problem comes when I select the checkmark for an uncollected item. I expect the item to be removed from the department it is listed under, and moved to the 'collected' section. Instead, I get:

CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

If I attempt the opposite, then I receive a different error:

* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 2 beyond bounds [0 .. 1]'

I suspect that in both cases there is a problem with consistency with the number of sections/rows in the FRCs and the tableview when an item moves from one FRC to the other. Although that second error makes me think there is maybe a simpler problem related to my retrieval of items.

Any direction or ideas would be appreciated. I can provide more of my code if it would help, and have also created a small test app with a single view to demonstrate the issue. I can upload it if necessary, but mostly I wanted to test the issue in a small scale sandbox.

Update - additional code requested

As requested, this is what happens when a checkmark is touched:

- (void)checkButtonTapped:(id)sender event:(id)event
{
    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
    if (indexPath != nil)
    {
        [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
    }
}

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{   
    Item *item;
    if (indexPath.section < firstFRC.sections.count) {
        item = [firstFRC objectAtIndexPath:indexPath];
    }
    else {
        item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
    }

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    UIButton *button = (UIButton *)cell.accessoryView;  

    if (![item collected]) {
        [item setCollected:YES];        
        [button setBackgroundImage:[UIImage imageNamed:@"checked.png"] forState:UIControlStateNormal];
    }
    else if ([item collected]){
        [item setCollected:NO];
        [button setBackgroundImage:[UIImage imageNamed:@"unchecked.png"] forState:UIControlStateNormal];
    }
    NSError *error = nil;
    if (![item.managedObjectContext save:&error]) {
        NSLog(@"Error saving collected items");
    }
}

解决方案

Well, I think I might have cracked it. As is often the case, stripping it down and reading just a few comments pointed me in the right direction.

When I get to the delegate method controllerDidChangeObject, I attempt to insert a row at the indexPath provided (for the 'checked' item). Except that when inserting into my additional section, this indexPath has no awareness of the fact that there are a bunch of other sections before it. So it receives section 0 and attempts to insert there. Instead, if the indexPath comes from the second FRC, I should be incrementing the section number by the number of sections in the first FRC's table. So, I replaced:

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

with

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {

UITableView *tableView = self.tableView;

switch(type) {
    case NSFetchedResultsChangeInsert:

        if ([controller.sectionNameKeyPath isEqualToString:@"department"]) {
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        }
        else {
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:newIndexPath.row inSection:firstFRC.sections.count]] withRowAnimation:UITableViewRowAnimationFade];         }
        break;

I will need to do this for each of insert, delete, update etc. I will mark this as the answer after I have validated this and to allow time for other comments.

这篇关于TableView有两个NSFetchedResultsController实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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