This may be a minor issue... but it looks like a race condition. I can reliably reproduce the following NSRangeException.
2014-07-01 08:20:02.324 XXXXXX 2[7302:60b] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
0x1e1698: blt 0x1e170a ; -[IGFlowLayoutView insertItemAtIndex:] + 398 at IGFlowLayoutView.m:877
0x1e169a: mov r0, r8
0x1e169c: mov r1, r6
0x1e169e: blx 0x21ad2c ; symbol stub for: objc_msgSend
0x1e16a2: ldr r1, [sp, #0x18]
0x1e16a4: adds r2, r0, #0x1
0x1e16a6: mov r0, r8
0x1e16a8: blx 0x21ad2c ; symbol stub for: objc_msgSend
0x1e16ac: ldr r1, [sp, #0x1c]
0x1e16ae: mov r0, r5
0x1e16b0: mov r4, r0
0x1e16b2: ldr r5, [r0, r1]
0x1e16b4: mov r0, r8
0x1e16b6: mov r1, r6
0x1e16b8: blx 0x21ad2c ; symbol stub for: objc_msgSend
0x1e16bc: ldr r1, [sp, #0x20]
0x1e16be: mov r2, r0
0x1e16c0: mov r0, r5
0x1e16c2: blx 0x21ad2c ; symbol stub for: objc_msgSend
0x1e16c6: mov r7, r7
0x1e16c8: blx 0x21ad4c ; symbol stub for: objc_retainAutoreleasedReturnValue
0x1e16cc: mov r11, r0
0x1e16ce: add r0, sp, #0x4c
0x1e16d0: cmp.w r11, #0x0
0x1e16d4: bne 0x1e16e4 ; -[IGFlowLayoutView insertItemAtIndex:] + 360 at IGFlowLayoutView.m:875
0x1e16d6: vst1.32 {d8, d9}, [r0]
0x1e16da: movs r0, #0x0
0x1e16dc: movs r1, #0x0
0x1e16de: movs r3, #0x0
0x1e16e0: movs r2, #0x0
0x1e16e2: b 0x1e16f4 ; -[IGFlowLayoutView insertItemAtIndex:] + 376 at IGFlowLayoutView.m:875
0x1e16e4: ldr r2, [sp, #0x10]
0x1e16e6: mov r1, r11
0x1e16e8: blx 0x21ad38 ; symbol stub for: objc_msgSend_stret
0x1e16ec: ldr r2, [sp, #0x4c]
0x1e16ee: ldr r3, [sp, #0x50]
0x1e16f0: ldr r1, [sp, #0x54]
0x1e16f2: ldr r0, [sp, #0x58]
-(void)viewWillAppear:(BOOL)animated
{
[self.flowView updateData];
}
Hey Caylan,
So are you calling insertItemAtIndex on the same flowLayout that you're calling updateData on? If so that would definitely cause issues, as insertItemAtIndex does its calculation of where to insert the item, and essentially calls updateData on its self. If during that phase, while its animating, it in turns gets another updateData call, you could wind up in a bad state. If you're not expecting the insertion to animate, you could set the transitonDuration property to zero, which may stop the issue from occurring.
Is there anyway you can send me the sample that shows this issue happening?
-SteveZ
Not easily... but I'm tracking down the code smell that led me to updateData being called in viewWillAppear.
If I get a FRC call for didChangeObject with changeType NSFetchedResultsChangeMove, what's the preferred way to "move" the cells programmatically?
I appreciate your help. Additionally, I can appreciate the design differences. I implemented most of this, but a problem remains: The VCs that get created are customized in such a way, with dynamic class allocation that... when it comes to reusing the same base, I have to recreate those controllers down the line. Best practices dictates reusing the cells... but if it means reevaluating how the child VCs are constructed... a horse-a-piece, IMHO.
How difficult would it be to add that moveItemFrom..To... method? I'm not convinced that optimizing for reusable cells is the right way forward.
Ok, so I was just looking at the code, and I actually already have the method implemented, its what i use internally.
So, really I just need to add it to the header file.
You can try it right now though, just using objc_msgSend
#import <objc/message.h>
objc_msgSend(_flowLayoutView, @selector(moveItemAtIndex:toIndex:), index, newIndex);
Doh! I'd tear out my hair but I'm already bald. ;)
Ok, recovering from dizziness... I verified I'm able to programmatically swap cells at index 0 and 1.
The problem now is that the FRC generates two calls to NSFetchedResultsChangeMove. One for 1-->0 and another for 0-->1. The dual calls effectively negates the swap. Do you have any experience with how to handle this situation?
It's strange, because it looks like the same amount of calls happen when using the flowView to move... The user's touch calls the delegate to moveItemAtIndex, which does some core data magic, saves the context, then the FRC reports two NSFetchedResultsChangeMove. You know, I bet it's still doing it... but since the movement is complete, it negates the already swapped cells, so it's effectively swapping 3 times.
Oh, NSFetchedResultsController... what a gem.
Thanks for your help. I think I've got a grip on the situation and just need to rein in the FRC.
I figured out the algorithm. Basically, for every “movement" we are only interested in the largest movement, which is the ACTUAL movement. All the other movements are noise. The logic is effectively coalesced from controllerWillChangeContent/controllerDidChangeContent into one command on the flowView.
Btw, this may be why UITableView has beginUpdates/endUpdates.