/** * 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 "UIView+CSSLayout.h" #import static CSSSize _measure( void *context, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode) { UIView *view = (__bridge UIView*) context; CGSize sizeThatFits = [view sizeThatFits:(CGSize) { .width = widthMode == CSSMeasureModeUndefined ? CGFLOAT_MAX : width, .height = heightMode == CSSMeasureModeUndefined ? CGFLOAT_MAX : height, }]; return (CSSSize) { .width = widthMode == CSSMeasureModeExactly ? width : sizeThatFits.width, .height = heightMode == CSSMeasureModeExactly ? height : sizeThatFits.height, }; } static void _attachNodesRecursive(UIView *view); static void _updateFrameRecursive(UIView *view); @interface CSSNodeBridge : NSObject @property (nonatomic, assign) CSSNodeRef cnode; @end @implementation CSSNodeBridge - (instancetype)init { if ([super init]) { _cnode = CSSNodeNew(); } return self; } - (void)dealloc { [super dealloc]; CSSNodeFree(_cnode); } @end @implementation UIView (CSSLayout) - (CSSNodeRef)cssNode { CSSNodeBridge *node = objc_getAssociatedObject(self, @selector(cssNode)); if (!node) { node = [CSSNodeBridge new]; CSSNodeSetContext(node.cnode, (__bridge void *) self); objc_setAssociatedObject(self, @selector(cssNode), node, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return node.cnode; } - (void)css_setUsesFlexbox:(BOOL)enabled { objc_setAssociatedObject( self, @selector(css_usesFlexbox), @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)css_usesFlexbox { if (objc_getAssociatedObject(self, @selector(css_usesFlexbox))) { return YES; } else { return NO; } } - (void)css_setDirection:(CSSDirection)direction { CSSNodeStyleSetDirection([self cssNode], direction); } - (void)css_setFlexDirection:(CSSFlexDirection)flexDirection { CSSNodeStyleSetFlexDirection([self cssNode], flexDirection); } - (void)css_setJustifyContent:(CSSJustify)justifyContent { CSSNodeStyleSetJustifyContent([self cssNode], justifyContent); } - (void)css_setAlignContent:(CSSAlign)alignContent { CSSNodeStyleSetAlignContent([self cssNode], alignContent); } - (void)css_setAlignItems:(CSSAlign)alignItems { CSSNodeStyleSetAlignItems([self cssNode], alignItems); } - (void)css_setAlignSelf:(CSSAlign)alignSelf { CSSNodeStyleSetAlignSelf([self cssNode], alignSelf); } - (void)css_setPositionType:(CSSPositionType)positionType { CSSNodeStyleSetPositionType([self cssNode], positionType); } - (void)css_setFlexWrap:(CSSWrapType)flexWrap { CSSNodeStyleSetFlexWrap([self cssNode], flexWrap); } - (void)css_setOverflow:(CSSOverflow)overflow { CSSNodeStyleSetOverflow([self cssNode], overflow); } - (void)css_setFlex:(CGFloat)flex { CSSNodeStyleSetFlex([self cssNode], flex); } - (void)css_setFlexGrow:(CGFloat)flexGrow { CSSNodeStyleSetFlexGrow([self cssNode], flexGrow); } - (void)css_setFlexShrink:(CGFloat)flexShrink { CSSNodeStyleSetFlexShrink([self cssNode], flexShrink); } - (void)css_setFlexBasis:(CGFloat)flexBasis { CSSNodeStyleSetFlexBasis([self cssNode], flexBasis); } - (void)css_setPosition:(CGFloat)position forEdge:(CSSEdge)edge { CSSNodeStyleSetPosition([self cssNode], edge, position); } - (void)css_setMargin:(CGFloat)margin forEdge:(CSSEdge)edge { CSSNodeStyleSetMargin([self cssNode], edge, margin); } - (void)css_setPadding:(CGFloat)padding forEdge:(CSSEdge)edge { CSSNodeStyleSetPadding([self cssNode], edge, padding); } - (void)css_setBorder:(CGFloat)border forEdge:(CSSEdge)edge { CSSNodeStyleSetBorder([self cssNode], edge, border); } - (void)css_setWidth:(CGFloat)width { CSSNodeStyleSetWidth([self cssNode], width); } - (void)css_setHeight:(CGFloat)height { CSSNodeStyleSetHeight([self cssNode], height); } - (void)css_setMinWidth:(CGFloat)minWidth { CSSNodeStyleSetMinWidth([self cssNode], minWidth); } - (void)css_setMinHeight:(CGFloat)minHeight { CSSNodeStyleSetMinHeight([self cssNode], minHeight); } - (void)css_setMaxWidth:(CGFloat)maxWidth { CSSNodeStyleSetMaxWidth([self cssNode], maxWidth); } - (void)css_setMaxHeight:(CGFloat)maxHeight { CSSNodeStyleSetMaxHeight([self cssNode], maxHeight); } - (CSSDirection)css_resolvedDirection { return CSSNodeLayoutGetDirection([self cssNode]); } - (void)css_applyLayout { NSAssert([NSThread isMainThread], @"Method called using a thread other than main!"); NSAssert([self css_usesFlexbox], @"css_applyLayout must be called on a node using css styling!"); _attachNodesRecursive(self); CSSNodeRef node = [self cssNode]; CSSNodeCalculateLayout( [self cssNode], CSSNodeStyleGetWidth(node), CSSNodeStyleGetHeight(node), CSSNodeStyleGetDirection(node)); _updateFrameRecursive(self); } @end static void _attachNodesRecursive(UIView *view) { CSSNodeRef node = [view cssNode]; const BOOL usesFlexbox = [view css_usesFlexbox]; const BOOL isLeaf = !usesFlexbox || view.subviews.count == 0; // Only leaf nodes should have a measure function if (isLeaf) { CSSNodeSetMeasureFunc(node, _measure); // Clear any children while (CSSNodeChildCount(node) > 0) { CSSNodeRemoveChild(node, CSSNodeGetChild(node, 0)); } } else { CSSNodeSetMeasureFunc(node, NULL); // Add any children which were added since the last call to css_applyLayout for (NSUInteger i = 0; i < view.subviews.count; i++) { CSSNodeRef childNode = [view.subviews[i] cssNode]; if (CSSNodeGetChild(node, i) != childNode) { CSSNodeInsertChild(node, childNode, i); } _attachNodesRecursive(view.subviews[i]); } // Remove any children which were removed since the last call to css_applyLayout while (view.subviews.count < CSSNodeChildCount(node)) { CSSNodeRemoveChild(node, CSSNodeGetChild(node, CSSNodeChildCount(node) - 1)); } } } static void _updateFrameRecursive(UIView *view) { CSSNodeRef node = [view cssNode]; const BOOL usesFlexbox = [view css_usesFlexbox]; const BOOL isLeaf = !usesFlexbox || view.subviews.count == 0; view.frame = (CGRect) { .origin = { .x = CSSNodeLayoutGetLeft(node), .y = CSSNodeLayoutGetTop(node), }, .size = { .width = CSSNodeLayoutGetWidth(node), .height = CSSNodeLayoutGetHeight(node), }, }; if (!isLeaf) { for (NSUInteger i = 0; i < view.subviews.count; i++) { _updateFrameRecursive(view.subviews[i]); } } }