ricky 997d37dc83 [ASEnvironment] Don't relayout as a result of clearing a traitCollection's context (#1759)
* Don't relayout as a result of clearing a traitCollection's context

I'm not completely sure this change is the best solution. Here is context:

An ASEnvironmentTraitCollection has a pointer to an optional context that an ASVC is the owner of. When the ASVC is dealloc'ed, we go through all subnodes of the VC and clear out the context so that the struct isn't holding on to a garbage pointer.

Setting the traitCollection on ASCollectionNode/ASTableNode causes the cells to relayout if the trait collection changed (this is  a special case for these two nodes since their cells are not actually subnodes). Setting the context to nil registered as a trait collection change and was causing a layout even as we were dealloc'ing the VC.

The logic I'm implementing here is:
If the trait collection changed AND the displayContext did not, then we should relayout.
If the trait collection changed AND the new displayContext is non-nil then we should layout
In the case where the trait collection change was caused soley by the displayContext going from non-nil to nil, then we should NOT layout.

```
// At this point we know that the two traits collections are NOT equal for some reason
BOOL needsLayout = (oldTraits.displayContext == currentTraits.displayContext) || currentTraits.displayContext != nil;
```

Is there a better place/safer way to do this?

* removed extra setNeedsLayout call
2016-06-23 20:07:45 -07:00

179 lines
7.5 KiB
Objective-C

//
// ASEnvironment.h
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
#import "ASDimension.h"
#import "ASStackLayoutDefines.h"
#import "ASRelativeSize.h"
@protocol ASEnvironment;
@class UITraitCollection;
ASDISPLAYNODE_EXTERN_C_BEGIN
NS_ASSUME_NONNULL_BEGIN
static const int kMaxEnvironmentStateBoolExtensions = 1;
static const int kMaxEnvironmentStateIntegerExtensions = 4;
static const int kMaxEnvironmentStateEdgeInsetExtensions = 1;
#pragma mark -
typedef struct ASEnvironmentStateExtensions {
// Values to store extensions
BOOL boolExtensions[kMaxEnvironmentStateBoolExtensions];
NSInteger integerExtensions[kMaxEnvironmentStateIntegerExtensions];
UIEdgeInsets edgeInsetsExtensions[kMaxEnvironmentStateEdgeInsetExtensions];
} ASEnvironmentStateExtensions;
#pragma mark - ASEnvironmentLayoutOptionsState
typedef struct ASEnvironmentLayoutOptionsState {
CGFloat spacingBefore;// = 0;
CGFloat spacingAfter;// = 0;
BOOL flexGrow;// = NO;
BOOL flexShrink;// = NO;
ASRelativeDimension flexBasis;// = ASRelativeDimensionUnconstrained;
ASStackLayoutAlignSelf alignSelf;// = ASStackLayoutAlignSelfAuto;
CGFloat ascender;// = 0;
CGFloat descender;// = 0;
ASRelativeSizeRange sizeRange;// = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(CGSizeZero), ASRelativeSizeMakeWithCGSize(CGSizeZero));;
CGPoint layoutPosition;// = CGPointZero;
struct ASEnvironmentStateExtensions _extensions;
} ASEnvironmentLayoutOptionsState;
#pragma mark - ASEnvironmentHierarchyState
typedef struct ASEnvironmentHierarchyState {
unsigned rasterized:1; // = NO
unsigned rangeManaged:1; // = NO
unsigned transitioningSupernodes:1; // = NO
unsigned layoutPending:1; // = NO
} ASEnvironmentHierarchyState;
#pragma mark - ASEnvironmentDisplayTraits
typedef struct ASEnvironmentTraitCollection {
CGFloat displayScale;
UIUserInterfaceSizeClass horizontalSizeClass;
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);
extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection);
extern BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs);
#pragma mark - ASEnvironmentState
typedef struct ASEnvironmentState {
struct ASEnvironmentHierarchyState hierarchyState;
struct ASEnvironmentLayoutOptionsState layoutOptionsState;
struct ASEnvironmentTraitCollection environmentTraitCollection;
} ASEnvironmentState;
extern ASEnvironmentState ASEnvironmentStateMakeDefault();
ASDISPLAYNODE_EXTERN_C_END
@class ASTraitCollection;
#pragma mark - ASEnvironment
/**
* ASEnvironment allows objects that conform to the ASEnvironment protocol to be able to propagate specific States
* defined in an ASEnvironmentState up and down the ASEnvironment tree. To be able to define how merges of
* States should happen, specific merge functions can be provided
*/
@protocol ASEnvironment <NSObject>
/// The environment collection of an object which class conforms to the ASEnvironment protocol
- (ASEnvironmentState)environmentState;
- (void)setEnvironmentState:(ASEnvironmentState)environmentState;
/// Returns the parent of an object which class conforms to the ASEnvironment protocol
- (id<ASEnvironment> _Nullable)parent;
/// Returns all children of an object which class conforms to the ASEnvironment protocol
- (nullable NSArray<id<ASEnvironment>> *)children;
/// Classes should implement this method and return YES / NO dependent if upward propagation is enabled or not
- (BOOL)supportsUpwardPropagation;
/// Classes should implement this method and return YES / NO dependent if downware propagation is enabled or not
- (BOOL)supportsTraitsCollectionPropagation;
/// Returns an NSObject-representation of the environment's ASEnvironmentDisplayTraits
- (ASTraitCollection *)asyncTraitCollection;
/// Returns a struct-representation of the environment's ASEnvironmentDisplayTraits. This only exists as a internal
/// convenience method. Users should access the trait collections through the NSObject based asyncTraitCollection API
- (ASEnvironmentTraitCollection)environmentTraitCollection;
/// sets a trait collection on this environment state.
- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection;
@end
// ASCollection/TableNodes don't actually have ASCellNodes as subnodes. Because of this we can't rely on display trait
// downward propagation via ASEnvironment. Instead if the new environmentState has displayTraits that are different from
// the cells', then we propagate downward explicitly and request a relayout.
//
// If there is any new downward propagating state, it should be added to this define.
//
// If the only change in a trait collection is that its dislplayContext has gone from non-nil to nil,
// assume that we are clearing the context as part of a ASVC dealloc and do not trigger a layout.
//
// This logic is used in both ASCollectionNode and ASTableNode
#define ASEnvironmentCollectionTableSetEnvironmentState(lock) \
- (void)setEnvironmentState:(ASEnvironmentState)environmentState\
{\
ASDN::MutexLocker l(lock);\
ASEnvironmentTraitCollection oldTraits = self.environmentState.environmentTraitCollection;\
[super setEnvironmentState:environmentState];\
ASEnvironmentTraitCollection currentTraits = environmentState.environmentTraitCollection;\
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];\
}\
}\
}\
});\
}\
}\
NS_ASSUME_NONNULL_END