This commit is contained in:
Hannah Trosi
2016-07-08 12:40:42 -07:00
17 changed files with 249 additions and 243 deletions

View File

@@ -25,11 +25,11 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout;
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (strong, nonatomic, readonly) ASCollectionView *view;
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
@property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
@property (nonatomic, readonly) ASCollectionView *view;
/**
* Tuning parameters for a range type in full mode.
*

View File

@@ -12,52 +12,76 @@
#import "ASCollectionNode.h"
#import "ASCollectionInternal.h"
#import "ASCollectionViewLayoutFacilitatorProtocol.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASEnvironmentInternal.h"
#import "ASInternalHelpers.h"
#import "ASRangeControllerUpdateRangeProtocol+Beta.h"
#include <vector>
#pragma mark - _ASCollectionPendingState
@interface _ASCollectionPendingState : NSObject
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
@property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
@property (assign, nonatomic) ASLayoutRangeMode rangeMode;
@end
@implementation _ASCollectionPendingState
@end
#if 0 // This is not used yet, but will provide a way to avoid creating the view to set range values.
@implementation _ASCollectionPendingState
- (instancetype)init
{
std::vector<ASRangeTuningParameters> _tuningParameters;
self = [super init];
if (self) {
_rangeMode = ASLayoutRangeModeCount;
}
return self;
}
@end
// TODO: Add support for tuning parameters in the pending state
#if 0 // This is not used yet, but will provide a way to avoid creating the view to set range values.
@implementation _ASCollectionPendingState {
std::vector<std::vector<ASRangeTuningParameters>> _tuningParameters;
}
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
self = [super init];
if (self) {
_tuningParameters = std::vector<std::vector<ASRangeTuningParameters>> (ASLayoutRangeModeCount, std::vector<ASRangeTuningParameters> (ASLayoutRangeTypeCount));
_rangeMode = ASLayoutRangeModeCount;
}
_tuningParameters = std::vector<ASRangeTuningParameters>(ASLayoutRangeTypeCount);
return self;
}
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters");
return _tuningParameters[rangeType];
return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
}
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters");
ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range tuning parameters (always 0, 0)");
_tuningParameters[rangeType] = tuningParameters;
return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
}
- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters");
return _tuningParameters[rangeMode][rangeType];
}
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters");
_tuningParameters[rangeMode][rangeType] = tuningParameters;
}
@end
#endif
#pragma mark - ASCollectionNode
@interface ASCollectionNode ()
{
ASDN::RecursiveMutex _environmentStateLock;
@@ -67,6 +91,8 @@
@implementation ASCollectionNode
#pragma mark Lifecycle
- (instancetype)init
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
@@ -109,6 +135,8 @@
return nil;
}
#pragma mark ASDisplayNode
- (void)didLoad
{
[super didLoad];
@@ -121,9 +149,39 @@
self.pendingState = nil;
view.asyncDelegate = pendingState.delegate;
view.asyncDataSource = pendingState.dataSource;
if (pendingState.rangeMode != ASLayoutRangeModeCount) {
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
}
}
}
- (ASCollectionView *)view
{
return (ASCollectionView *)[super view];
}
- (void)clearContents
{
[super clearContents];
[self.view clearContents];
}
- (void)clearFetchedData
{
[super clearFetchedData];
[self.view clearFetchedData];
}
#if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible
{
[super visibleStateDidChange:isVisible];
NSLog(@"%@ - visible: %d", self, isVisible);
}
#endif
#pragma mark Setter / Getter
- (_ASCollectionPendingState *)pendingState
{
if (!_pendingState && ![self isNodeLoaded]) {
@@ -171,47 +229,7 @@
}
}
- (ASCollectionView *)view
{
return (ASCollectionView *)[super view];
}
#if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible
{
[super visibleStateDidChange:isVisible];
NSLog(@"%@ - visible: %d", self, isVisible);
}
#endif
- (void)clearContents
{
[super clearContents];
[self.view clearContents];
}
- (void)clearFetchedData
{
[super clearFetchedData];
[self.view clearFetchedData];
}
- (void)beginUpdates
{
[self.view.dataController beginUpdates];
}
- (void)endUpdatesAnimated:(BOOL)animated
{
[self endUpdatesAnimated:animated completion:nil];
}
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
{
[self.view.dataController endUpdatesAnimated:animated completion:completion];
}
#pragma mark - ASCollectionView Forwards
#pragma mark ASCollectionView Forwards
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
{
@@ -233,11 +251,6 @@
return [self.view.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType];
}
- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode;
{
[self.view.rangeController updateCurrentRangeWithMode:rangeMode];
}
- (void)reloadDataWithCompletion:(void (^)())completion
{
[self.view reloadDataWithCompletion:completion];
@@ -253,6 +266,34 @@
[self.view reloadDataImmediately];
}
- (void)beginUpdates
{
[self.view.dataController beginUpdates];
}
- (void)endUpdatesAnimated:(BOOL)animated
{
[self endUpdatesAnimated:animated completion:nil];
}
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
{
[self.view.dataController endUpdatesAnimated:animated completion:completion];
}
#pragma mark - ASRangeControllerUpdateRangeProtocol
- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode;
{
if ([self pendingState]) {
_pendingState.rangeMode = rangeMode;
} else {
[self.view.rangeController updateCurrentRangeWithMode:rangeMode];
}
}
#pragma mark ASEnvironment
ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock)
@end

View File

@@ -107,6 +107,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle;
/**
* Our layer, retained. Under iOS < 9, when collection views are removed from the hierarchy,
* their layers may be deallocated and become dangling pointers. This puts the collection view
* into a very dangerous state where pretty much any call will crash it. So we manually retain our layer.
*
* You should never access this, and it will be nil under iOS >= 9.
*/
CALayer *_retainedLayer;
/**
* If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it.
@@ -248,6 +257,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
[self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kCellReuseIdentifier];
if (!AS_AT_LEAST_IOS9) {
_retainedLayer = self.layer;
}
return self;
}

View File

@@ -245,9 +245,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
dispatch_once(&onceToken, ^{
renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain()
andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) {
CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0;
[dequeuedItem _recursivelyTriggerDisplayAndBlock:NO];
if (isQueueDrained) {
CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent();
[[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil
userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}];
@@ -2317,27 +2317,27 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
});
}
- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState
- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState
{
ASInterfaceState oldState = self.interfaceState;
ASInterfaceState newState = interfaceState;
// Instead of each node in the recursion assuming it needs to schedule itself for display,
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
// If our range manager intends for us to be displayed right now, and didn't before, get started!
BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState];
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState = interfaceState;
node.interfaceState = newInterfaceState;
});
if ([self supportsRangeManagedInterfaceState]) {
// Instead of each node in the recursion assuming it needs to schedule itself for display,
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
// If our range manager intends for us to be displayed right now, and didn't before, get started!
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState);
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState);
if (nowDisplay && (nowDisplay != wasDisplay)) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
if (shouldScheduleDisplay) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
}
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState
{
BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState);
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState);
return willDisplay && (willDisplay != nowDisplay);
}
- (ASHierarchyState)hierarchyState
{
ASDN::MutexLocker l(_propertyLock);

View File

@@ -21,7 +21,7 @@
- (instancetype)init; // UITableViewStylePlain
- (instancetype)initWithStyle:(UITableViewStyle)style;
@property (nonatomic, readonly) ASTableView *view;
@property (strong, nonatomic, readonly) ASTableView *view;
// These properties can be set without triggering the view to be created, so it's fine to set them in -init.
@property (weak, nonatomic) id <ASTableDelegate> delegate;

View File

@@ -10,21 +10,35 @@
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "ASTableNode.h"
#import "ASTableViewInternal.h"
#import "ASEnvironmentInternal.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASFlowLayoutController.h"
#import "ASInternalHelpers.h"
#import "ASRangeControllerUpdateRangeProtocol+Beta.h"
#import "ASTableViewInternal.h"
#pragma mark - _ASTablePendingState
@interface _ASTablePendingState : NSObject
@property (weak, nonatomic) id <ASTableDelegate> delegate;
@property (weak, nonatomic) id <ASTableDataSource> dataSource;
@property (assign, nonatomic) ASLayoutRangeMode rangeMode;
@end
@implementation _ASTablePendingState
- (instancetype)init
{
self = [super init];
if (self) {
_rangeMode = ASLayoutRangeModeCount;
}
return self;
}
@end
#pragma mark - ASTableView
@interface ASTableNode ()
{
ASDN::RecursiveMutex _environmentStateLock;
@@ -39,6 +53,8 @@
@implementation ASTableNode
#pragma mark Lifecycle
- (instancetype)_initWithTableView:(ASTableView *)tableView
{
// Avoid a retain cycle. In this case, the ASTableView is creating us, and strongly retains us.
@@ -72,6 +88,8 @@
return [self _initWithFrame:CGRectZero style:UITableViewStylePlain dataControllerClass:nil];
}
#pragma mark ASDisplayNode
- (void)didLoad
{
[super didLoad];
@@ -84,22 +102,43 @@
self.pendingState = nil;
view.asyncDelegate = pendingState.delegate;
view.asyncDataSource = pendingState.dataSource;
if (pendingState.rangeMode != ASLayoutRangeModeCount) {
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
}
}
}
- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode
- (ASTableView *)view
{
if (!self.isNodeLoaded) {
return;
}
[self.view.rangeController updateCurrentRangeWithMode:rangeMode];
return (ASTableView *)[super view];
}
- (void)clearContents
{
[super clearContents];
[self.view clearContents];
}
- (void)clearFetchedData
{
[super clearFetchedData];
[self.view clearFetchedData];
}
#if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible
{
[super visibleStateDidChange:isVisible];
NSLog(@"%@ - visible: %d", self, isVisible);
}
#endif
#pragma mark Setter / Getter
- (_ASTablePendingState *)pendingState
{
if (!_pendingState && ![self isNodeLoaded]) {
self.pendingState = [[_ASTablePendingState alloc] init];
_pendingState = [[_ASTablePendingState alloc] init];
}
ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode should not have a pendingState once it is loaded");
return _pendingState;
@@ -143,30 +182,19 @@
}
}
- (ASTableView *)view
#pragma mark ASRangeControllerUpdateRangeProtocol
- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode
{
return (ASTableView *)[super view];
if ([self pendingState]) {
_pendingState.rangeMode = rangeMode;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
[self.view.rangeController updateCurrentRangeWithMode:rangeMode];
}
}
#if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible
{
[super visibleStateDidChange:isVisible];
NSLog(@"%@ - visible: %d", self, isVisible);
}
#endif
- (void)clearContents
{
[super clearContents];
[self.view clearContents];
}
- (void)clearFetchedData
{
[super clearFetchedData];
[self.view clearFetchedData];
}
#pragma mark ASEnvironment
ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock)

View File

@@ -27,18 +27,6 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C
@property (nonatomic, strong, readonly) DisplayNodeType node;
/**
* An optional context to pass along with an ASTraitCollection.
* This can be used to pass any internal state to all subnodes via the ASTraitCollection that is not
* included in UITraitCollection. This could range from more fine-tuned size classes to a class of
* constants that is based upon the new trait collection.
*
* Be aware that internally this context is held by a C struct which cannot retain the pointer. Therefore
* ASVC keeps a strong reference to the context to make sure that it stays alive. If you change this value
* it will propagate the change to the subnodes.
*/
@property (nonatomic, strong) id _Nullable traitCollectionContext;
/**
* Set this block to customize the ASDisplayTraits returned when the VC transitions to the given traitCollection.
*/

