// // ASLayoutValidation.mm // 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 "ASLayoutValidation.h" #import "ASLayout.h" #import "ASDisplayNode.h" #import "ASAbsoluteLayoutSpec.h" #import "ASStackLayoutSpec.h" #import #pragma mark - Layout Validation void ASLayoutableValidateLayout(ASLayout *layout) { ASLayoutableValidation *validation = [[ASLayoutableValidation alloc] init]; [validation registerValidator:[[ASLayoutableStaticValidator alloc] init]]; [validation registerValidator:[[ASLayoutableStackValidator alloc] init]]; [validation validateLayout:layout]; } #pragma mark - Helpers static NSString *ASLayoutValidationWrappingAssertMessage(SEL selector, id obj, Class cl) { return [NSString stringWithFormat:@"%@ was set on %@. It is either unecessary or the node needs to be wrapped in a %@", NSStringFromSelector(selector), obj, NSStringFromClass(cl)]; } #pragma mark - ASLayoutableBlockValidator @implementation ASLayoutableBlockValidator #pragma mark Lifecycle - (instancetype)initWithBlock:(ASLayoutableBlockValidatorBlock)block { self = [super init]; if (self) { _block = [block copy]; } return self; } #pragma mark - (void)validateLayout:(ASLayout *)layout { if (self.block) { self.block(layout); } } @end #pragma mark - ASLayoutableStaticValidator @implementation ASLayoutableStaticValidator - (void)validateLayout:(ASLayout *)layout { for (ASLayout *sublayout in layout.sublayouts) { id layoutable = layout.layoutable; id sublayoutLayoutable = sublayout.layoutable; NSString *assertMessage = nil; Class stackContainerClass = [ASAbsoluteLayoutSpec class]; // Check for default layoutPosition if (!CGPointEqualToPoint(sublayoutLayoutable.style.layoutPosition, CGPointZero)) { assertMessage = ASLayoutValidationWrappingAssertMessage(@selector(layoutPosition), sublayoutLayoutable, stackContainerClass); } // Sublayout layoutable should be wrapped in a ASAbsoluteLayoutSpec if (assertMessage == nil || [layoutable isKindOfClass:stackContainerClass]) { continue; } ASDisplayNodeCAssert(NO, assertMessage); } } @end #pragma mark - ASLayoutableStackValidator @implementation ASLayoutableStackValidator #pragma mark - (void)validateLayout:(ASLayout *)layout { id layoutable = layout.layoutable; for (ASLayout *sublayout in layout.sublayouts) { id sublayoutLayoutable = sublayout.layoutable; NSString *assertMessage = nil; Class stackContainerClass = [ASStackLayoutSpec class]; // Check if default values related to ASStackLayoutSpec have changed if (sublayoutLayoutable.style.spacingBefore != 0) { assertMessage = ASLayoutValidationWrappingAssertMessage(@selector(spacingBefore), sublayoutLayoutable, stackContainerClass); } else if (sublayoutLayoutable.style.spacingAfter != 0) { assertMessage = ASLayoutValidationWrappingAssertMessage(@selector(spacingAfter), sublayoutLayoutable, stackContainerClass); } else if (sublayoutLayoutable.style.flexGrow == YES) { assertMessage = ASLayoutValidationWrappingAssertMessage(@selector(flexGrow), sublayoutLayoutable, stackContainerClass); } else if (sublayoutLayoutable.style.flexShrink == YES) { assertMessage = ASLayoutValidationWrappingAssertMessage(@selector(flexShrink), sublayoutLayoutable, stackContainerClass); } else if (!ASDimensionEqualToDimension(sublayoutLayoutable.style.flexBasis, ASDimensionAuto) ) { assertMessage = ASLayoutValidationWrappingAssertMessage(@selector(flexBasis), sublayoutLayoutable, stackContainerClass); } else if (sublayoutLayoutable.style.alignSelf != ASStackLayoutAlignSelfAuto) { assertMessage = ASLayoutValidationWrappingAssertMessage(@selector(alignSelf), sublayoutLayoutable, stackContainerClass); } // Sublayout layoutable should be wrapped in a ASStackLayoutSpec if (assertMessage == nil || [layoutable isKindOfClass:stackContainerClass]) { continue; } ASDisplayNodeCAssert(NO, assertMessage); } } @end #pragma mark ASLayoutablePreferredSizeValidator @implementation ASLayoutablePreferredSizeValidator #pragma mark - (void)validateLayout:(ASLayout *)layout { // TODO: Implement validation that certain node classes need to have a preferredSize set e.g. ASVideoNode } @end #pragma mark - ASLayoutableValidation @interface ASLayoutableValidation () @end @implementation ASLayoutableValidation { NSMutableArray *_validators; } #pragma mark Lifecycle - (instancetype)init { self = [super init]; if (self) { _validators = [NSMutableArray array]; } return self; } #pragma mark Validator Management - (NSArray> *)validators { return [_validators copy]; } - (void)registerValidator:(id)validator { [_validators addObject:validator]; } - (id)registerValidatorWithBlock:(ASLayoutableBlockValidatorBlock)block { ASLayoutableBlockValidator *blockValidator = [[ASLayoutableBlockValidator alloc] initWithBlock:block]; [_validators addObject:blockValidator]; return blockValidator; } - (void)unregisterValidator:(id)validator { [_validators removeObject:validator]; } #pragma mark Validation Process - (void)validateLayout:(ASLayout *)layout { // Queue used to keep track of sublayouts while traversing this layout in a BFS fashion. std::queue queue; queue.push(layout); while (!queue.empty()) { layout = queue.front(); queue.pop(); // Validate layout with all registered validators for (id validator in self.validators) { [validator validateLayout:layout]; } // Push sublayouts to queue for validation for (id sublayout in [layout sublayouts]) { queue.push(sublayout); } } } @end