diff --git a/YogaKit/UIView+YogaKit.h b/YogaKit/UIView+YogaKit.h index d6f0b8bf..d5b17514 100644 --- a/YogaKit/UIView+YogaKit.h +++ b/YogaKit/UIView+YogaKit.h @@ -8,68 +8,10 @@ */ #import -#import "YKEnums.h" +#import @interface UIView (YogaKit) -/** - The property that decides if we should include this view when calculating layout. Defaults to YES. - */ -@property (nonatomic, setter=yk_setIncludeInLayout:) BOOL yk_includeInLayout NS_SWIFT_NAME(includeInLayout); - -/** - The property that decides during layout/sizing whether or not yk_* properties should be applied. Defaults to NO. - */ -@property (nonatomic, setter=yk_setUsesYoga:) BOOL yk_usesYoga NS_SWIFT_NAME(usesYoga); - -@property (nonatomic, setter=yk_setDirection:) YKDirection yk_direction NS_SWIFT_NAME(layoutDirection); -@property (nonatomic, setter=yk_setFlexDirection:) YKFlexDirection yk_flexDirection NS_SWIFT_NAME(layoutFlexDirection); -@property (nonatomic, setter=yk_setJustifyContent:) YKJustify yk_justifyContent NS_SWIFT_NAME(layoutJustifyContent); -@property (nonatomic, setter=yk_setAlignContent:) YKAlign yk_alignContent NS_SWIFT_NAME(layoutAlignContent); -@property (nonatomic, setter=yk_setAlignItems:) YKAlign yk_alignItems NS_SWIFT_NAME(layoutAlignItems); -@property (nonatomic, setter=yk_setAlignSelf:) YKAlign yk_alignSelf NS_SWIFT_NAME(layoutAlignSelf); -@property (nonatomic, setter=yk_setPositionType:) YKPositionType yk_positionType NS_SWIFT_NAME(layoutPositionType); -@property (nonatomic, setter=yk_setFlexWrap:) YKWrap yk_flexWrap NS_SWIFT_NAME(layoutFlexWrap); - -@property (nonatomic, setter=yk_setFlexGrow:) CGFloat yk_flexGrow NS_SWIFT_NAME(layoutFlexGrow); -@property (nonatomic, setter=yk_setFlexShrink:) CGFloat yk_flexShrink NS_SWIFT_NAME(layoutFlexShrink); -@property (nonatomic, setter=yk_setFlexBasis:) CGFloat yk_flexBasis NS_SWIFT_NAME(layoutFlexBasis); - -- (void)yk_positionForEdge:(YKEdge)edge; -- (void)yk_setPosition:(CGFloat)position forEdge:(YKEdge)edge; -- (void)yk_marginForEdge:(YKEdge)edge; -- (void)yk_setMargin:(CGFloat)margin forEdge:(YKEdge)edge; -- (void)yk_paddingForEdge:(YKEdge)edge; -- (void)yk_setPadding:(CGFloat)padding forEdge:(YKEdge)edge; - -@property (nonatomic, setter=yk_setWidth:) CGFloat yk_width NS_SWIFT_NAME(layoutWidth); -@property (nonatomic, setter=yk_setHeight:) CGFloat yk_height NS_SWIFT_NAME(layoutHeight); -@property (nonatomic, setter=yk_setMinWidth:) CGFloat yk_minWidth NS_SWIFT_NAME(layoutMinWidth); -@property (nonatomic, setter=yk_setMinHeight:) CGFloat yk_minHeight NS_SWIFT_NAME(layoutMinHeight); -@property (nonatomic, setter=yk_setMaxWidth:) CGFloat yk_maxWidth NS_SWIFT_NAME(layoutMaxWidth); -@property (nonatomic, setter=yk_setMaxHeight:) CGFloat yk_maxHeight NS_SWIFT_NAME(layoutMaxHeight); - -// Yoga specific properties, not compatible with flexbox specification -@property (nonatomic, setter=yk_setAspectRatio:) CGFloat yk_aspectRatio NS_SWIFT_NAME(layoutAspectRatio); - -/** - Get the resolved direction of this node. This won't be YGDirectionInherit - */ -@property (nonatomic, readonly) CGFloat yk_resolvedDirection NS_SWIFT_NAME(layoutResolvedDirection); - -/** - Perform a layout calculation and update the frames of the views in the hierarchy with the results - */ -- (void)yk_applyLayout NS_SWIFT_NAME(applyLayout()); - -/** - Returns the size of the view if no constraints were given. This could equivalent to calling [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; - */ -@property (nonatomic, readonly) CGSize yk_intrinsicSize NS_SWIFT_NAME(layoutIntrinsicSize); - -/** - Returns the number of children that are using Flexbox. - */ -@property (nonatomic, readonly) NSUInteger yk_numberOfChildren NS_SWIFT_NAME(layoutNumberOfChildren); +@property (nonatomic, readonly) YKLayout* layout; @end diff --git a/YogaKit/UIView+YogaKit.m b/YogaKit/UIView+YogaKit.m index e56e7689..a5221310 100644 --- a/YogaKit/UIView+YogaKit.m +++ b/YogaKit/UIView+YogaKit.m @@ -7,478 +7,22 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import -#import +#import +#import #import -@interface YGNodeBridge : NSObject -@property (nonatomic, assign, readonly) YGNodeRef cnode; -@end - -@implementation YGNodeBridge - -+ (void)initialize -{ - YGSetExperimentalFeatureEnabled(YGExperimentalFeatureWebFlexBasis, true); -} - -- (instancetype)init -{ - if ([super init]) { - _cnode = YGNodeNew(); - } - - return self; -} - -- (void)dealloc -{ - YGNodeFree(_cnode); -} -@end - @implementation UIView (YogaKit) -- (BOOL)yk_usesYoga +- (YKLayout *)layout { - NSNumber *usesYoga = objc_getAssociatedObject(self, @selector(yk_usesYoga)); - return [usesYoga boolValue]; -} - -- (BOOL)yk_includeInLayout -{ - NSNumber *includeInLayout = objc_getAssociatedObject(self, @selector(yk_includeInLayout)); - return (includeInLayout != nil) ? [includeInLayout boolValue] : YES; -} - -- (NSUInteger)yk_numberOfChildren -{ - return YGNodeGetChildCount([self ygNode]); -} - -#pragma mark - Setters - -- (void)yk_setIncludeInLayout:(BOOL)includeInLayout -{ - objc_setAssociatedObject( - self, - @selector(yk_includeInLayout), - @(includeInLayout), - OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (void)yk_setUsesYoga:(BOOL)enabled -{ - objc_setAssociatedObject( - self, - @selector(yk_usesYoga), - @(enabled), - OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (YKDirection)yk_direction -{ - return (YKDirection)YGNodeStyleGetDirection([self ygNode]); -} - -- (void)yk_setDirection:(YKDirection)direction -{ - YGNodeStyleSetDirection([self ygNode], (YGDirection)direction); -} - -- (YKFlexDirection)yk_flexDirection -{ - return (YKFlexDirection)YGNodeStyleGetFlexDirection([self ygNode]); -} - -- (void)yk_setFlexDirection:(YKFlexDirection)flexDirection -{ - YGNodeStyleSetFlexDirection([self ygNode], (YGFlexDirection)flexDirection); -} - -- (YKJustify)yk_justifyContent -{ - return (YKJustify)YGNodeStyleGetJustifyContent([self ygNode]); -} - -- (void)yk_setJustifyContent:(YKJustify)justifyContent -{ - YGNodeStyleSetJustifyContent([self ygNode], (YGJustify)justifyContent); -} - -- (YKAlign)yk_alignContent -{ - return (YKAlign)YGNodeStyleGetAlignContent([self ygNode]); -} - -- (void)yk_setAlignContent:(YKAlign)alignContent -{ - YGNodeStyleSetAlignContent([self ygNode], (YGAlign)alignContent); -} - -- (YKAlign)yk_alignItems -{ - return (YKAlign)YGNodeStyleGetAlignItems([self ygNode]); -} - -- (void)yk_setAlignItems:(YKAlign)alignItems -{ - YGNodeStyleSetAlignItems([self ygNode], (YGAlign)alignItems); -} - -- (YKAlign)yk_alignSelf -{ - return (YKAlign)YGNodeStyleGetAlignSelf([self ygNode]); -} - -- (void)yk_setAlignSelf:(YKAlign)alignSelf -{ - YGNodeStyleSetAlignSelf([self ygNode], (YGAlign)alignSelf); -} - -- (YKPositionType)yk_positionType -{ - return (YKPositionType)YGNodeStyleGetPositionType([self ygNode]); -} - -- (void)yk_setPositionType:(YKPositionType)positionType -{ - YGNodeStyleSetPositionType([self ygNode], (YGPositionType)positionType); -} - -- (YKWrap)yk_flexWrap -{ - return (YKWrap)YGNodeStyleGetFlexWrap([self ygNode]); -} - -- (void)yk_setFlexWrap:(YKWrap)flexWrap -{ - YGNodeStyleSetFlexWrap([self ygNode], (YGWrap)flexWrap); -} - -- (CGFloat)yk_flexGrow -{ - return YGNodeStyleGetFlexGrow([self ygNode]); -} - -- (void)yk_setFlexGrow:(CGFloat)flexGrow -{ - YGNodeStyleSetFlexGrow([self ygNode], flexGrow); -} - -- (CGFloat)yk_flexShrink -{ - return YGNodeStyleGetFlexShrink([self ygNode]); -} - -- (void)yk_setFlexShrink:(CGFloat)flexShrink -{ - YGNodeStyleSetFlexShrink([self ygNode], flexShrink); -} - -- (CGFloat)yk_flexBasis -{ - return YGNodeStyleGetFlexBasis([self ygNode]); -} - -- (void)yk_setFlexBasis:(CGFloat)flexBasis -{ - YGNodeStyleSetFlexBasis([self ygNode], flexBasis); -} - -- (CGFloat)yk_positionForEdge:(YKEdge)edge -{ - return YGNodeStyleGetPosition([self ygNode], (YGEdge)edge); -} - -- (void)yk_setPosition:(CGFloat)position forEdge:(YKEdge)edge -{ - YGNodeStyleSetPosition([self ygNode], (YGEdge)edge, position); -} - -- (CGFloat)yk_marginForEdge:(YKEdge)edge -{ - return YGNodeStyleGetMargin([self ygNode], (YGEdge)edge); -} - -- (void)yk_setMargin:(CGFloat)margin forEdge:(YKEdge)edge -{ - YGNodeStyleSetMargin([self ygNode], (YGEdge)edge, margin); -} - -- (CGFloat)yk_paddingForEdge:(YKEdge)edge -{ - return YGNodeStyleGetPadding([self ygNode], (YGEdge)edge); -} - -- (void)yk_setPadding:(CGFloat)padding forEdge:(YKEdge)edge -{ - YGNodeStyleSetPadding([self ygNode], (YGEdge)edge, padding); -} - -- (CGFloat)yk_width -{ - return YGNodeStyleGetWidth([self ygNode]); -} - -- (void)yk_setWidth:(CGFloat)width -{ - YGNodeStyleSetWidth([self ygNode], width); -} - -- (CGFloat)yk_height -{ - return YGNodeStyleGetHeight([self ygNode]); -} - -- (void)yk_setHeight:(CGFloat)height -{ - YGNodeStyleSetHeight([self ygNode], height); -} - -- (CGFloat)yk_minWidth -{ - return YGNodeStyleGetMinWidth([self ygNode]); -} - -- (void)yk_setMinWidth:(CGFloat)minWidth -{ - YGNodeStyleSetMinWidth([self ygNode], minWidth); -} - -- (CGFloat)yk_minHeight -{ - return YGNodeStyleGetMinHeight([self ygNode]); -} - -- (void)yk_setMinHeight:(CGFloat)minHeight -{ - YGNodeStyleSetMinHeight([self ygNode], minHeight); -} - -- (CGFloat)yk_maxWidth -{ - return YGNodeStyleGetMaxWidth([self ygNode]); -} - -- (void)yk_setMaxWidth:(CGFloat)maxWidth -{ - YGNodeStyleSetMaxWidth([self ygNode], maxWidth); -} - -- (CGFloat)yk_maxHeight -{ - return YGNodeStyleGetMaxHeight([self ygNode]); -} - -- (void)yk_setMaxHeight:(CGFloat)maxHeight -{ - YGNodeStyleSetMaxHeight([self ygNode], maxHeight); -} - -- (CGFloat)yk_aspectRatio -{ - return YGNodeStyleGetAspectRatio([self ygNode]); -} - -- (void)yk_setAspectRatio:(CGFloat)aspectRatio -{ - YGNodeStyleSetAspectRatio([self ygNode], aspectRatio); -} - -#pragma mark - Layout and Sizing - -- (YKDirection)yk_resolvedDirection -{ - return (YKDirection)YGNodeLayoutGetDirection([self ygNode]); -} - -- (void)yk_applyLayout -{ - [self calculateLayoutWithSize:self.bounds.size]; - YKApplyLayoutToViewHierarchy(self); -} - -- (CGSize)yk_intrinsicSize -{ - const CGSize constrainedSize = { - .width = YGUndefined, - .height = YGUndefined, - }; - return [self calculateLayoutWithSize:constrainedSize]; -} - -#pragma mark - Private - -- (YGNodeRef)ygNode -{ - YGNodeBridge *node = objc_getAssociatedObject(self, @selector(ygNode)); - if (!node) { - node = [YGNodeBridge new]; - YGNodeSetContext(node.cnode, (__bridge void *) self); - objc_setAssociatedObject(self, @selector(ygNode), node, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + YKLayout *layout = objc_getAssociatedObject(self, @selector(layout)); + if (!layout) { + layout = [[YKLayout alloc] initWithView:self]; + objc_setAssociatedObject(self, @selector(layout), layout, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - - return node.cnode; + + return layout; } -- (CGSize)calculateLayoutWithSize:(CGSize)size -{ - NSAssert([NSThread isMainThread], @"YG Layout calculation must be done on main."); - NSAssert([self yk_usesYoga], @"YG Layout is not enabled for this view."); - - YKAttachNodesFromViewHierachy(self); - - const YGNodeRef node = [self ygNode]; - YGNodeCalculateLayout( - node, - size.width, - size.height, - YGNodeStyleGetDirection(node)); - - return (CGSize) { - .width = YGNodeLayoutGetWidth(node), - .height = YGNodeLayoutGetHeight(node), - }; -} - -static YGSize YKMeasureView( - YGNodeRef node, - float width, - YGMeasureMode widthMode, - float height, - YGMeasureMode heightMode) -{ - const CGFloat constrainedWidth = (widthMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : width; - const CGFloat constrainedHeight = (heightMode == YGMeasureModeUndefined) ? CGFLOAT_MAX: height; - - UIView *view = (__bridge UIView*) YGNodeGetContext(node); - const CGSize sizeThatFits = [view sizeThatFits:(CGSize) { - .width = constrainedWidth, - .height = constrainedHeight, - }]; - - return (YGSize) { - .width = YKSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), - .height = YKSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), - }; -} - -static CGFloat YKSanitizeMeasurement( - CGFloat constrainedSize, - CGFloat measuredSize, - YGMeasureMode measureMode) -{ - CGFloat result; - if (measureMode == YGMeasureModeExactly) { - result = constrainedSize; - } else if (measureMode == YGMeasureModeAtMost) { - result = MIN(constrainedSize, measuredSize); - } else { - result = measuredSize; - } - - return result; -} - -static void YKAttachNodesFromViewHierachy(UIView *view) { - YGNodeRef node = [view ygNode]; - - // Only leaf nodes should have a measure function - if (![view yk_usesYoga] || view.subviews.count == 0) { - YGNodeSetMeasureFunc(node, YKMeasureView); - YKRemoveAllChildren(node); - } else { - YGNodeSetMeasureFunc(node, NULL); - - // Create a list of all the subviews that we are going to use for layout. - NSMutableArray *subviewsToInclude = [[NSMutableArray alloc] initWithCapacity:view.subviews.count]; - for (UIView *subview in view.subviews) { - if ([subview yk_includeInLayout]) { - [subviewsToInclude addObject:subview]; - } - } - - BOOL shouldReconstructChildList = NO; - if (YGNodeGetChildCount(node) != subviewsToInclude.count) { - shouldReconstructChildList = YES; - } else { - for (int i = 0; i < subviewsToInclude.count; i++) { - if (YGNodeGetChild(node, i) != [subviewsToInclude[i] ygNode]) { - shouldReconstructChildList = YES; - break; - } - } - } - - if (shouldReconstructChildList) { - YKRemoveAllChildren(node); - - for (int i = 0 ; i < subviewsToInclude.count; i++) { - UIView *const subview = subviewsToInclude[i]; - YGNodeInsertChild(node, [subview ygNode], i); - YKAttachNodesFromViewHierachy(subview); - } - } - } -} - -static void YKRemoveAllChildren(const YGNodeRef node) -{ - if (node == NULL) { - return; - } - - while (YGNodeGetChildCount(node) > 0) { - YGNodeRemoveChild(node, YGNodeGetChild(node, YGNodeGetChildCount(node) - 1)); - } -} - -static CGFloat YKRoundPixelValue(CGFloat value) -{ - static CGFloat scale; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^(){ - scale = [UIScreen mainScreen].scale; - }); - - return round(value * scale) / scale; -} - -static void YKApplyLayoutToViewHierarchy(UIView *view) { - NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread."); - if (![view yk_includeInLayout]) { - return; - } - - YGNodeRef node = [view ygNode]; - const CGPoint topLeft = { - YGNodeLayoutGetLeft(node), - YGNodeLayoutGetTop(node), - }; - - const CGPoint bottomRight = { - topLeft.x + YGNodeLayoutGetWidth(node), - topLeft.y + YGNodeLayoutGetHeight(node), - }; - - view.frame = (CGRect) { - .origin = { - .x = YKRoundPixelValue(topLeft.x), - .y = YKRoundPixelValue(topLeft.y), - }, - .size = { - .width = YKRoundPixelValue(bottomRight.x) - YKRoundPixelValue(topLeft.x), - .height = YKRoundPixelValue(bottomRight.y) - YKRoundPixelValue(topLeft.y), - }, - }; - - const BOOL isLeaf = ![view yk_usesYoga] || view.subviews.count == 0; - if (!isLeaf) { - for (NSUInteger i = 0; i < view.subviews.count; i++) { - YKApplyLayoutToViewHierarchy(view.subviews[i]); - } - } -} @end diff --git a/YogaKit/YKEnums.h b/YogaKit/YKEnums.h index 3e684fdc..fdfb8721 100644 --- a/YogaKit/YKEnums.h +++ b/YogaKit/YKEnums.h @@ -12,24 +12,12 @@ typedef NS_ENUM(NSInteger, YKFlexDirection) { YKFlexDirectionColumnReverse, YKFlexDirectionRow, YKFlexDirectionRowReverse, -}; - -typedef NS_ENUM(NSInteger, YKEdge) { - YKEdgeLeft, - YKEdgeTop, - YKEdgeRight, - YKEdgeBottom, - YKEdgeStart, - YKEdgeEnd, - YKEdgeHorizontal, - YKEdgeVertical, - YKEdgeAll, -}; +} NS_SWIFT_NAME(FlexDirection); typedef NS_ENUM(NSInteger, YKPositionType) { YKPositionTypeRelative, YKPositionTypeAbsolute, -}; +} NS_SWIFT_NAME(PositionType); typedef NS_ENUM(NSInteger, YKJustify) { YKJustifyFlexStart, @@ -37,18 +25,18 @@ typedef NS_ENUM(NSInteger, YKJustify) { YKJustifyFlexEnd, YKJustifySpaceBetween, YKJustifySpaceAround, -}; +} NS_SWIFT_NAME(Justify); typedef NS_ENUM(NSInteger, YKDirection) { YKDirectionInherit, YKDirectionLeftToRight, YKDirectionRightToLeft, -}; +} NS_SWIFT_NAME(Direction); typedef NS_ENUM(NSInteger, YKWrap) { YKWrapNoWrap, YKWrapWrap, -}; +} NS_SWIFT_NAME(Wrap); typedef NS_ENUM(NSInteger, YKAlign) { YKAlignAuto, @@ -56,5 +44,5 @@ typedef NS_ENUM(NSInteger, YKAlign) { YKAlignCenter, YKAlignFlexEnd, YKAlignStretch, -}; +} NS_SWIFT_NAME(Align); diff --git a/YogaKit/YKLayout+Private.h b/YogaKit/YKLayout+Private.h new file mode 100644 index 00000000..c51d3ec3 --- /dev/null +++ b/YogaKit/YKLayout+Private.h @@ -0,0 +1,16 @@ +/** + * 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 + +@interface YKLayout () + +- (instancetype)initWithView:(UIView*)view; + +@end diff --git a/YogaKit/YKLayout.h b/YogaKit/YKLayout.h new file mode 100644 index 00000000..15ebadc7 --- /dev/null +++ b/YogaKit/YKLayout.h @@ -0,0 +1,97 @@ +/** + * 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 "YKEnums.h" + +@interface YKLayout : NSObject + +/** + The property that decides if we should include this view when calculating layout. Defaults to YES. + */ +@property (nonatomic, setter=setIncluded:) BOOL isIncluded; + +/** + The property that decides during layout/sizing whether or not yk_* properties should be applied. Defaults to NO. + */ +@property (nonatomic, setter=setEnabled:) BOOL isEnabled; + +@property (nonatomic) YKDirection direction; +@property (nonatomic) YKFlexDirection flexDirection; +@property (nonatomic) YKJustify justifyContent; +@property (nonatomic) YKAlign alignContent; +@property (nonatomic) YKAlign alignItems; +@property (nonatomic) YKAlign alignSelf; +@property (nonatomic) YKPositionType positionType; +@property (nonatomic) YKWrap flexWrap; + +@property (nonatomic) CGFloat flexGrow; +@property (nonatomic) CGFloat flexShrink; +@property (nonatomic) CGFloat flexBasis; + +@property (nonatomic) CGFloat positionLeft; +@property (nonatomic) CGFloat positionTop; +@property (nonatomic) CGFloat positionRight; +@property (nonatomic) CGFloat positionBottom; +@property (nonatomic) CGFloat positionStart; +@property (nonatomic) CGFloat positionEnd; +@property (nonatomic) CGFloat positionHorizontal; +@property (nonatomic) CGFloat positionVertical; +@property (nonatomic) CGFloat positionAll; + +@property (nonatomic) CGFloat marginLeft; +@property (nonatomic) CGFloat marginTop; +@property (nonatomic) CGFloat marginRight; +@property (nonatomic) CGFloat marginBottom; +@property (nonatomic) CGFloat marginStart; +@property (nonatomic) CGFloat marginEnd; +@property (nonatomic) CGFloat marginHorizontal; +@property (nonatomic) CGFloat marginVertical; +@property (nonatomic) CGFloat marginAll; + +@property (nonatomic) CGFloat paddingLeft; +@property (nonatomic) CGFloat paddingTop; +@property (nonatomic) CGFloat paddingRight; +@property (nonatomic) CGFloat paddingBottom; +@property (nonatomic) CGFloat paddingStart; +@property (nonatomic) CGFloat paddingEnd; +@property (nonatomic) CGFloat paddingHorizontal; +@property (nonatomic) CGFloat paddingVertical; +@property (nonatomic) CGFloat paddingAll; + +@property (nonatomic) CGFloat width; +@property (nonatomic) CGFloat height; +@property (nonatomic) CGFloat minWidth; +@property (nonatomic) CGFloat minHeight; +@property (nonatomic) CGFloat maxWidth; +@property (nonatomic) CGFloat maxHeight; + +// Yoga specific properties, not compatible with flexbox specification +@property (nonatomic) CGFloat aspectRatio; + +/** + Get the resolved direction of this node. This won't be YGDirectionInherit + */ +@property (nonatomic, readonly) YKDirection resolvedDirection; + +/** + Perform a layout calculation and update the frames of the views in the hierarchy with the results + */ +- (void)apply; + +/** + Returns the size of the view if no constraints were given. This could equivalent to calling [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + */ +@property (nonatomic, readonly) CGSize intrinsicSize; + +/** + Returns the number of children that are using Flexbox. + */ +@property (nonatomic, readonly) NSUInteger numberOfChildren; + +@end diff --git a/YogaKit/YKLayout.m b/YogaKit/YKLayout.m new file mode 100644 index 00000000..bd4b32ff --- /dev/null +++ b/YogaKit/YKLayout.m @@ -0,0 +1,294 @@ +/** + * 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 +#import +#import +#import + +#define YK_STYLE_PROPERTY_IMPL(objc_type, c_type, lowercased_name, capitalized_name) \ +- (objc_type)lowercased_name \ +{ \ +return (objc_type)YGNodeStyleGet##capitalized_name(_node); \ +} \ +\ +- (void)set##capitalized_name:(objc_type)lowercased_name \ +{ \ +YGNodeStyleSet##capitalized_name(_node, (c_type)lowercased_name); \ +} + +#define _YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, edge) \ +- (CGFloat)lowercased_name##edge \ +{ \ +return YGNodeStyleGet##capitalized_name(_node, YGEdge##edge); \ +} \ +\ +- (void)set##capitalized_name##edge:(CGFloat)lowercased_name##edge \ +{ \ +YGNodeStyleSet##capitalized_name(_node, YGEdge##edge, lowercased_name##edge); \ +} + +#define YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, Left) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, Top) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, Right) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, Bottom) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, Start) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, End) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, Horizontal) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, Vertical) \ +_YK_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, All) + +@interface YKLayout () +@property (nonatomic, weak, readonly) UIView* view; +@property (nonatomic, assign, readonly) YGNodeRef node; +@end + +@implementation YKLayout + +@synthesize isEnabled=_isEnabled; +@synthesize isIncluded=_isIncluded; + ++ (void)initialize +{ + YGSetExperimentalFeatureEnabled(YGExperimentalFeatureWebFlexBasis, true); +} + +- (instancetype)initWithView:(UIView*)view +{ + if ([super init]) { + _view = view; + _node = YGNodeNew(); + YGNodeSetContext(_node, (__bridge void *) view); + _isEnabled = NO; + _isIncluded = YES; + } + + return self; +} + +- (void)dealloc +{ + YGNodeFree(_node); +} + +- (NSUInteger)numberOfChildren +{ + return YGNodeGetChildCount(_node); +} + +YK_STYLE_PROPERTY_IMPL(YKDirection, YGDirection, direction, Direction) +YK_STYLE_PROPERTY_IMPL(YKFlexDirection, YGFlexDirection, flexDirection, FlexDirection) +YK_STYLE_PROPERTY_IMPL(YKJustify, YGJustify, justifyContent, JustifyContent) +YK_STYLE_PROPERTY_IMPL(YKAlign, YGAlign, alignContent, AlignContent) +YK_STYLE_PROPERTY_IMPL(YKAlign, YGAlign, alignItems, AlignItems) +YK_STYLE_PROPERTY_IMPL(YKAlign, YGAlign, alignSelf, AlignSelf) +YK_STYLE_PROPERTY_IMPL(YKPositionType, YGPositionType, positionType, PositionType) +YK_STYLE_PROPERTY_IMPL(YKWrap, YGWrap, flexWrap, FlexWrap) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, flexGrow, FlexGrow) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, flexShrink, FlexShrink) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, flexBasis, FlexBasis) +YK_STYLE_EDGE_PROPERTY_IMPL(position, Position) +YK_STYLE_EDGE_PROPERTY_IMPL(margin, Margin) +YK_STYLE_EDGE_PROPERTY_IMPL(padding, Padding) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, width, Width) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, height, Height) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, minWidth, MinWidth) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, minHeight, MinHeight) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, maxWidth, MaxWidth) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, maxHeight, MaxHeight) +YK_STYLE_PROPERTY_IMPL(CGFloat, CGFloat, aspectRatio, AspectRatio) + +#pragma mark - Layout and Sizing + +- (YKDirection)resolvedDirection +{ + return (YKDirection)YGNodeLayoutGetDirection(_node); +} + +- (void)apply +{ + [self calculateLayoutWithSize:self.view.bounds.size]; + YKApplyLayoutToViewHierarchy(self); +} + +- (CGSize)intrinsicSize +{ + const CGSize constrainedSize = { + .width = YGUndefined, + .height = YGUndefined, + }; + return [self calculateLayoutWithSize:constrainedSize]; +} + +#pragma mark - Private + +- (CGSize)calculateLayoutWithSize:(CGSize)size +{ + NSAssert([NSThread isMainThread], @"YG Layout calculation must be done on main."); + NSAssert([self isEnabled], @"YG Layout is not enabled for this view."); + + YKAttachNodesFromViewHierachy(self); + + const YGNodeRef node = _node; + YGNodeCalculateLayout( + node, + size.width, + size.height, + YGNodeStyleGetDirection(node)); + + return (CGSize) { + .width = YGNodeLayoutGetWidth(node), + .height = YGNodeLayoutGetHeight(node), + }; +} + +static YGSize YKMeasureView( + YGNodeRef node, + float width, + YGMeasureMode widthMode, + float height, + YGMeasureMode heightMode) +{ + const CGFloat constrainedWidth = (widthMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : width; + const CGFloat constrainedHeight = (heightMode == YGMeasureModeUndefined) ? CGFLOAT_MAX: height; + + UIView *view = (__bridge UIView*) YGNodeGetContext(node); + const CGSize sizeThatFits = [view sizeThatFits:(CGSize) { + .width = constrainedWidth, + .height = constrainedHeight, + }]; + + return (YGSize) { + .width = YKSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), + .height = YKSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), + }; +} + +static CGFloat YKSanitizeMeasurement( + CGFloat constrainedSize, + CGFloat measuredSize, + YGMeasureMode measureMode) +{ + CGFloat result; + if (measureMode == YGMeasureModeExactly) { + result = constrainedSize; + } else if (measureMode == YGMeasureModeAtMost) { + result = MIN(constrainedSize, measuredSize); + } else { + result = measuredSize; + } + + return result; +} + +static void YKAttachNodesFromViewHierachy(YKLayout *layout) { + YGNodeRef node = layout.node; + UIView *view = layout.view; + + // Only leaf nodes should have a measure function + if (![layout isEnabled] || view.subviews.count == 0) { + YGNodeSetMeasureFunc(node, YKMeasureView); + YKRemoveAllChildren(node); + } else { + YGNodeSetMeasureFunc(node, NULL); + + // Create a list of all the subviews that we are going to use for layout. + NSMutableArray *subviewsToInclude = [[NSMutableArray alloc] initWithCapacity:view.subviews.count]; + for (UIView *subview in view.subviews) { + if (subview.layout.isIncluded) { + [subviewsToInclude addObject:subview]; + } + } + + BOOL shouldReconstructChildList = NO; + if (YGNodeGetChildCount(node) != subviewsToInclude.count) { + shouldReconstructChildList = YES; + } else { + for (int i = 0; i < subviewsToInclude.count; i++) { + if (YGNodeGetChild(node, i) != subviewsToInclude[i].layout.node) { + shouldReconstructChildList = YES; + break; + } + } + } + + if (shouldReconstructChildList) { + YKRemoveAllChildren(node); + + for (int i = 0 ; i < subviewsToInclude.count; i++) { + UIView *const subview = subviewsToInclude[i]; + YGNodeInsertChild(node, subview.layout.node, i); + YKAttachNodesFromViewHierachy(subview.layout); + } + } + } +} + +static void YKRemoveAllChildren(const YGNodeRef node) +{ + if (node == NULL) { + return; + } + + while (YGNodeGetChildCount(node) > 0) { + YGNodeRemoveChild(node, YGNodeGetChild(node, YGNodeGetChildCount(node) - 1)); + } +} + +static CGFloat YKRoundPixelValue(CGFloat value) +{ + static CGFloat scale; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^(){ + scale = [UIScreen mainScreen].scale; + }); + + return round(value * scale) / scale; +} + +static void YKApplyLayoutToViewHierarchy(YKLayout *layout) { + NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread."); + + if (!layout.isIncluded) { + return; + } + + YGNodeRef node = layout.node; + const CGPoint topLeft = { + YGNodeLayoutGetLeft(node), + YGNodeLayoutGetTop(node), + }; + + const CGPoint bottomRight = { + topLeft.x + YGNodeLayoutGetWidth(node), + topLeft.y + YGNodeLayoutGetHeight(node), + }; + + UIView *view = layout.view; + view.frame = (CGRect) { + .origin = { + .x = YKRoundPixelValue(topLeft.x), + .y = YKRoundPixelValue(topLeft.y), + }, + .size = { + .width = YKRoundPixelValue(bottomRight.x) - YKRoundPixelValue(topLeft.x), + .height = YKRoundPixelValue(bottomRight.y) - YKRoundPixelValue(topLeft.y), + }, + }; + + const BOOL isLeaf = !layout.isEnabled || view.subviews.count == 0; + if (!isLeaf) { + for (NSUInteger i = 0; i < view.subviews.count; i++) { + YKApplyLayoutToViewHierarchy(view.subviews[i].layout); + } + } +} + +@end diff --git a/YogaKit/YogaKit/YogaKit.xcodeproj/project.pbxproj b/YogaKit/YogaKit/YogaKit.xcodeproj/project.pbxproj index a3eb674e..ae667b6f 100644 --- a/YogaKit/YogaKit/YogaKit.xcodeproj/project.pbxproj +++ b/YogaKit/YogaKit/YogaKit.xcodeproj/project.pbxproj @@ -25,6 +25,9 @@ 63EE08511E06EECB00EE5F9A /* UIView+YogaKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 13687D691DF8778F00E7C260 /* UIView+YogaKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 63EE08531E06F3D100EE5F9A /* YKEnums.h in Headers */ = {isa = PBXBuildFile; fileRef = 63EE08521E06F3D100EE5F9A /* YKEnums.h */; settings = {ATTRIBUTES = (Public, ); }; }; 63EE08551E072EF800EE5F9A /* SwiftViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EE08541E072EF800EE5F9A /* SwiftViewController.swift */; }; + 63EE08571E07590C00EE5F9A /* YKLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 63EE08561E07590C00EE5F9A /* YKLayout.m */; }; + 63EE08591E07598A00EE5F9A /* YKLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 63EE08581E07591A00EE5F9A /* YKLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 63EE085B1E075B0B00EE5F9A /* YKLayout+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 63EE085A1E075AAA00EE5F9A /* YKLayout+Private.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -74,6 +77,9 @@ 63EE08401E06ED3D00EE5F9A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63EE08521E06F3D100EE5F9A /* YKEnums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = YKEnums.h; path = ../../YKEnums.h; sourceTree = ""; }; 63EE08541E072EF800EE5F9A /* SwiftViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftViewController.swift; sourceTree = ""; }; + 63EE08561E07590C00EE5F9A /* YKLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = YKLayout.m; path = ../../YKLayout.m; sourceTree = ""; }; + 63EE08581E07591A00EE5F9A /* YKLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = YKLayout.h; path = ../../YKLayout.h; sourceTree = ""; }; + 63EE085A1E075AAA00EE5F9A /* YKLayout+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "YKLayout+Private.h"; path = "../../YKLayout+Private.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -169,6 +175,9 @@ 63EE08521E06F3D100EE5F9A /* YKEnums.h */, 13687D691DF8778F00E7C260 /* UIView+YogaKit.h */, 13687D6A1DF8778F00E7C260 /* UIView+YogaKit.m */, + 63EE08581E07591A00EE5F9A /* YKLayout.h */, + 63EE085A1E075AAA00EE5F9A /* YKLayout+Private.h */, + 63EE08561E07590C00EE5F9A /* YKLayout.m */, 63EE08401E06ED3D00EE5F9A /* Info.plist */, ); path = YogaKit; @@ -181,6 +190,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 63EE085B1E075B0B00EE5F9A /* YKLayout+Private.h in Headers */, + 63EE08591E07598A00EE5F9A /* YKLayout.h in Headers */, 63EE08411E06ED3D00EE5F9A /* YogaKit.h in Headers */, 63EE08531E06F3D100EE5F9A /* YKEnums.h in Headers */, 63EE08511E06EECB00EE5F9A /* UIView+YogaKit.h in Headers */, @@ -306,6 +317,7 @@ 63EE084A1E06EEB700EE5F9A /* YGNodeList.c in Sources */, 63EE084B1E06EEB700EE5F9A /* Yoga.c in Sources */, 63EE084C1E06EEB700EE5F9A /* UIView+YogaKit.m in Sources */, + 63EE08571E07590C00EE5F9A /* YKLayout.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/YogaKit/YogaKit/YogaKit.xcodeproj/project.xcworkspace/xcuserdata/david.xcuserdatad/UserInterfaceState.xcuserstate b/YogaKit/YogaKit/YogaKit.xcodeproj/project.xcworkspace/xcuserdata/david.xcuserdatad/UserInterfaceState.xcuserstate index b9c1ca19..f71eca1f 100644 Binary files a/YogaKit/YogaKit/YogaKit.xcodeproj/project.xcworkspace/xcuserdata/david.xcuserdatad/UserInterfaceState.xcuserstate and b/YogaKit/YogaKit/YogaKit.xcodeproj/project.xcworkspace/xcuserdata/david.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/YogaKit/YogaKit/YogaKitSample/SwiftViewController.swift b/YogaKit/YogaKit/YogaKitSample/SwiftViewController.swift index 40c032ef..60e6c3d2 100644 --- a/YogaKit/YogaKit/YogaKitSample/SwiftViewController.swift +++ b/YogaKit/YogaKit/YogaKitSample/SwiftViewController.swift @@ -13,17 +13,17 @@ class SwiftViewController : UIViewController { override func viewDidLoad() { let root = view! root.backgroundColor = .red - root.usesYoga = true - root.layoutWidth = view.bounds.size.width - root.layoutHeight = view.bounds.size.height - root.layoutAlignItems = .center - root.layoutJustifyContent = .center + root.layout.isEnabled = true + root.layout.width = view.bounds.size.width + root.layout.height = view.bounds.size.height + root.layout.alignItems = .center + root.layout.justifyContent = .center let child1 = UIView() child1.backgroundColor = .blue - child1.usesYoga = true - child1.layoutWidth = 100 - child1.layoutHeight = 100 + child1.layout.isEnabled = true + child1.layout.width = 100 + child1.layout.height = 100 let child2 = UIView() child2.backgroundColor = .green @@ -36,6 +36,6 @@ class SwiftViewController : UIViewController { child2.addSubview(child3) root.addSubview(child1) root.addSubview(child2) - root.applyLayout() + root.layout.apply() } } diff --git a/YogaKit/YogaKit/YogaKitSample/ViewController.m b/YogaKit/YogaKit/YogaKitSample/ViewController.m index 40ab77b0..02e565ee 100644 --- a/YogaKit/YogaKit/YogaKitSample/ViewController.m +++ b/YogaKit/YogaKit/YogaKitSample/ViewController.m @@ -19,17 +19,17 @@ { UIView *root = self.view; root.backgroundColor = [UIColor redColor]; - [root yk_setUsesYoga:YES]; - [root yk_setWidth:self.view.bounds.size.width]; - [root yk_setHeight:self.view.bounds.size.height]; - [root yk_setAlignItems:YKAlignCenter]; - [root yk_setJustifyContent:YKJustifyCenter]; + root.layout.isEnabled = YES; + root.layout.width = self.view.bounds.size.width; + root.layout.height = self.view.bounds.size.height; + root.layout.alignItems = YKAlignCenter; + root.layout.justifyContent = YKJustifyCenter; UIView *child1 = [UIView new]; child1.backgroundColor = [UIColor blueColor]; - [child1 yk_setUsesYoga:YES]; - [child1 yk_setWidth:100]; - [child1 yk_setHeight:100]; + child1.layout.isEnabled = YES; + child1.layout.width = 100; + child1.layout.height = 100; UIView *child2 = [UIView new]; child2.backgroundColor = [UIColor greenColor]; @@ -52,7 +52,7 @@ [child2 addSubview:child3]; [root addSubview:child1]; [root addSubview:child2]; - [root yk_applyLayout]; + [root.layout apply]; } diff --git a/enums.py b/enums.py index 2a3c7c01..8584f81c 100644 --- a/enums.py +++ b/enums.py @@ -95,6 +95,7 @@ OBJC_ENUMS = { 'LeftToRight', 'RightToLeft', ], + 'Edge': None, 'MeasureMode': None, 'PrintOptions': None, 'Dimension': None, @@ -215,5 +216,5 @@ with open(root + '/YogaKit/YKEnums.h', 'w') as f: f.write(' YK%s%s = %d,\n' % (name, value[0], value[1])) else: f.write(' YK%s%s,\n' % (name, value)) - f.write('};\n') + f.write('} NS_SWIFT_NAME(%s);\n' % name) f.write('\n')