View File

@@ -58,17 +58,6 @@
return self;
}
- (void)dealloc
{
if (_traitCollectionContext != nil) {
// The setter will iterate through the VC's subnodes and replace the traitCollectionContext in their ASEnvironmentTraitCollection with nil.
// Since the VC holds the only strong reference to this context and we are in the process of destroying
// the VC, all the references in the subnodes will be unsafe unless we nil them out. More than likely all the subnodes will be dealloc'ed
// as part of the VC being dealloc'ed, but this is just to make extra sure.
self.traitCollectionContext = nil;
}
}
- (void)loadView
{
ASDisplayNodeAssertTrue(!_node.layerBacked);
@@ -199,27 +188,16 @@ ASVisibilityDepthImplementation;
#pragma mark - ASEnvironmentTraitCollection
- (void)setTraitCollectionContext:(id)traitCollectionContext
{
if (_traitCollectionContext != traitCollectionContext) {
// nil out the displayContext in the subnodes so they aren't hanging around with a dealloc'ed pointer don't set
// the new context yet as this will cause ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection to fail
ASEnvironmentTraitCollectionUpdateDisplayContext(self.node, nil);
_traitCollectionContext = traitCollectionContext;
}
}
- (ASEnvironmentTraitCollection)environmentTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection
{
if (self.overrideDisplayTraitsWithTraitCollection) {
ASTraitCollection *asyncTraitCollection = self.overrideDisplayTraitsWithTraitCollection(traitCollection);
self.traitCollectionContext = asyncTraitCollection.traitCollectionContext;
return [asyncTraitCollection environmentTraitCollection];
}
ASDisplayNodeAssertMainThread();
ASEnvironmentTraitCollection asyncTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(traitCollection);
asyncTraitCollection.displayContext = self.traitCollectionContext;
asyncTraitCollection.containerSize = self.view.frame.size;
return asyncTraitCollection;
}
@@ -227,9 +205,9 @@ ASVisibilityDepthImplementation;
{
if (self.overrideDisplayTraitsWithWindowSize) {
ASTraitCollection *traitCollection = self.overrideDisplayTraitsWithWindowSize(windowSize);
self.traitCollectionContext = traitCollection.traitCollectionContext;
return [traitCollection environmentTraitCollection];
}
self.node.environmentTraitCollection.containerSize = windowSize;
return self.node.environmentTraitCollection;
}
@@ -255,17 +233,13 @@ ASVisibilityDepthImplementation;
[super traitCollectionDidChange:previousTraitCollection];
ASEnvironmentTraitCollection environmentTraitCollection = [self environmentTraitCollectionForUITraitCollection:self.traitCollection];
environmentTraitCollection.containerSize = self.view.bounds.size;
[self progagateNewEnvironmentTraitCollection:environmentTraitCollection];
}
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
ASEnvironmentTraitCollection environmentTraitCollection = [self environmentTraitCollectionForUITraitCollection:newCollection];
[self progagateNewEnvironmentTraitCollection:environmentTraitCollection];
}
// Note: We don't override willTransitionToTraitCollection:withTransitionCoordinator: because viewWillTransitionToSize:withTransitionCoordinator: will also called
// called in all these cases. However, there are cases where viewWillTransitionToSize:withTransitionCoordinator: but willTransitionToTraitCollection:withTransitionCoordinator:
// is not.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

