From 5f5e5df32808c773fe1c2615388d49f6eb4c40f0 Mon Sep 17 00:00:00 2001 From: David Hart Date: Mon, 19 Dec 2016 01:13:16 +0100 Subject: [PATCH] Moved properties to a YKLayout object --- YogaKit/UIView+YogaKit.h | 62 +-- YogaKit/UIView+YogaKit.m | 474 +----------------- YogaKit/YKEnums.h | 24 +- YogaKit/YKLayout+Private.h | 16 + YogaKit/YKLayout.h | 97 ++++ YogaKit/YKLayout.m | 294 +++++++++++ .../YogaKit/YogaKit.xcodeproj/project.pbxproj | 12 + .../UserInterfaceState.xcuserstate | Bin 27486 -> 33058 bytes .../YogaKitSample/SwiftViewController.swift | 18 +- .../YogaKit/YogaKitSample/ViewController.m | 18 +- enums.py | 3 +- 11 files changed, 456 insertions(+), 562 deletions(-) create mode 100644 YogaKit/YKLayout+Private.h create mode 100644 YogaKit/YKLayout.h create mode 100644 YogaKit/YKLayout.m 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 b9c1ca19bf172ab3d32b354f7ab67f6ec17cd296..f71eca1f940daa91844f18bc12a3bfac99a8171f 100644 GIT binary patch literal 33058 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 Gr2hwT2AORD delta 15283 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 Y1%sL{t|{`?dDA~(sN=i&|AN8)52r_CF#rGn 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')