Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App crashes when scrolling UITableView with cells that have UICollectionView with FlowLayout

The app is written in Swift 2.2 using Xcode 7.3.1.

I have a UITableView with cells that have UICollectionView with horizontal FlowLayout. When I scroll the table view up and down really fast and without letting it stop on a device (iPad Air 2, iOS 9.3.2) or simulator, the app crashes with

 EXC_BAD_ACCESS

in AppDelegate.

The app is not hitting a breakpoint set up in a view controller for didReceiveMemoryWarning().

It seems that the code crashes on

[UICollectionViewData invalidateItemsAtIndexPaths:]

or

[_UIFlowLayoutSection computeLayoutInRect:forSection:invalidating:]

followed by

0_dispatch_barrier_async_f_slow.

The stack trace doesn't point me to any line of app code.

Console stack trace for simulator (no console stack trace when crashing on device):

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[_UIFlowLayoutItem frame]: unrecognized selector sent to class 0x110f9ecc0'
*** First throw call stack:
(
0   CoreFoundation                      0x000000011259cd85 __exceptionPreprocess + 165
1   libobjc.A.dylib                     0x00000001137d3deb objc_exception_throw + 48
2   CoreFoundation                      0x00000001125a5c3d +[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3   CoreFoundation                      0x00000001124ebcfa ___forwarding___ + 970
4   CoreFoundation                      0x000000011259e1f8 __forwarding_prep_1___ + 120
5   UIKit                               0x0000000110b1d8b5 -[UICollectionViewData invalidateItemsAtIndexPaths:] + 273
6   UIKit                               0x0000000110ae26b7 -[UICollectionView _invalidateWithBlock:] + 62
7   UIKit                               0x0000000110ae2a6f -[UICollectionView _invalidateLayoutWithContext:] + 656
8   UIKit                               0x0000000110af0ad9 -[UICollectionViewLayout invalidateLayoutWithContext:] + 189
9   UIKit                               0x0000000110afe2cc -[UICollectionViewFlowLayout invalidateLayoutWithContext:] + 604
10  libdispatch.dylib                   0x0000000114257d9d _dispatch_call_block_and_release + 12
11  libdispatch.dylib                   0x00000001142783eb _dispatch_client_callout + 8
12  libdispatch.dylib                   0x00000001142601ef _dispatch_main_queue_callback_4CF + 1738
13  CoreFoundation                      0x00000001124f60f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
14  CoreFoundation                      0x00000001124b7b99 __CFRunLoopRun + 2073
15  CoreFoundation                      0x00000001124b70f8 CFRunLoopRunSpecific + 488
16  GraphicsServices                    0x0000000114bcaad2 GSEventRunModal + 161
17  UIKit                               0x000000011024cf09 UIApplicationMain + 171
18  market-touch                        0x000000010d4cc472 main + 114
19  libdyld.dylib                       0x00000001142ac92d start + 1
20  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

Here's the stack trace (slightly different version here).

Why is that happening and is there anything I can do to prevent that?

EDIT: As per Doro's request, here is my cellForRowAtIndexPath:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier(customCellNibName, forIndexPath: indexPath) as! CustomTableViewCell

    //populating cell with data

    return cell 
}

I do not call func setCollectionViewLayout(_ layout: UICollectionViewLayout, animated animated: Bool) at all.

The flow layout setup is done in IB.

Enabling zombie objects did not help me much, if at all. Here is a stack trace I got:

-[NSIndexPath frame]: unrecognized selector sent to instance 0xc000000012de4890
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSIndexPath frame]: unrecognized selector sent to instance 0xc000000012de4890'
*** First throw call stack:
(0x1815f6db0 0x180c5bf80 0x1815fdc4c 0x1815fabec 0x1814f8c5c 0x186fcccc0 0x186f9e688 0x1868de558 0x1868da0a4 0x1869ae9ec 0x1024f9a7c 0x1024f9a3c 0x1024ff4e4 0x1815acd50 0x1815aabb8 0x1814d4c50 0x182dbc088 0x1867b6088 0x1000c9f58 0x1810728b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

EDIT 2:

In my CustomTableViewCell's awakeFromNib() method I have the following code:

if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
    layout.estimatedItemSize = CGSizeMake(50, 22)
}

I have noticed that if I comment that out, I no longer get the crashes (although sometimes the whole table view freezes and is unusable). However, the collection view cells are not sized properly then. Compare: right vs wrong.

In IB, the Collection View Flow Layout -> Item Size is 50x22. The cell used for the collection view has one button in it, with height constraint 22pt. Here is the method for setting layout attributes for collection view cell:

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let attributes = super.preferredLayoutAttributesFittingAttributes(layoutAttributes)

    button.setTitle(symbol, forState: .Normal)
    button.sizeToFit()

    attributes.frame = button.frame

    return attributes
}

The idea is to have these cells be 22pt high and of varying width (around 50pt), with the same distance between them. Can I achieve that without calling the code that, when commented out, prevents the crash?

like image 583
pingd Avatar asked Nov 08 '22 14:11

pingd


1 Answers

Checkout this tutorial.

Basically, this could happen because of reusable essential of UITableViewCell. When you scroll tableView, it uses the same set of visible cells, and set the content from the your cellForRowAtIndexPath: method.

Depending on your implementation, it seems UICollectionViewFlowLayout is deallocated at the specific point of time. I mean, you have call to

func setCollectionViewLayout(_ layout: UICollectionViewLayout,
                    animated animated: Bool)

inside cellForRowAtIndexPath: and while you are scrolling, the new UICollectionViewLayout are allocated and set to the collectionView inside your cell. Enable Zombie objects in Edit Scheme -> Diagnostics -> Enable Zombie Objects. Check the correctness of current state.

It's hard to suggest any working snippet, but as an idea, try to set all the properties related to flow layout inside init method of uitableviewCell (only once) this will avoid re-allocating:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"CellIdentifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        // set your flow layout or other stuff here
    }


    return cell;
}

Hope this helps.

like image 129
Doro Avatar answered Nov 14 '22 22:11

Doro