View File

@@ -91,15 +91,13 @@ extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningPar
- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(),
@"Requesting a range that is OOB for the configured tuning parameters");
ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters");
return _tuningParameters[rangeMode][rangeType];
}
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(),
@"Setting a range that is OOB for the configured tuning parameters");
ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters");
_tuningParameters[rangeMode][rangeType] = tuningParameters;
}

View File

@@ -27,7 +27,8 @@
- (void)beginUpdates
{
ASDisplayNodeAssertMainThread();
// NOTE: This assertion is failing in some apps and will be enabled soon.
// ASDisplayNodeAssertMainThread();
if (_changeSetBatchUpdateCounter <= 0) {
_changeSet = [_ASHierarchyChangeSet new];
_changeSetBatchUpdateCounter = 0;
@@ -37,11 +38,13 @@
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
{
ASDisplayNodeAssertMainThread();
// NOTE: This assertion is failing in some apps and will be enabled soon.
// ASDisplayNodeAssertMainThread();
_changeSetBatchUpdateCounter--;
// Prevent calling endUpdatesAnimated:completion: in an unbalanced way
NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call");
// NOTE: This assertion is failing in some apps and will be enabled soon.
// NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call");
if (_changeSetBatchUpdateCounter == 0) {
[_changeSet markCompleted];

View File

@@ -69,25 +69,9 @@ typedef struct ASEnvironmentTraitCollection {
UIUserInterfaceIdiom userInterfaceIdiom;
UIUserInterfaceSizeClass verticalSizeClass;
UIForceTouchCapability forceTouchCapability;
// WARNING:
// This pointer is in a C struct and therefore not managed by ARC. It is
// an unsafe unretained pointer, so when you dereference it you better be
// sure that it is valid.
//
// Use displayContext when you wish to pass view context specific data along with the
// display traits to subnodes. This should be a piece of data owned by an
// ASViewController, which will ensure that the data is still valid when laying out
// its subviews. When the VC is dealloc'ed, the displayContext it created will also
// be dealloced but any subnodes that are hanging around (why would they be?) will now
// have a displayContext that points to a bad pointer.
//
// As an added precaution ASDisplayTraitsClearDisplayContext is called from ASVC's desctructor
// which will propagate a nil displayContext to its subnodes.
id __unsafe_unretained displayContext;
} ASEnvironmentTraitCollection;
extern void ASEnvironmentTraitCollectionUpdateDisplayContext(id<ASEnvironment> rootEnvironment, id _Nullable context);
CGSize containerSize;
} ASEnvironmentTraitCollection;
extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection);
extern BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs);
@@ -165,14 +149,11 @@ ASDISPLAYNODE_EXTERN_C_END
if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(currentTraits, oldTraits) == NO) {\
/* Must dispatch to main for self.view && [self.view.dataController completedNodes]*/ \
ASPerformBlockOnMainThread(^{\
BOOL needsLayout = (oldTraits.displayContext == currentTraits.displayContext) || currentTraits.displayContext != nil;\
NSArray<NSArray <ASCellNode *> *> *completedNodes = [self.view.dataController completedNodes];\
for (NSArray *sectionArray in completedNodes) {\
for (ASCellNode *cellNode in sectionArray) {\
ASEnvironmentStatePropagateDown(cellNode, currentTraits);\
if (needsLayout) {\
[cellNode setNeedsLayout];\
}\
[cellNode setNeedsLayout];\
}\
}\
});\

View File

@@ -26,23 +26,11 @@ ASEnvironmentHierarchyState _ASEnvironmentHierarchyStateMakeDefault()
};
}
extern void ASEnvironmentTraitCollectionUpdateDisplayContext(id<ASEnvironment> rootEnvironment, id context)
{
ASEnvironmentState envState = [rootEnvironment environmentState];
ASEnvironmentTraitCollection environmentTraitCollection = envState.environmentTraitCollection;
environmentTraitCollection.displayContext = context;
envState.environmentTraitCollection = environmentTraitCollection;
[rootEnvironment setEnvironmentState:envState];
for (id<ASEnvironment> child in [rootEnvironment children]) {
ASEnvironmentStatePropagateDown(child, environmentTraitCollection);
}
}
ASEnvironmentTraitCollection _ASEnvironmentTraitCollectionMakeDefault()
{
return (ASEnvironmentTraitCollection) {
// Default values can be defined in here
.containerSize = CGSizeZero,
};
}
@@ -69,7 +57,7 @@ BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnviron
lhs.displayScale == rhs.displayScale &&
lhs.userInterfaceIdiom == rhs.userInterfaceIdiom &&
lhs.forceTouchCapability == rhs.forceTouchCapability &&
lhs.displayContext == rhs.displayContext;
CGSizeEqualToSize(lhs.containerSize, rhs.containerSize);
}
ASEnvironmentState ASEnvironmentStateMakeDefault()

