UICollectionViewFlowLayout subclass crashes accessing array beyond bounds while scrolling and reloading

后端 未结 4 2016
小蘑菇
小蘑菇 2021-02-03 14:16

I have a UICollectionView that uses a custom subclass of UICollectionViewFlowLayout to make the section headers stick to the top of the screen while scrolling just like UITableV

4条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-02-03 14:29

    Edit 2, as per 2nd BenRB's comment.

    When dataSource gets updated and you call reloadData, this latter really invalidates everything in the collection view. However the logic and the exact sequence of the initiated refresh process happens inside the default flow layout and is hidden from us.

    In particular, the default flow layout has its own private _prepareLayout (exactly, with underscore) method, which is independent from the prepareLayout and its overloads provided by the subclasses.

    prepareLayout's (without underscore) default implementation of the base flow layout class does nothing, by the way.

    During the refresh process the default flow layout gives its subclass a chance to provide more information (e.g. additional layoutAttributes) through layoutAttributesForElementsInRect: and layoutAttributesForItemAtIndexPath: "callbacks". To guarantee the consistency between base class' data and respective indexPath / layoutAttributes array, calls to corresponding "super" should only happen inside these respective methods:

    • [super layoutAttributesForElementsInRect:] only inside the overloaded [layoutAttributesForElementsInRect:]

    • [super layoutAttributesForItemAtIndexPath:] only inside the overloaded [layoutAttributesForItemAtIndexPath:],

    No cross-calls between these methods should happen, at least with indexPaths not supplied by their corresponding "super" methods, because we don't know exactly what happens inside.

    I was fighting with my collection view for a long time and ended with the only working sequence finally:

    1. Prepare add'l layout data by directly accessing the dataSource (without mediation of collection view's numberOfItemsInSection:), and store that data inside the subclass' object, e.g. in a dictionary property, using indexPath as a key. I am doing this in the overloaded [prepareLayout].

    2. Supply the stored layout data to the base class when it requests this information through callbacks:

    // layoutAttributesForElementsInRect

    - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    
    //calls super to get the array of layoutAttributes initialised by the base class
    **NSArray *array = [super layoutAttributesForElementsInRect:rect];**
    
    for(MyLayoutAttributes *la in array)
    
        if(la.representedElementCategory == UICollectionElementCategoryCell ){
           NSIndexPath indexPath =  la.indexPath //only this indexPath can be used during the call to layoutAttributesForItemAtIndexPath:!!! 
           //extracts custom layout data from a layouts dictionary
           MyLayoutAttributes *cellLayout = layouts[la.indexPath];  
           //sets additional properties
            la.this = cellLayout.this
            la.that = cellLayout.that
            ...
        }
       ....
    return array;
    }
    

    //layoutAttributesForItemAtIndexPath:

    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    
      MyLayoutAttributes *la = (MyLayoutAttributes *)[super layoutAttributesForItemAtIndexPath:indexPath ];
    
        if(la.representedElementCategory == UICollectionElementCategoryCell ){
            NSIndexPath indexPath =  la.indexPath //only this indexPath can be used during the call !!! 
           //extracts custom layout data from a layouts dictionary using indexPath as a key
           MyLayoutAttributes *cellLayout = layouts[la.indexPath];  
           //sets additional properties
            la.this = cellLayout.this
            la.that = cellLayout.that
        }
    return la;
    }
    

提交回复
热议问题