Overhaul ASDataController and extensively test ASTableView.

This diff resolves all known consistency issues with ASTableView and ASCollectionView.
It includes significantly more aggressive thrash-testing in ASTableViewStressTest,
which now passes on a variety of device and simulator configurations.  It also updates
the unit tests run on every commit to ensure any regression is caught quickly.

A few of the salient changes in this diff:
- ASTableView now uses Rene's ASCollectionViewLayoutController, and actually uses a
UICollectionViewFlowLayout without any UICollectionView.  This resolves an issue where
ASFlowLayoutController was generating slightly out-of-bounds indicies when programmatically
scrolling past the end of the table content.  Because the custom implementation is likely
faster, I will revisit this later with profiling and possibly returning to the custom impl.
- There is now a second copy of the _nodes array maintained by ASDataController.  It shares
the same node instances, but this does add some overhead to manipulating the arrays. I've
filed a task to follow up with optimization, as there are several great opportunities to
make it faster.  However, I don't believe the overhead is a significant issue, and it does
guarantee correctness in even the toughest app usage scenarios.
- ASDataController no longer supports calling its delegate /before/ edit operations.  No
other class was relying on this behavior, and it would be unusual for an app developer to
use ASDataController directly.  However, it is possible that someone with a custom view
that integrates with ASDataController and ASRangeController could be affected by this.
- Further cleanup of organization, naming, additional comments, reduced code length
wherever possible.  Overall, significantly more accessible to a new reader.
This commit is contained in:
Scott Goodson
2015-06-28 18:03:45 -07:00
parent a654d880c3
commit 57465c7fd3
12 changed files with 479 additions and 411 deletions

View File

@@ -21,7 +21,8 @@ struct ASDirectionalScreenfulBuffer {
typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer;
ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection,
ASRangeTuningParameters rangeTuningParameters) {
ASRangeTuningParameters rangeTuningParameters)
{
ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0};
BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection);
horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls :
@@ -32,7 +33,8 @@ ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDire
}
ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection,
ASRangeTuningParameters rangeTuningParameters) {
ASRangeTuningParameters rangeTuningParameters)
{
ASDirectionalScreenfulBuffer verticalBuffer = {0, 0};
BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection);
verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls :
@@ -52,44 +54,64 @@ typedef struct ASRangeGeometry ASRangeGeometry;
#pragma mark -
#pragma mark ASCollectionViewLayoutController
@interface ASCollectionViewLayoutController () {
ASCollectionView * __weak _collectionView;
@interface ASCollectionViewLayoutController ()
{
UIScrollView * __weak _scrollView;
UICollectionViewLayout * __strong _collectionViewLayout;
std::vector<CGRect> _updateRangeBoundsIndexedByRangeType;
ASScrollDirection _scrollableDirections;
}
@end
@implementation ASCollectionViewLayoutController
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView {
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView
{
if (!(self = [super init])) {
return nil;
}
_collectionView = collectionView;
_scrollableDirections = [collectionView scrollableDirections];
_scrollView = collectionView;
_collectionViewLayout = [collectionView collectionViewLayout];
_updateRangeBoundsIndexedByRangeType = std::vector<CGRect>(ASLayoutRangeTypeCount);
return self;
}
- (instancetype)initWithScrollView:(UIScrollView *)scrollView collectionViewLayout:(UICollectionViewLayout *)layout
{
if (!(self = [super init])) {
return nil;
}
_scrollableDirections = ASScrollDirectionVerticalDirections;
_scrollView = scrollView;
_collectionViewLayout = layout;
_updateRangeBoundsIndexedByRangeType = std::vector<CGRect>(ASLayoutRangeTypeCount);
return self;
}
#pragma mark -
#pragma mark Index Paths in Range
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection
viewportSize:(CGSize)viewportSize
rangeType:(ASLayoutRangeType)rangeType {
rangeType:(ASLayoutRangeType)rangeType
{
ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection
collectionView:_collectionView
rangeTuningParameters:[self tuningParametersForRangeType:rangeType]];
_updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds;
return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds collectionView:_collectionView];
return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds];
}
- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection
collectionView:(ASCollectionView *)collectionView
rangeTuningParameters:(ASRangeTuningParameters)rangeTuningParameters {
CGRect rangeBounds = collectionView.bounds;
CGRect updateBounds = collectionView.bounds;
ASScrollDirection scrollableDirections = [collectionView scrollableDirections];
rangeTuningParameters:(ASRangeTuningParameters)rangeTuningParameters
{
CGRect rangeBounds = _scrollView.bounds;
CGRect updateBounds = _scrollView.bounds;
BOOL canScrollHorizontally = ASScrollDirectionContainsHorizontalDirection(scrollableDirections);
BOOL canScrollHorizontally = ASScrollDirectionContainsHorizontalDirection(_scrollableDirections);
if (canScrollHorizontally) {
ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection,
rangeTuningParameters);
@@ -102,7 +124,7 @@ typedef struct ASRangeGeometry ASRangeGeometry;
MIN(horizontalBuffer.positiveDirection * 0.5, 0.95));
}
BOOL canScrollVertically = ASScrollDirectionContainsVerticalDirection(scrollableDirections);
BOOL canScrollVertically = ASScrollDirectionContainsVerticalDirection(_scrollableDirections);
if (canScrollVertically) {
ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection,
rangeTuningParameters);
@@ -118,9 +140,10 @@ typedef struct ASRangeGeometry ASRangeGeometry;
return {rangeBounds, updateBounds};
}
- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds collectionView:(ASCollectionView *)collectionView {
- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds
{
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
NSArray *layoutAttributes = [collectionView.collectionViewLayout layoutAttributesForElementsInRect:rangeBounds];
NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds];
for (UICollectionViewLayoutAttributes *la in layoutAttributes) {
[indexPathSet addObject:la.indexPath];
}
@@ -132,13 +155,14 @@ typedef struct ASRangeGeometry ASRangeGeometry;
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths
viewportSize:(CGSize)viewportSize
rangeType:(ASLayoutRangeType)rangeType {
rangeType:(ASLayoutRangeType)rangeType
{
CGRect updateRangeBounds = _updateRangeBoundsIndexedByRangeType[rangeType];
if (CGRectIsEmpty(updateRangeBounds)) {
return YES;
}
CGRect currentBounds = _collectionView.bounds;
CGRect currentBounds = _scrollView.bounds;
if (CGRectIsEmpty(currentBounds)) {
currentBounds = CGRectMake(0, 0, viewportSize.width, viewportSize.height);
}