View File

@@ -27,7 +27,7 @@
NSSet<NSIndexPath *> *_allPreviousIndexPaths;
ASLayoutRangeMode _currentRangeMode;
BOOL _didUpdateCurrentRange;
BOOL _didRegisterForNotifications;
BOOL _didRegisterForNodeDisplayNotifications;
CFAbsoluteTime _pendingDisplayNodesTimestamp;
}
@@ -56,7 +56,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
- (void)dealloc
{
if (_didRegisterForNotifications) {
if (_didRegisterForNodeDisplayNotifications) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil];
}
}
@@ -242,10 +242,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
[allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)];
}
// TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline.
// This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings
[self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
#if ASRangeControllerLoggingEnabled
ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]);
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
@@ -309,16 +305,21 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
#if ASRangeControllerLoggingEnabled
[modifiedIndexPaths addObject:indexPath];
#endif
BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState];
[node recursivelySetInterfaceState:interfaceState];
if (nodeShouldScheduleDisplay) {
[self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
if (_didRegisterForNodeDisplayNotifications) {
_pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent();
}
}
}
}
}
}
if (_didRegisterForNotifications) {
_pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent();
}
_rangeIsValid = YES;
_queuedRangeUpdate = NO;
@@ -338,9 +339,9 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
#pragma mark - Notification observers
- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState
- (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState
{
if (!_didRegisterForNotifications) {
if (!_didRegisterForNodeDisplayNotifications) {
ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState
currentRangeMode:_currentRangeMode];
if (_currentRangeMode != nextRangeMode) {
@@ -348,7 +349,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
selector:@selector(scheduledNodesDidDisplay:)
name:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil];
_didRegisterForNotifications = YES;
_didRegisterForNodeDisplayNotifications = YES;
}
}
}
@@ -359,7 +360,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
if (_pendingDisplayNodesTimestamp < notificationTimestamp) {
// The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil];
_didRegisterForNotifications = NO;
_didRegisterForNodeDisplayNotifications = NO;
[self scheduleRangeUpdate];
}

