Improvements for automatic range mode

- Only update range mode if visibility changes if the node is not range controlled
- Only change explicitly set range mode if ASRangeController becomes visible
- Return interface state for range controller in ASCollectionView and ASTableView based on if the containing node is range managed
This commit is contained in:
Michael Schneider 2016-03-15 13:38:46 -07:00
parent b30fa8b2f6
commit d3ba80ccfd
6 changed files with 59 additions and 25 deletions

View File

@ -918,14 +918,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController
{
ASCollectionNode *collectionNode = self.collectionNode;
if (collectionNode) {
if (collectionNode && [collectionNode supportsRangeManagedInterfaceState]) {
// Only use the interfaceStatate of nodes that are range managed
return collectionNode.interfaceState;
} else {
// Until we can always create an associated ASCollectionNode without a retain cycle,
// we might be on our own to try to guess if we're visible. The node normally
// handles this even if it is the root / directly added to the view hierarchy.
return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone);
}
// For not range managed nodes or until we can always create an associated ASCollectionNode
// without a retain cycle, we might be on our own to try to guess if we're visible. The node normally
// handles this even if it is the root / directly added to the view hierarchy.
return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone);
}
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths
@ -1144,9 +1145,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (!visible && node.inHierarchy) {
[node __exitHierarchy];
}
// Trigger updating interfaceState for cells in case ASCollectionView becomes visible or invisible
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
// Updating the visible node index paths only for range managed nodes. Not range managed nodes will get their
// their update in the layout pass
if (![node supportsRangeManagedInterfaceState]) {
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
}
}
#pragma mark - UICollectionView dead-end intercepts

View File

@ -1980,13 +1980,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
// subclass override
}
/**
* We currently only set interface state on nodes in table/collection views. For other nodes, if they are
* in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`.
*/
- (BOOL)supportsRangeManagedInterfaceState
{
return (_hierarchyState & ASHierarchyStateRangeManaged);
return ASHierarchyStateIncludesRangeManaged(_hierarchyState);
}
- (ASInterfaceState)interfaceState

View File

@ -795,14 +795,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController
{
ASTableNode *tableNode = self.tableNode;
if (tableNode) {
if (tableNode && [tableNode supportsRangeManagedInterfaceState]) {
// Only use the interfaceStatate of nodes that are range managed
return self.tableNode.interfaceState;
} else {
// Until we can always create an associated ASTableNode without a retain cycle,
// we might be on our own to try to guess if we're visible. The node normally
// handles this even if it is the root / directly added to the view hierarchy.
return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone);
}
// For not range managed nodes or until we can always create an associated ASTableNode
// without a retain cycle, we might be on our own to try to guess if we're visible. The node normally
// handles this even if it is the root / directly added to the view hierarchy.
return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone);
}
#pragma mark - ASRangeControllerDelegate
@ -1076,9 +1077,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
if (!visible && node.inHierarchy) {
[node __exitHierarchy];
}
// Trigger updating interfaceState for cells in case ASTableView becomes visible or invisible
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
// Updating the visible node index paths only for range managed nodes. Not range managed nodes will get their
// their update in the layout pass
if (![node supportsRangeManagedInterfaceState]) {
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
}
}
@end

View File

@ -189,7 +189,9 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
ASInterfaceState selfInterfaceState = [self interfaceState];
ASLayoutRangeMode rangeMode = _currentRangeMode;
if (!_didUpdateCurrentRange) {
// If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the
// range controller becomes visible again or explicitly changes the range mode again
if (!_didUpdateCurrentRange || ASInterfaceStateIncludesVisible(selfInterfaceState)) {
rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode];
}
@ -232,7 +234,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
_allPreviousIndexPaths = allCurrentIndexPaths;
_currentRangeMode = rangeMode;
_didUpdateCurrentRange = NO;
// Reset the current range mode only if the range controller comes visible
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
_didUpdateCurrentRange = NO;
}
if (!_rangeIsValid) {
[allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)];

View File

@ -12,6 +12,18 @@
#import "ASCollectionNode.h"
#import "ASTableNode.h"
/**
* Update the range mode for a range controller to a specific range mode until the node that contains the range
* controller becomes visible again
*
* Logic for the automatic range mode:
* 1. If there are no visible node paths available nothing is to be done and no range update is done
* 2. The initial range update always will be ASLayoutRangeModeCount (ASLayoutRangeModeMinimum) as it's the initial fetch
* 3. If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the range controller becomes visible and a new range update was triggered or a new range mode via updateCurrentRangeWithMode: is set
* 4. If range mode is not explicitly set the range mode is variying based if the range controller is visible or not
*/
@protocol ASRangeControllerUpdateRangeProtocol <NSObject>

View File

@ -56,6 +56,11 @@ inline BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyStat
return ((hierarchyState & ASHierarchyStateLayoutPending) == ASHierarchyStateLayoutPending);
}
inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged);
}
@interface ASDisplayNode ()
{
@protected
@ -91,6 +96,14 @@ inline BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyStat
*/
@property (nonatomic, readwrite) ASHierarchyState hierarchyState;
/**
* @abstract Return if the node is range managed or not
*
* @discussion Currently only set interface state on nodes in table and collection views. For other nodes, if they are
* in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`.
*/
- (BOOL)supportsRangeManagedInterfaceState;
// The two methods below will eventually be exposed, but their names are subject to change.
/**
* @abstract Ensure that all rendering is complete for this node and its descendents.