3lvis / Form

The most flexible and powerful way to build a form on iOS
http://hyper.no
Other
1.64k stars 145 forks source link

NSInternalInconsistency exception when attempting to process FORMTarget #582

Open mmarbouh opened 7 years ago

mmarbouh commented 7 years ago

Hi,

The Issue

I am trying to automate the process of filling form field data based on data present in other fields. Basically, implement a rules engine of sorts into forms. For example, if a checkbox gets checked, it'll hide a specific field further down the line, and vice versa if it's unchecked.

The logic behind this is already fleshed out, and functional, but only when the form isn't too big so that there are non-visible fields. I.e., if there's fields at the bottom of the form that one has yet to scroll through, and I attempt to update any field (visible or otherwise) through the use of FORMTargets, a NSInternalInconsistency exception is thrown. That the number of items before the update must be equal to after the update plus or minus the modifications.

The Code

data.json.txt

Attached you will find a very simple json to demo this on, as of this writing (August 21st, 2017), I am on the latest version of the engine, and am using the demo HYPSampleCollectionViewController from the Basic-ObjC scheme, I am just swapping out the filename.

- (void)checkRules { FORMField *field = [self.dataSource fieldWithID:@"check" includingHiddenFields:YES]; NSMutableArray *targets = [NSMutableArray array];

if ([field.value boolValue])
{
    [targets addObject:[FORMTarget hideFieldTargetWithID:@"adult_dates"]];
    [targets addObject:[FORMTarget showFieldTargetWithID:@"juvenile"]];
}
else
{
    [targets addObject:[FORMTarget hideFieldTargetWithID:@"juvenile"]];
    [targets addObject:[FORMTarget showFieldTargetWithID:@"adult_dates"]];
}

field = [self.dataSource fieldWithID:@"choice" includingHiddenFields:YES];
FORMFieldValue *value = field.value;

if ([value.value isEqualToNumber:@0])
{
    FORMTarget *target1 = [FORMTarget updateFieldTargetWithID:@"male"];
    target1.targetValue = @YES;

    FORMTarget *target2 = [FORMTarget updateFieldTargetWithID:@"female"];
    target2.targetValue = @NO;

    [targets addObject:target1];
    [targets addObject:target2];
}
else if ([value.value isEqualToNumber:@1])
{
    FORMTarget *target1 = [FORMTarget updateFieldTargetWithID:@"female"];
    target1.targetValue = @YES;

    FORMTarget *target2 = [FORMTarget updateFieldTargetWithID:@"male"];
    target2.targetValue = @NO;

    [targets addObject:target1];
    [targets addObject:target2];
}

[self.dataSource processTargets:targets];

}

Above is the code I use to check the current field values and generate targets accordingly. This is called in every field's fieldUpdatedBlock.

So yeah, as mentioned, the code does work, it's just that when there's fields out of view that the number of items somehow gets corrupted after/while processing the targets. If I modify my json to only have one group, so that it can all fit on the screen without having to scroll, then there are no issues.

For example, here is the exception logged when attempting to toggle the "Is Juvenile?" switch: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (4) must be equal to the number of items contained in that section before the update (4), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).' *** First throw call stack:

0   CoreFoundation                      0x00000001014c5b0b __exceptionPreprocess + 171

1   libobjc.A.dylib                     0x0000000100f2a141 objc_exception_throw + 48

2   CoreFoundation                      0x00000001014c9cf2 +[NSException raise:format:arguments:] + 98

3   Foundation                          0x0000000100ac43b6 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 193

4   UIKit                               0x000000010291d9f2 -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:] + 16569

5   UIKit                               0x000000010291975a -[UICollectionView _updateRowsAtIndexPaths:updateAction:] + 372

6   Form                                0x000000010080ba49 -[FORMDataSource deleteItemsAtIndexPaths:] + 153

7   Form                                0x0000000100812599 __33-[FORMDataSource processTargets:]_block_invoke + 825

8   Form                                0x0000000100839acf +[FORMTarget filteredTargets:filtered:] + 1199

9   Form                                0x0000000100812230 -[FORMDataSource processTargets:] + 160

10  Basic-ObjC                          0x0000000100752151 -[HYPSampleCollectionViewController checkRules] + 1953

11  Basic-ObjC                          0x00000001007500a8 __52-[HYPSampleCollectionViewController setUpDataSource]_block_invoke_2 + 648

12  Form                                0x0000000100810dd3 -[FORMDataSource fieldCell:updatedWithField:] + 195

13  Form                                0x0000000100836695 -[FORMSwitchFieldCell switchAction:] + 469`

Thank you in advance for any insight you could provide.

3lvis commented 7 years ago

Hi @mmarbouh, I was trying to reproduce your issue but got a bit confused, also the JSON that you provided doesn't work out of the box with the demo project since it needs a few custom fields. Could you please provide a sample project that shows the problem?

Thanks in advance :)