View File

@@ -18,27 +18,13 @@
@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom;
@property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass;
@property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability;
/**
* An optional context to pass along with an ASTraitCollection.
* This can be used to pass any internal state to all subnodes via the ASTraitCollection that is not
* included in UITraitCollection. This could range from more fine-tuned size classes to a class of
* constants that is based upon the new trait collection.
*
* Be aware that internally this context is held by a C struct which cannot retain the pointer.
* ASTraitCollection is generally a very short-lived class, existing only to provide a non-struct API
* to trait collections. When an ASTraitCollection is returned via one of ASViewController's 2
* custom trait collection creation blocks, traitCollectionContext is assigned to the VC's traitCollectionContext.
* This makes sure that the VC is the owner of the context and ASEnvironmentTraitCollections will not
* have a reference to a dangling pointer.
*/
@property (nonatomic, strong, readonly) id traitCollectionContext;
@property (nonatomic, assign, readonly) CGSize containerSize;
+ (ASTraitCollection *)traitCollectionWithASEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traits;
+ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection
traitCollectionContext:(id)traitCollectionContext;
containerSize:(CGSize)windowSize;
+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale
@@ -46,7 +32,7 @@
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
traitCollectionContext:(id)traitCollectionContext;
containerSize:(CGSize)windowSize;
- (ASEnvironmentTraitCollection)environmentTraitCollection;

