Cleanup calculateLayoutThatFits: (#2480)

This commit is contained in:
Michael Schneider
2016-10-26 11:26:57 -07:00
committed by Adlai Holler
parent 12534ee6fb
commit 6d5bd6e969
10 changed files with 123 additions and 167 deletions

View File

@@ -63,9 +63,6 @@ typedef struct {
+ (BOOL)suppressesInvalidCollectionUpdateExceptions AS_WARN_UNUSED_RESULT;
+ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses;
/** @name Layout */
/**
* @abstract Recursively ensures node and all subnodes are displayed.
* @see Full documentation in ASDisplayNode+FrameworkPrivate.h
@@ -97,8 +94,6 @@ typedef struct {
*/
@property (nonatomic, assign, readonly) ASDisplayNodePerformanceMeasurements performanceMeasurements;
/** @name Layout Transitioning */
/**
* @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network.
* Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished.

View File

@@ -41,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface ASDisplayNode (Subclassing)
#pragma mark - Properties
/** @name Properties */
/**
@@ -64,9 +65,9 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nullable, nonatomic, readonly, assign) ASLayout *calculatedLayout;
#pragma mark - View Lifecycle
/** @name View Lifecycle */
/**
* @abstract Called on the main thread immediately after self.view is created.
*
@@ -75,9 +76,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)didLoad ASDISPLAYNODE_REQUIRES_SUPER;
#pragma mark - Layout
/** @name Layout */
/**
* @abstract Called on the main thread by the view's -layoutSubviews.
*
@@ -101,6 +102,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)calculatedLayoutDidChange ASDISPLAYNODE_REQUIRES_SUPER;
#pragma mark - Layout calculation
/** @name Layout calculation */
/**
@@ -159,9 +162,9 @@ NS_ASSUME_NONNULL_BEGIN
*
* @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedLayout instead.
*
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger
* an exception. A future version of the framework may support using both, calling them serially, with the
* .layoutSpecBlock superseding any values set by the method override.
* @warning Subclasses that implement -layoutSpecThatFits: must not use .layoutSpecBlock. Doing so will trigger an
* exception. A future version of the framework may support using both, calling them serially, with the .layoutSpecBlock
* superseding any values set by the method override.
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize;
@@ -174,9 +177,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)invalidateCalculatedLayout;
#pragma mark - Drawing
/** @name Drawing */
/**
* @summary Delegate method to draw layer contents into a CGBitmapContext. The current UIGraphics context will be set
* to an appropriate context.
@@ -407,9 +410,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign, readonly) CGFloat contentsScaleForDisplay;
#pragma mark - Touch handling
/** @name Touch handling */
/**
* @abstract Tells the node when touches began in its view.
*
@@ -443,9 +446,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
#pragma mark - Managing Gesture Recognizers
/** @name Managing Gesture Recognizers */
/**
* @abstract Asks the node if a gesture recognizer should continue tracking touches.
*
@@ -454,8 +457,9 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
/** @name Hit Testing */
#pragma mark - Hit Testing
/** @name Hit Testing */
/**
* @abstract Returns the view that contains the point.
@@ -472,6 +476,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
#pragma mark - Placeholders
/** @name Placeholders */
/**
@@ -492,6 +498,7 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable UIImage *)placeholderImage;
#pragma mark - Description
/** @name Description */
/**

View File

@@ -186,10 +186,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
// At most a layoutSpecBlock or one of the three layout methods is overridden
#define __ASDisplayNodeCheckForLayoutMethodOverrides \
ASDisplayNodeAssert(_layoutSpecBlock != NULL || \
(ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \
((ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0) <= 1, \
@"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits, layoutSpecThatFits or calculateSizeThatFits", NSStringFromClass(self.class))
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \
@"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class))
+ (void)initialize
{
@@ -199,14 +199,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
// Subclasses should never override these. Use unused to prevent warnings
__unused NSString *classString = NSStringFromClass(self);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measure:)), @"Subclass %@ must not override measure: method", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange: method. Instead overwrite calculateLayoutThatFits:", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearFetchedData)), @"Subclass %@ must not override recursivelyClearFetchedData method", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead overwrite calculateLayoutThatFits:.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead overwrite calculateLayoutThatFits:.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearFetchedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString);
}
// Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values
@@ -232,18 +232,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
#if DEBUG
// Check if subnodes where modified during layoutSpecThatFits:
if (self == [ASDisplayNode class] || ASSubclassOverridesSelector([ASDisplayNode class], self, @selector(layoutSpecThatFits:)))
{
__block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(layoutSpecThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) {
// Check if subnodes where modified during the creation of the layout
if (self == [ASDisplayNode class]) {
__block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(_layoutElementThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) {
NSArray *oldSubnodes = _self.subnodes;
ASLayoutSpec *layoutSpec = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(layoutSpecThatFits:), sizeRange);
ASLayoutSpec *layoutElement = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_layoutElementThatFits:), sizeRange);
NSArray *subnodes = _self.subnodes;
ASDisplayNodeAssert(oldSubnodes.count == subnodes.count, @"Adding or removing nodes in layoutSpecThatFits: is verboten.");
ASDisplayNodeAssert(oldSubnodes.count == subnodes.count, @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior.");
for (NSInteger i = 0; i < oldSubnodes.count; i++) {
ASDisplayNodeAssert(oldSubnodes[i] == subnodes[i], @"Adding and removing nodes in layoutSpecThatFits: is verboten.");
ASDisplayNodeAssert(oldSubnodes[i] == subnodes[i], @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior.");
}
return layoutSpec;
return layoutElement;
});
}
#endif
@@ -2425,62 +2424,67 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
__ASDisplayNodeCheckForLayoutMethodOverrides;
ASDN::MutexLocker l(__instanceLock__);
if ((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) || _layoutSpecBlock != NULL) {
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
if (measureLayoutSpec) {
_layoutSpecNumberOfPasses++;
}
ASLayoutSpec *layoutSpec = ({
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
[self layoutSpecThatFits:constrainedSize];
});
#if AS_DEDUPE_LAYOUT_SPEC_TREE
NSSet *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree];
if (duplicateElements.count > 0) {
ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements);
// Use an empty layout spec to avoid crash.
layoutSpec = [[ASLayoutSpec alloc] init];
}
#endif
ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec);
layoutSpec.parent = self; // This causes upward propogation of any non-default layoutElement values.
// manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection
{
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
ASEnvironmentStatePropagateDown(layoutSpec, self.environmentTraitCollection);
}
layoutSpec.isMutable = NO;
BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation;
if (measureLayoutComputation) {
_layoutComputationNumberOfPasses++;
}
ASLayout *layout = ({
ASDN::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation);
[layoutSpec layoutThatFits:constrainedSize];
});
ASDisplayNodeAssertNotNil(layout, @"[ASLayoutSpec measureWithSizeRange:] should never return nil! %@, %@", self, layoutSpec);
// Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct.
BOOL isFinalLayoutElement = (layout.layoutElement != self);
if (isFinalLayoutElement) {
layout.position = CGPointZero;
layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]];
}
ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout);
return [layout filteredNodeLayoutTree];
} else {
// Manual size calculation via calculateSizeThatFits:
if (((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) ||
(_layoutSpecBlock != NULL)) == NO) {
CGSize size = [self calculateSizeThatFits:constrainedSize.max];
ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size));
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil];
}
// Size calcualtion with layout elements
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
if (measureLayoutSpec) {
_layoutSpecNumberOfPasses++;
}
// Get layout element from the node
id<ASLayoutElement> layoutElement = [self _layoutElementThatFits:constrainedSize];
// Certain properties are necessary to set on an element of type ASLayoutSpec
if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) {
ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement;
NSSet *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree];
if (duplicateElements.count > 0) {
ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements);
// Use an empty layout spec to avoid crashes
layoutSpec = [[ASLayoutSpec alloc] init];
}
ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec);
layoutSpec.parent = self;
layoutSpec.isMutable = NO;
}
// Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection
{
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
ASEnvironmentStatePropagateDown(layoutElement, [self environmentTraitCollection]);
}
BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation;
if (measureLayoutComputation) {
_layoutComputationNumberOfPasses++;
}
// Layout element layout creation
ASLayout *layout = ({
ASDN::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation);
[layoutElement layoutThatFits:constrainedSize];
});
ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout);
// Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct.
BOOL isFinalLayoutElement = (layout.layoutElement != self);
if (isFinalLayoutElement) {
layout.position = CGPointZero;
layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]];
}
ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout);
return [layout filteredNodeLayoutTree];
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
@@ -2490,15 +2494,28 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
return CGSizeZero;
}
- (id<ASLayoutElement>)_layoutElementThatFits:(ASSizeRange)constrainedSize
{
__ASDisplayNodeCheckForLayoutMethodOverrides;
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
if (_layoutSpecBlock != NULL) {
return ({
ASDN::MutexLocker l(__instanceLock__);
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
_layoutSpecBlock(self, constrainedSize);
});
} else {
return ({
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
[self layoutSpecThatFits:constrainedSize];
});
}
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
__ASDisplayNodeCheckForLayoutMethodOverrides;
ASDN::MutexLocker l(__instanceLock__);
if (_layoutSpecBlock != NULL) {
return _layoutSpecBlock(self, constrainedSize);
}
ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported.");
return [[ASLayoutSpec alloc] init];
@@ -2506,7 +2523,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
{
// For now there should never be a overwrite of layoutSpecThatFits: and a layoutSpecThatFitsBlock: be provided
// For now there should never be an overwrite of layoutSpecThatFits: / layoutElementThatFits: and a layoutSpecBlock
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), @"Overwriting layoutSpecThatFits: and providing a layoutSpecBlock block is currently not supported");
ASDN::MutexLocker l(__instanceLock__);
@@ -3421,11 +3438,6 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
return self.subnodes;
}
- (BOOL)supportsUpwardPropagation
{
return ASEnvironmentStatePropagationEnabled();
}
- (BOOL)supportsTraitsCollectionPropagation
{
return ASEnvironmentStateTraitCollectionPropagationEnabled();

View File

@@ -110,8 +110,9 @@ ASDISPLAYNODE_EXTERN_C_END
/// 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 upward propagation is enabled or not
// Currently this is disabled as propagation of any attributions besides trait collections is not supported at the moment
// - (BOOL)supportsUpwardPropagation;
/// Classes should implement this method and return YES / NO dependent if downware propagation is enabled or not
- (BOOL)supportsTraitsCollectionPropagation;

View File

@@ -100,16 +100,6 @@
@end
#pragma mark - ASEnvironment
@implementation ASAbsoluteLayoutSpec (ASEnvironment)
- (BOOL)supportsUpwardPropagation
{
return NO;
}
@end
#pragma mark - Debugging

View File

@@ -88,10 +88,6 @@
// Replace object at the given index with the layoutElement
_childrenArray[index] = layoutElement;
// TODO: Should we propagate up the layoutElement at it could happen that multiple children will propagated up their
// layout options and one child will overwrite values from another child
// [self propagateUpLayoutElement:finalLayoutElement];
}
- (id<ASLayoutElement>)childAtIndex:(NSUInteger)index

View File

@@ -105,19 +105,6 @@
return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min];
}
#pragma mark - Parent
- (void)setParent:(id<ASLayoutElement>)parent
{
// FIXME: Locking should be evaluated here. _parent is not widely used yet, though.
_parent = parent;
if ([parent supportsUpwardPropagation]) {
ASEnvironmentStatePropagateUp(parent, self.environmentState.layoutOptionsState);
}
}
#pragma mark - Child
- (void)setChild:(id<ASLayoutElement>)child
@@ -129,7 +116,6 @@
id<ASLayoutElement> finalLayoutElement = [self layoutElementToAddFromLayoutElement:child];
if (finalLayoutElement) {
_childrenArray[0] = finalLayoutElement;
[self propagateUpLayoutElement:finalLayoutElement];
}
} else {
if (_childrenArray.count) {
@@ -185,28 +171,11 @@
_environmentState = environmentState;
}
// Subclasses can override this method to return NO, because upward propagation is not enabled if a layout
// specification has more than one child. Currently ASStackLayoutSpec and ASAbsoluteLayoutSpec are currently
// the specifications that are known to have more than one.
- (BOOL)supportsUpwardPropagation
{
return ASEnvironmentStatePropagationEnabled();
}
- (BOOL)supportsTraitsCollectionPropagation
{
return ASEnvironmentStateTraitCollectionPropagationEnabled();
}
- (void)propagateUpLayoutElement:(id<ASLayoutElement>)layoutElement
{
if ([layoutElement isKindOfClass:[ASLayoutSpec class]]) {
[(ASLayoutSpec *)layoutElement setParent:self]; // This will trigger upward propogation if needed.
} else if ([self supportsUpwardPropagation]) {
ASEnvironmentStatePropagateUp(self, layoutElement.environmentState.layoutOptionsState); // Probably an ASDisplayNode
}
}
- (ASEnvironmentTraitCollection)environmentTraitCollection
{
return _environmentState.environmentTraitCollection;

View File

@@ -182,15 +182,6 @@
@end
@implementation ASStackLayoutSpec (ASEnvironment)
- (BOOL)supportsUpwardPropagation
{
return NO;
}
@end
@implementation ASStackLayoutSpec (Debugging)
#pragma mark - ASLayoutElementAsciiArtProtocol

View File

@@ -45,8 +45,7 @@
{
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalStack.spacing = INTER_COMMENT_SPACING;
[verticalStack setChildren:_commentNodes];
verticalStack.children = [_commentNodes copy];
return verticalStack;
}

View File

@@ -116,30 +116,26 @@
_photoLocationLabel.style.flexShrink = 1.0;
_userNameLabel.style.flexShrink = 1.0;
ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec];
ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec];
headerSubStack.style.flexShrink = 1.0;
if (_photoLocationLabel.attributedText) {
[headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]];
} else {
[headerSubStack setChildren:@[_userNameLabel]];
}
headerSubStack.children = _photoLocationLabel.attributedText ? @[_userNameLabel, _photoLocationLabel]
: @[_userNameLabel];
// header stack
// constrain avatar image frame size
_userAvatarImageView.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT);
_photoTimeIntervalSincePostLabel.style.spacingBefore = HORIZONTAL_BUFFER; // to remove double spaces around spacer
_photoTimeIntervalSincePostLabel.style.spacingBefore = HORIZONTAL_BUFFER; // to remove double spaces around spacer
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; // FIXME: long locations overflow post time - set max size?
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; // FIXME: long locations overflow post time - set max size?
spacer.style.flexGrow = 1.0;
UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER);
ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView];
ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack
headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack
[headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]];
headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack
headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack
headerStack.children = @[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel];
// header inset stack
UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER);
@@ -148,7 +144,7 @@
// footer stack
ASStackLayoutSpec *footerStack = [ASStackLayoutSpec verticalStackLayoutSpec];
footerStack.spacing = VERTICAL_BUFFER;
[footerStack setChildren:@[_photoLikesLabel, _photoDescriptionLabel, _photoCommentsView]];
footerStack.children = @[_photoLikesLabel, _photoDescriptionLabel, _photoCommentsView];
// footer inset stack
UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER);
@@ -161,8 +157,8 @@
_photoImageView.style.preferredSize = CGSizeMake(cellWidth, cellWidth);
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
[verticalStack setChildren:@[headerWithInset, _photoImageView, footerWithInset]];
verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
verticalStack.children = @[headerWithInset, _photoImageView, footerWithInset];
return verticalStack;
}