Moved properties to a YKLayout object

This commit is contained in:
David Hart
2016-12-19 01:13:16 +01:00
parent f24baffffa
commit 5f5e5df328
11 changed files with 456 additions and 562 deletions

View File

@@ -8,68 +8,10 @@
*/
#import <UIKit/UIKit.h>
#import "YKEnums.h"
#import <YogaKit/YKLayout.h>
@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

View File

@@ -7,478 +7,22 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <YogaKit/UIView+Yoga.h>
#import <YogaKit/yoga.h>
#import <YogaKit/UIView+YogaKit.h>
#import <YogaKit/YKLayout+Private.h>
#import <objc/runtime.h>
@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];
YKLayout *layout = objc_getAssociatedObject(self, @selector(layout));
if (!layout) {
layout = [[YKLayout alloc] initWithView:self];
objc_setAssociatedObject(self, @selector(layout), layout, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)yk_includeInLayout
{
NSNumber *includeInLayout = objc_getAssociatedObject(self, @selector(yk_includeInLayout));
return (includeInLayout != nil) ? [includeInLayout boolValue] : YES;
return layout;
}
- (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);
}
return node.cnode;
}
- (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<UIView *> *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

View File

@@ -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);

View File

@@ -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 <YogaKit/YKLayout.h>
@interface YKLayout ()
- (instancetype)initWithView:(UIView*)view;
@end

97
YogaKit/YKLayout.h Normal file
View File

@@ -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

294
YogaKit/YKLayout.m Normal file
View File

@@ -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 <UIKit/UIKit.h>
#import <YogaKit/UIView+YogaKit.h>
#import <YogaKit/YKLayout.h>
#import <YogaKit/yoga.h>
#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<UIView *> *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

View File

@@ -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 = "<group>"; };
63EE08521E06F3D100EE5F9A /* YKEnums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = YKEnums.h; path = ../../YKEnums.h; sourceTree = "<group>"; };
63EE08541E072EF800EE5F9A /* SwiftViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftViewController.swift; sourceTree = "<group>"; };
63EE08561E07590C00EE5F9A /* YKLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = YKLayout.m; path = ../../YKLayout.m; sourceTree = "<group>"; };
63EE08581E07591A00EE5F9A /* YKLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = YKLayout.h; path = ../../YKLayout.h; sourceTree = "<group>"; };
63EE085A1E075AAA00EE5F9A /* YKLayout+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "YKLayout+Private.h"; path = "../../YKLayout+Private.h"; sourceTree = "<group>"; };
/* 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;
};

View File

@@ -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()
}
}

View File

@@ -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];
}

View File

@@ -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')