View File

@@ -21,7 +21,7 @@
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
traitCollectionContext:(id)traitCollectionContext
containerSize:(CGSize)windowSize
{
self = [super init];
if (self) {
@@ -30,7 +30,7 @@
_horizontalSizeClass = horizontalSizeClass;
_verticalSizeClass = verticalSizeClass;
_forceTouchCapability = forceTouchCapability;
_traitCollectionContext = traitCollectionContext;
_containerSize = windowSize;
}
return self;
}
@@ -40,29 +40,29 @@
horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass
forceTouchCapability:(UIForceTouchCapability)forceTouchCapability
traitCollectionContext:(id)traitCollectionContext
containerSize:(CGSize)windowSize
{
return [[[self class] alloc] initWithDisplayScale:displayScale
userInterfaceIdiom:userInterfaceIdiom
horizontalSizeClass:horizontalSizeClass
verticalSizeClass:verticalSizeClass
forceTouchCapability:forceTouchCapability
traitCollectionContext:traitCollectionContext];
containerSize:windowSize];
}
+ (ASTraitCollection *)traitCollectionWithASEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traits
{
return [[[self class] alloc] initWithDisplayScale:traits.displayScale
userInterfaceIdiom:traits.userInterfaceIdiom
horizontalSizeClass:traits.horizontalSizeClass
verticalSizeClass:traits.verticalSizeClass
forceTouchCapability:traits.forceTouchCapability
traitCollectionContext:traits.displayContext];
return [[[self class] alloc] initWithDisplayScale:traits.displayScale
userInterfaceIdiom:traits.userInterfaceIdiom
horizontalSizeClass:traits.horizontalSizeClass
verticalSizeClass:traits.verticalSizeClass
forceTouchCapability:traits.forceTouchCapability
containerSize:traits.containerSize];
}
+ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection
traitCollectionContext:(id)traitCollectionContext
containerSize:(CGSize)windowSize
{
ASTraitCollection *asyncTraitCollection = nil;
if (AS_AT_LEAST_IOS9) {
@@ -71,7 +71,7 @@
horizontalSizeClass:traitCollection.horizontalSizeClass
verticalSizeClass:traitCollection.verticalSizeClass
forceTouchCapability:traitCollection.forceTouchCapability
traitCollectionContext:traitCollectionContext];
containerSize:windowSize];
}
else if (AS_AT_LEAST_IOS8) {
asyncTraitCollection = [[[self class] alloc] initWithDisplayScale:traitCollection.displayScale
@@ -79,7 +79,7 @@
horizontalSizeClass:traitCollection.horizontalSizeClass
verticalSizeClass:traitCollection.verticalSizeClass
forceTouchCapability:0
traitCollectionContext:traitCollectionContext];
containerSize:windowSize];
} else {
asyncTraitCollection = [[[self class] alloc] init];
}
@@ -95,7 +95,7 @@
.userInterfaceIdiom = self.userInterfaceIdiom,
.verticalSizeClass = self.verticalSizeClass,
.forceTouchCapability = self.forceTouchCapability,
.displayContext = self.traitCollectionContext,
.containerSize = self.containerSize,
};
}
@@ -105,7 +105,7 @@
self.horizontalSizeClass == traitCollection.horizontalSizeClass &&
self.verticalSizeClass == traitCollection.verticalSizeClass &&
self.userInterfaceIdiom == traitCollection.userInterfaceIdiom &&
self.traitCollectionContext == traitCollection.traitCollectionContext &&
CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) &&
self.forceTouchCapability == traitCollection.forceTouchCapability;
}

View File

@@ -135,6 +135,11 @@ inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState
*/
@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay;
/**
* @abstract Checks whether a node should be scheduled for display, considering its current and new interface states.
*/
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState;
@end
@interface UIView (ASDisplayNodeInternal)

View File

@@ -207,7 +207,7 @@ ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState childEnvi
childTraitCollection.userInterfaceIdiom = parentTraitCollection.userInterfaceIdiom;
childTraitCollection.forceTouchCapability = parentTraitCollection.forceTouchCapability;
childTraitCollection.displayScale = parentTraitCollection.displayScale;
childTraitCollection.displayContext = parentTraitCollection.displayContext;
childTraitCollection.containerSize = parentTraitCollection.containerSize;
childEnvironmentState.environmentTraitCollection = childTraitCollection;
}