Files
yoga/uikit/CSSLayout/UIView+CSSLayout.m
Dustin Shahidehpour 26bcc1e072 Remove view.isHidden optimization from sizing/layout.
Summary:
Originally, we thought that skipping the measurement of views that were hidden would be a nice optimiatzion. Upon further review, we saw that according to Apple's `UIView` documentation:

> A hidden view disappears from its window and does not receive input events. It remains in its superview’s list of subviews, however, and participates in autoresizing as usual.

So, to keep parity with common iOS layout APIs, we are going to size and layout views, even if they are hidden.

Reviewed By: emilsjolander

Differential Revision: D4051225

fbshipit-source-id: 0794cbad293a7de83d109dad2b3983d83845d145
2016-10-20 09:37:39 -07:00

293 lines
6.8 KiB
Objective-C

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