This repository has been archived on 2024-07-22. You can view files and clone it, but cannot push or open issues or pull requests.
Doric/doric-iOS/Pod/Classes/Shader/DoricLayouts.m
2023-04-03 14:21:54 +08:00

1564 lines
62 KiB
Objective-C

/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// Created by pengfei.zhou on 2019/10/23.
//
#import "DoricLayouts.h"
#import <objc/runtime.h>
#import "UIView+Doric.h"
#import "DoricExtensions.h"
#import <YogaKit/UIView+Yoga.h>
#import <QuartzCore/QuartzCore.h>
#import <yoga/Yoga.h>
@interface YGLayout ()
@property(nonatomic, assign, readonly) YGNodeRef node;
@end
void DoricAddEllipticArcPath(CGMutablePathRef path,
CGPoint origin,
CGFloat radius,
CGFloat startAngle,
CGFloat endAngle) {
CGAffineTransform t = CGAffineTransformMakeTranslation(origin.x, origin.y);
CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, NO);
}
CGPathRef DoricCreateRoundedRectPath(CGRect bounds,
CGFloat leftTop,
CGFloat rightTop,
CGFloat rightBottom,
CGFloat leftBottom) {
const CGFloat minX = CGRectGetMinX(bounds);
const CGFloat minY = CGRectGetMinY(bounds);
const CGFloat maxX = CGRectGetMaxX(bounds);
const CGFloat maxY = CGRectGetMaxY(bounds);
CGMutablePathRef path = CGPathCreateMutable();
DoricAddEllipticArcPath(path, (CGPoint) {
minX + leftTop, minY + leftTop
}, leftTop, M_PI, 3 * M_PI_2);
DoricAddEllipticArcPath(path, (CGPoint) {
maxX - rightTop, minY + rightTop
}, rightTop, 3 * M_PI_2, 0);
DoricAddEllipticArcPath(path, (CGPoint) {
maxX - rightBottom, maxY - rightBottom
}, rightBottom, 0, M_PI_2);
DoricAddEllipticArcPath(path, (CGPoint) {
minX + leftBottom, maxY - leftBottom
}, leftBottom, M_PI_2, M_PI);
CGPathCloseSubpath(path);
return path;
}
static const void *kLayoutConfig = &kLayoutConfig;
@interface DoricShapeLayer : CAShapeLayer
@property CGRect viewBounds;
@property UIEdgeInsets corners;
@end
@implementation DoricShapeLayer
@end
@implementation UIView (DoricLayout)
@dynamic doricLayout;
- (void)setDoricLayout:(DoricLayout *)doricLayout {
objc_setAssociatedObject(self, kLayoutConfig, doricLayout, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (DoricLayout *)doricLayout {
DoricLayout *layout = objc_getAssociatedObject(self, kLayoutConfig);
if (!layout) {
layout = [DoricLayout new];
layout.width = self.width;
layout.height = self.height;
layout.view = self;
self.doricLayout = layout;
}
return layout;
}
@end
struct DoricSizeAndState {
NSUInteger state;
CGFloat size;
};
typedef struct DoricSizeAndState DoricSizeAndState;
DoricMeasureSpec DoricMeasureSpecMake(DoricMeasureSpecMode mode, CGFloat size) {
DoricMeasureSpec spec;
spec.mode = mode;
spec.size = size;
return spec;
}
NSUInteger DORIC_MEASURED_STATE_MASK = 0x11;
NSUInteger DORIC_MEASURED_HEIGHT_STATE_SHIFT = 8;
NSUInteger DORIC_MEASURED_STATE_TOO_SMALL = 0x01;
@interface DoricLayout ()
/**
* width-height
* 0xff--ff
* */
@property(nonatomic, assign) NSUInteger measuredState;
@property(nonatomic, strong) NSMutableDictionary *measuredCache;
@end
@implementation DoricLayout
- (instancetype)init {
if (self = [super init]) {
_widthSpec = DoricLayoutJust;
_heightSpec = DoricLayoutJust;
_maxWidth = CGFLOAT_MAX;
_maxHeight = CGFLOAT_MAX;
_minWidth = -1;
_minHeight = -1;
_measuredState = 0;
_measuredCache = [NSMutableDictionary new];
}
return self;
}
- (void)setMeasuredWidth:(CGFloat)measuredWidth {
_measuredWidth = MAX(0, measuredWidth);
}
- (void)setMeasuredHeight:(CGFloat)measuredHeight {
_measuredHeight = MAX(0, measuredHeight);
}
- (void)apply:(CGSize)frameSize {
self.resolved = NO;
[self measure:frameSize];
[self setFrame];
self.resolved = YES;
}
- (void)apply {
[self apply:self.view.frame.size];
}
- (void)measure:(CGSize)targetSize {
[self doMeasure:targetSize];
[self layout];
}
- (DoricMeasureSpec)getRootMeasureSpec:(CGFloat)targetSize
layoutSpec:(DoricLayoutSpec)spec
size:(CGFloat)size {
switch (spec) {
case DoricLayoutMost:
return DoricMeasureSpecMake(DoricMeasureExactly, targetSize);
case DoricLayoutFit:
if ([self.view.superview isKindOfClass:UIScrollView.class]) {
return DoricMeasureSpecMake(DoricMeasureUnspecified, 0);
}
return DoricMeasureSpecMake(DoricMeasureAtMost, targetSize);
default:
return DoricMeasureSpecMake(DoricMeasureExactly, size);
}
}
- (void)doMeasure:(CGSize)targetSize {
DoricMeasureSpec widthSpec = [self getRootMeasureSpec:targetSize.width
layoutSpec:self.widthSpec
size:self.width];
DoricMeasureSpec heightSpec = [self getRootMeasureSpec:targetSize.height
layoutSpec:self.heightSpec
size:self.height];
[self measureWidth:widthSpec
height:heightSpec];
}
#pragma helper
- (CGFloat)takenWidth {
return self.measuredWidth + self.marginLeft + self.marginRight;
}
- (CGFloat)takenHeight {
return self.measuredHeight + self.marginTop + self.marginBottom;
}
- (BOOL)rect:(CGRect)rect1 equalTo:(CGRect)rect2 {
return ABS(rect1.origin.x - rect2.origin.x) < 0.00001f
&& ABS(rect1.origin.y - rect2.origin.y) < 0.00001f
&& ABS(rect1.size.width - rect2.size.width) < 0.00001f
&& ABS(rect1.size.height - rect2.size.height) < 0.00001f;
}
- (NSString *)getLayoutType {
switch (self.layoutType) {
case DoricVLayout:
return @"VLayout";
case DoricHLayout:
return @"HLayout";
case DoricStack:
return @"Stack";
default:
return [NSString stringWithFormat:@"Undefined:%@", self.view.class];
}
}
- (NSString *)getSpecType:(DoricLayoutSpec)spec {
switch (spec) {
case DoricLayoutJust:
return @"JUST";
case DoricLayoutMost:
return @"MOST";
default:
return @"FIT";
}
}
- (NSString *)toString {
return [NSString stringWithFormat:@"%@[width:%@,height:%@]",
[self getLayoutType],
[self getSpecType:self.widthSpec],
[self getSpecType:self.heightSpec]
];
}
#pragma measure
- (DoricMeasureSpec)getChildMeasureSpec:(DoricMeasureSpec)spec
padding:(CGFloat)padding
childLayoutSpec:(DoricLayoutSpec)childLayoutSpec
childSize:(CGFloat)childSize {
DoricMeasureSpecMode specMode = spec.mode;
CGFloat specSize = spec.size;
CGFloat size = MAX(0, specSize - padding);
CGFloat resultSize = 0;
DoricMeasureSpecMode resultMode = DoricMeasureUnspecified;
switch (specMode) {
// Parent has imposed an exact size on us
case DoricMeasureExactly:
if (childLayoutSpec == DoricLayoutJust) {
resultSize = childSize;
resultMode = DoricMeasureExactly;
} else if (childLayoutSpec == DoricLayoutMost) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = DoricMeasureExactly;
} else if (childLayoutSpec == DoricLayoutFit) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = DoricMeasureAtMost;
}
break;
// Parent has imposed a maximum size on us
case DoricMeasureAtMost:
if (childLayoutSpec == DoricLayoutJust) {
// Child wants a specific size... so be it
resultSize = childSize;
resultMode = DoricMeasureExactly;
} else if (childLayoutSpec == DoricLayoutMost) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = DoricMeasureAtMost;
} else if (childLayoutSpec == DoricLayoutFit) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = DoricMeasureAtMost;
}
break;
// Parent asked to see how big we want to be
case DoricMeasureUnspecified:
if (childLayoutSpec == DoricLayoutJust) {
// Child wants a specific size... let them have it
resultSize = childSize;
resultMode = DoricMeasureExactly;
} else if (childLayoutSpec == DoricLayoutMost) {
// Child wants to be our size... find out how big it should
// be
resultSize = size;
resultMode = DoricMeasureUnspecified;
} else if (childLayoutSpec == DoricLayoutFit) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = DoricMeasureUnspecified;
}
break;
}
return DoricMeasureSpecMake(resultMode, resultSize);
}
- (void)measureChild:(DoricLayout *)child
widthSpec:(DoricMeasureSpec)widthSpec
usedWidth:(CGFloat)usedWidth
heightSpec:(DoricMeasureSpec)heightSpec
usedHeight:(CGFloat)usedHeight {
DoricMeasureSpec childWidthSpec =
[self getChildMeasureSpec:widthSpec
padding:self.paddingLeft + self.paddingRight
+ child.marginLeft + child.marginRight
+ usedWidth
childLayoutSpec:child.widthSpec
childSize:child.width];
DoricMeasureSpec childHeightSpec =
[self getChildMeasureSpec:heightSpec
padding:self.paddingTop + self.paddingBottom
+ child.marginTop + child.marginBottom
+ usedHeight
childLayoutSpec:child.heightSpec
childSize:child.height];
[child measureWidth:childWidthSpec height:childHeightSpec];
}
- (DoricSizeAndState)resolveSizeAndState:(CGFloat)size
spec:(DoricMeasureSpec)measureSpec
childMeasuredState:(NSUInteger)state {
DoricSizeAndState result;
DoricMeasureSpecMode specMode = measureSpec.mode;
CGFloat specSize = measureSpec.size;
result.state = 0;
switch (specMode) {
case DoricMeasureAtMost:
if (specSize < size) {
result.size = specSize;
result.state = DORIC_MEASURED_STATE_TOO_SMALL;
} else {
result.size = size;
}
break;
case DoricMeasureExactly:
result.size = specSize;
break;
case DoricMeasureUnspecified:
default:
result.size = size;
break;
}
result.state = result.state | (state & DORIC_MEASURED_STATE_MASK);
return result;
}
- (CGFloat)getDefaultSize:(CGFloat)size spec:(DoricMeasureSpec)measureSpec {
DoricMeasureSpecMode specMode = measureSpec.mode;
CGFloat specSize = measureSpec.size;
CGFloat result = size;
switch (specMode) {
case DoricMeasureUnspecified:
result = size;
break;
case DoricMeasureAtMost:
case DoricMeasureExactly:
result = specSize;
break;
}
return result;
}
- (void)forceUniformWidth:(DoricMeasureSpec)heightMeasureSpec {
DoricMeasureSpec uniformMeasureSpec = DoricMeasureSpecMake(DoricMeasureExactly,
self.measuredWidth);
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childLayout = subview.doricLayout;
if (childLayout.disabled) {
continue;
}
if (childLayout.widthSpec == DoricLayoutMost) {
// Temporarily force children to reuse their old measured height
// FIXME: this may not be right for something like wrapping text?
CGFloat oldHeight = childLayout.height;
DoricLayoutSpec oldHeightSpec = childLayout.heightSpec;
childLayout.height = childLayout.measuredHeight;
childLayout.heightSpec = DoricLayoutJust;
// Remeasure with new dimensions
[self measureChild:childLayout
widthSpec:uniformMeasureSpec
usedWidth:0
heightSpec:heightMeasureSpec
usedHeight:0];
childLayout.height = oldHeight;
childLayout.heightSpec = oldHeightSpec;
}
}
}
- (void)forceUniformHeight:(DoricMeasureSpec)widthMeasureSpec {
// Pretend that the linear layout has an exact size. This is the measured height of
// ourselves. The measured height should be the max height of the children, changed
// to accommodate the heightMeasureSpec from the parent
DoricMeasureSpec uniformMeasureSpec
= DoricMeasureSpecMake(DoricMeasureExactly, self.measuredHeight);
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childLayout = subview.doricLayout;
if (childLayout.disabled) {
continue;
}
if (childLayout.heightSpec == DoricLayoutMost) {
// Temporarily force children to reuse their old measured width
// FIXME: this may not be right for something like wrapping text?
CGFloat oldWidth = childLayout.width;
DoricLayoutSpec oldWidthSpec = childLayout.widthSpec;
childLayout.width = childLayout.measuredWidth;
childLayout.widthSpec = DoricLayoutJust;
// Remeasure with new dimensions
[self measureChild:childLayout
widthSpec:widthMeasureSpec
usedWidth:0
heightSpec:uniformMeasureSpec
usedHeight:0];
childLayout.width = oldWidth;
childLayout.widthSpec = oldWidthSpec;
}
}
}
- (void)measureWidth:(DoricMeasureSpec)widthSpec height:(DoricMeasureSpec)heightSpec {
// NSString *measuredKey = [NSString stringWithFormat:@"%@;%@;%@;%@",
// @(widthSpec.mode), @(widthSpec.size),
// @(heightSpec.mode), @(heightSpec.size)];
//
// NSString *cached = self.measuredCache[measuredKey];
// if (cached) {
// NSArray <NSString *> *nums = [cached componentsSeparatedByString:@":"];
// if (nums.count == 2) {
// self.measuredWidth = nums[0].floatValue;
// self.measuredHeight = nums[1].floatValue;
// return;
// }
// }
switch (self.layoutType) {
case DoricStack: {
[self stackMeasureWidth:widthSpec height:heightSpec];
break;
}
case DoricVLayout: {
[self verticalMeasureWidth:widthSpec height:heightSpec];
break;
}
case DoricHLayout: {
[self horizontalMeasureWidth:widthSpec height:heightSpec];
break;
}
case DoricScroller: {
[self scrollerMeasureWidth:widthSpec height:heightSpec];
break;
}
case DoricFlexLayout: {
[self flexMeasureWidth:widthSpec height:heightSpec];
break;
}
default: {
[self undefinedMeasureWidth:widthSpec height:heightSpec];
break;
}
}
if (self.measuredWidth > self.maxWidth || self.measuredHeight > self.measuredHeight) {
[self measureWidth:DoricMeasureSpecMake(DoricMeasureAtMost,
MIN(self.measuredWidth, self.maxWidth))
height:DoricMeasureSpecMake(DoricMeasureAtMost,
MIN(self.measuredHeight, self.maxHeight))];
}
// self.measuredCache[measuredKey] = [NSString stringWithFormat:@"%@;%@",
// @(self.measuredWidth),
// @(self.measuredHeight)];
}
- (void)verticalMeasureWidth:(DoricMeasureSpec)widthMeasureSpec
height:(DoricMeasureSpec)heightMeasureSpec {
CGFloat maxWidth = 0, totalLength = 0;
NSUInteger totalWeight = 0;
BOOL hadExtraSpace = NO;
DoricMeasureSpecMode widthMode = widthMeasureSpec.mode;
DoricMeasureSpecMode heightMode = heightMeasureSpec.mode;
BOOL skippedMeasure = NO;
BOOL matchWidth = NO;
BOOL allFillParent = YES;
CGFloat weightedMaxWidth = 0;
CGFloat alternativeMaxWidth = 0;
NSUInteger childState = 0;
// See how tall everyone is. Also remember max width.
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childLayout = subview.doricLayout;
if (childLayout.disabled) {
continue;
}
hadExtraSpace = YES;
totalLength += self.spacing;
totalWeight += childLayout.weight;
if (heightMode == DoricMeasureExactly
&& childLayout.heightSpec == DoricLayoutJust
&& childLayout.height == 0
&& childLayout.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
totalLength = MAX(totalLength, totalLength
+childLayout.marginTop + childLayout.marginBottom);
skippedMeasure = YES;
} else {
CGFloat oldHeight = CGFLOAT_MIN;
if (childLayout.heightSpec == DoricLayoutJust
&& childLayout.height == 0
&& childLayout.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
childLayout.heightSpec = DoricLayoutFit;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
[self measureChild:childLayout
widthSpec:widthMeasureSpec
usedWidth:0
heightSpec:heightMeasureSpec
usedHeight:totalWeight == 0 ? totalLength : 0];
if (oldHeight != CGFLOAT_MIN) {
childLayout.heightSpec = DoricLayoutJust;
childLayout.height = oldHeight;
}
CGFloat childHeight = childLayout.measuredHeight;
totalLength = MAX(totalLength, totalLength + childHeight +
childLayout.marginTop + childLayout.marginBottom);
}
BOOL matchWidthLocally = NO;
if (widthMode != DoricMeasureExactly && childLayout.widthSpec == DoricLayoutMost) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = YES;
matchWidthLocally = YES;
}
CGFloat margin = childLayout.marginLeft + childLayout.marginRight;
CGFloat measuredWidth = childLayout.measuredWidth + margin;
maxWidth = MAX(maxWidth, measuredWidth);
childState = childState | childLayout.measuredState;
allFillParent = allFillParent && childLayout.widthSpec == DoricLayoutMost;
if (childLayout.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth
= MAX(weightedMaxWidth, matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth
= MAX(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth);
}
}
if (hadExtraSpace) {
totalLength -= self.spacing;
}
// Add in our padding
totalLength += self.paddingTop + self.paddingBottom;
CGFloat heightSize = totalLength;
// Check against our minimum height
heightSize = MAX(heightSize, self.minHeight);
// Reconcile our calculated size with the heightMeasureSpec
DoricSizeAndState heightSizeAndState = [self resolveSizeAndState:heightSize
spec:heightMeasureSpec
childMeasuredState:0];
heightSize = heightSizeAndState.size;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
CGFloat delta = heightSize - totalLength;
if (skippedMeasure || (delta != 0 && totalWeight > 0)) {
NSUInteger weightSum = totalWeight;
totalLength = 0;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childLayout = subview.doricLayout;
if (childLayout.disabled) {
continue;
}
NSUInteger childExtra = childLayout.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
CGFloat share = childExtra * delta / weightSum;
weightSum -= childExtra;
delta -= share;
DoricMeasureSpec childWidthMeasureSpec =
[self getChildMeasureSpec:widthMeasureSpec
padding:self.paddingLeft + self.paddingRight
+ childLayout.marginLeft
+ childLayout.marginRight
childLayoutSpec:childLayout.widthSpec
childSize:childLayout.width];
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if (!(childLayout.heightSpec == DoricLayoutJust && childLayout.height == 0)
|| heightMode != DoricMeasureExactly) {
// child was measured once already above...
// base new measurement on stored values
CGFloat childHeight = childLayout.measuredHeight + share;
if (childHeight < 0) {
childHeight = 0;
}
[childLayout measureWidth:childWidthMeasureSpec
height:DoricMeasureSpecMake(DoricMeasureExactly,
childHeight)];
} else {
// child was skipped in the loop above.
// Measure for this first time here
[childLayout measureWidth:childWidthMeasureSpec
height:DoricMeasureSpecMake(DoricMeasureExactly,
share > 0 ? share : 0)];
}
// Child may now not fit in vertical dimension.
childState = childState | (childLayout.measuredState
& (DORIC_MEASURED_STATE_MASK >> DORIC_MEASURED_HEIGHT_STATE_SHIFT));
}
CGFloat margin = childLayout.marginLeft + childLayout.marginRight;
CGFloat measuredWidth = childLayout.measuredWidth + margin;
maxWidth = MAX(maxWidth, measuredWidth);
BOOL matchWidthLocally = widthMode != DoricMeasureExactly
&& childLayout.widthSpec == DoricLayoutMost;
alternativeMaxWidth = MAX(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && childLayout.widthSpec == DoricLayoutMost;
totalLength = MAX(totalLength, totalLength + childLayout.measuredHeight
+ childLayout.marginTop + childLayout.marginBottom);
totalLength += self.spacing;
}
if (hadExtraSpace) {
totalLength -= self.spacing;
}
totalLength += self.paddingTop + self.paddingBottom;
// TODO: Should we recompute the heightMeasureSpec based on the new total length?
} else {
alternativeMaxWidth = MAX(alternativeMaxWidth, weightedMaxWidth);
};
if (!allFillParent && widthMode != DoricMeasureExactly) {
maxWidth = alternativeMaxWidth;
}
maxWidth += self.paddingLeft + self.paddingRight;
// Check against our minimum width
maxWidth = MAX(maxWidth, self.minWidth);
DoricSizeAndState widthSizeAndState = [self resolveSizeAndState:maxWidth
spec:widthMeasureSpec
childMeasuredState:childState];
self.measuredWidth = widthSizeAndState.size;
self.measuredHeight = heightSize;
self.measuredState = (widthSizeAndState.state
<< DORIC_MEASURED_HEIGHT_STATE_SHIFT) | heightSizeAndState.state;
if (matchWidth) {
[self forceUniformWidth:heightMeasureSpec];
}
self.totalLength = totalLength;
}
- (void)horizontalMeasureWidth:(DoricMeasureSpec)widthMeasureSpec
height:(DoricMeasureSpec)heightMeasureSpec {
CGFloat maxHeight = 0, totalLength = 0;
NSUInteger totalWeight = 0;
BOOL hadExtraSpace = NO;
DoricMeasureSpecMode widthMode = widthMeasureSpec.mode;
DoricMeasureSpecMode heightMode = heightMeasureSpec.mode;
BOOL skippedMeasure = NO;
BOOL matchHeight = NO;
BOOL allFillParent = YES;
CGFloat weightedMaxHeight = 0;
CGFloat alternativeMaxHeight = 0;
BOOL isExactly = widthMode == DoricMeasureExactly;
NSUInteger childState = 0;
// See how wide everyone is. Also remember max height.
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childLayout = subview.doricLayout;
if (childLayout.disabled) {
continue;
}
hadExtraSpace = YES;
totalLength += self.spacing;
totalWeight += childLayout.weight;
if (widthMode == DoricMeasureExactly
&& childLayout.widthSpec == DoricLayoutJust
&& childLayout.width == 0
&& childLayout.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
if (isExactly) {
totalLength += childLayout.marginLeft + childLayout.marginRight;
} else {
totalLength = MAX(totalLength, totalLength
+childLayout.marginLeft + childLayout.marginRight);
}
skippedMeasure = YES;
} else {
CGFloat oldWidth = CGFLOAT_MIN;
if (childLayout.widthSpec == DoricLayoutJust
&& childLayout.width == 0
&& childLayout.weight > 0) {
// widthMode is either UNSPECIFIED or AT_MOST, and this
// child
// wanted to stretch to fill available space. Translate that to
// WRAP_CONTENT so that it does not end up with a width of 0
oldWidth = 0;
childLayout.widthSpec = DoricLayoutFit;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
[self measureChild:childLayout
widthSpec:widthMeasureSpec
usedWidth:totalWeight == 0 ? totalLength : 0
heightSpec:heightMeasureSpec
usedHeight:0];
if (oldWidth != CGFLOAT_MIN) {
childLayout.widthSpec = DoricLayoutJust;
childLayout.width = oldWidth;
}
CGFloat childWidth = childLayout.measuredWidth;
if (isExactly) {
totalLength += childWidth + childLayout.marginLeft + childLayout.marginRight;
} else {
totalLength = MAX(totalLength, totalLength + childWidth +
childLayout.marginLeft + childLayout.marginRight);
}
}
BOOL matchHeightLocally = NO;
if (heightMode != DoricMeasureExactly && childLayout.heightSpec == DoricLayoutMost) {
// The height of the linear layout will scale, and at least one
// child said it wanted to match our height. Set a flag indicating that
// we need to remeasure at least that view when we know our height.
matchHeight = YES;
matchHeightLocally = YES;
}
CGFloat margin = childLayout.marginTop + childLayout.marginBottom;
CGFloat childHeight = childLayout.measuredHeight + margin;
childState = childState | childLayout.measuredState;
maxHeight = MAX(maxHeight, childHeight);
allFillParent = allFillParent && childLayout.heightSpec == DoricLayoutMost;
if (childLayout.weight > 0) {
/*
* Heights of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxHeight = MAX(weightedMaxHeight, matchHeightLocally ? margin : childHeight);
} else {
alternativeMaxHeight = MAX(alternativeMaxHeight,
matchHeightLocally ? margin : childHeight);
}
}
if (hadExtraSpace) {
totalLength -= self.spacing;
}
// Add in our padding
totalLength += self.paddingLeft + self.paddingRight;
CGFloat widthSize = totalLength;
// Check against our minimum width
widthSize = MAX(widthSize, self.minWidth);
// Reconcile our calculated size with the widthMeasureSpec
DoricSizeAndState widthSizeAndState = [self resolveSizeAndState:widthSize
spec:widthMeasureSpec
childMeasuredState:0];
widthSize = widthSizeAndState.size;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
CGFloat delta = widthSize - totalLength;
if (skippedMeasure || (delta != 0 && totalWeight > 0)) {
NSUInteger weightSum = totalWeight;
totalLength = 0;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childLayout = subview.doricLayout;
if (childLayout.disabled) {
continue;
}
NSUInteger childExtra = childLayout.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
CGFloat share = childExtra * delta / weightSum;
weightSum -= childExtra;
delta -= share;
DoricMeasureSpec childHeightMeasureSpec
= [self getChildMeasureSpec:heightMeasureSpec
padding:self.paddingTop + self.paddingBottom
+ childLayout.marginTop
+ childLayout.marginBottom
childLayoutSpec:childLayout.heightSpec
childSize:childLayout.height];
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if (!(childLayout.widthSpec == DoricLayoutJust && childLayout.width == 0)
|| widthMode != DoricMeasureExactly) {
// child was measured once already above ... base new measurement
// on stored values
CGFloat childWidth = childLayout.measuredWidth + share;
if (childWidth < 0) {
childWidth = 0;
}
[childLayout measureWidth:DoricMeasureSpecMake(DoricMeasureExactly, childWidth)
height:childHeightMeasureSpec];
} else {
// child was skipped in the loop above. Measure for this first time here
[childLayout measureWidth:DoricMeasureSpecMake(DoricMeasureExactly,
share > 0 ? share : 0)
height:childHeightMeasureSpec];
}
// Child may now not fit in horizontal dimension.
childState = childState | (childLayout.measuredState & DORIC_MEASURED_STATE_MASK);
}
if (isExactly) {
totalLength += childLayout.measuredWidth
+ childLayout.marginLeft + childLayout.marginRight;
} else {
totalLength = MAX(totalLength, totalLength + childLayout.measuredWidth
+ childLayout.marginLeft + childLayout.marginRight);
}
totalLength += self.spacing;
BOOL matchHeightLocally = heightMode != DoricMeasureExactly
&& childLayout.heightSpec == DoricLayoutMost;
CGFloat margin = childLayout.marginTop + childLayout.marginBottom;
CGFloat childHeight = childLayout.measuredHeight + margin;
maxHeight = MAX(maxHeight, childHeight);
alternativeMaxHeight = MAX(alternativeMaxHeight,
matchHeightLocally ? margin : childHeight);
allFillParent = allFillParent && childLayout.heightSpec == DoricLayoutMost;
}
if (hadExtraSpace) {
totalLength -= self.spacing;
}
// Add in our padding
totalLength += self.paddingLeft + self.paddingRight;
// TODO: Should we update widthSize with the new total length?
} else {
alternativeMaxHeight = MAX(alternativeMaxHeight, weightedMaxHeight);
}
if (!allFillParent && heightMode != DoricMeasureExactly) {
maxHeight = alternativeMaxHeight;
}
maxHeight += self.paddingTop + self.paddingBottom;
// Check against our minimum height
maxHeight = MAX(maxHeight, self.minHeight);
self.measuredWidth = widthSize;
DoricSizeAndState heightSizeAndState = [self resolveSizeAndState:maxHeight
spec:heightMeasureSpec
childMeasuredState:childState
<< DORIC_MEASURED_HEIGHT_STATE_SHIFT];
self.measuredHeight = heightSizeAndState.size;
self.measuredState = ((widthSizeAndState.state | (childState & DORIC_MEASURED_STATE_MASK))
<< DORIC_MEASURED_HEIGHT_STATE_SHIFT) | heightSizeAndState.state;
if (matchHeight) {
[self forceUniformHeight:widthMeasureSpec];
}
self.totalLength = totalLength;
}
- (void)stackMeasureWidth:(DoricMeasureSpec)widthMeasureSpec
height:(DoricMeasureSpec)heightMeasureSpec {
CGFloat maxWidth = 0;
CGFloat maxHeight = 0;
BOOL measureMatchParentChildren = widthMeasureSpec.mode != DoricMeasureExactly
|| heightMeasureSpec.mode != DoricMeasureExactly;
NSMutableArray *matchParentChildren = [NSMutableArray new];
NSUInteger childState = 0;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childLayout = subview.doricLayout;
if (childLayout.disabled) {
continue;
}
[self measureChild:childLayout
widthSpec:widthMeasureSpec usedWidth:0
heightSpec:heightMeasureSpec usedHeight:0];
maxWidth = MAX(maxWidth, childLayout.measuredWidth
+ childLayout.marginLeft + childLayout.marginRight);
maxHeight = MAX(maxHeight, childLayout.measuredHeight
+ childLayout.marginTop + childLayout.marginBottom);
childState = childState | childLayout.measuredState;
if (measureMatchParentChildren) {
if (childLayout.widthSpec == DoricLayoutMost
|| childLayout.heightSpec == DoricLayoutMost) {
[matchParentChildren addObject:childLayout];
}
}
}
// Account for padding too
maxWidth += self.paddingLeft + self.paddingRight;
maxHeight += self.paddingTop + self.paddingBottom;
// Check against our minimum height and width
maxWidth = MAX(maxWidth, self.minWidth);
maxHeight = MAX(maxHeight, self.minHeight);
DoricSizeAndState widthSizeAndState = [self resolveSizeAndState:maxWidth
spec:widthMeasureSpec
childMeasuredState:childState];
DoricSizeAndState heightSizeAndState = [self resolveSizeAndState:maxHeight
spec:heightMeasureSpec
childMeasuredState:childState
<< DORIC_MEASURED_HEIGHT_STATE_SHIFT];
self.measuredWidth = widthSizeAndState.size;
self.measuredHeight = heightSizeAndState.size;
self.measuredState = (widthSizeAndState.state
<< DORIC_MEASURED_HEIGHT_STATE_SHIFT) | heightSizeAndState.state;
if (matchParentChildren.count > 0) {
for (DoricLayout *child in matchParentChildren) {
DoricMeasureSpec childWidthMeasureSpec;
if (child.widthSpec == DoricLayoutMost) {
childWidthMeasureSpec.size = MAX(0,
self.measuredWidth - self.paddingLeft - self.paddingRight
- child.marginLeft - child.marginRight);
childWidthMeasureSpec.mode = DoricMeasureExactly;
} else {
childWidthMeasureSpec
= [self getChildMeasureSpec:widthMeasureSpec
padding:self.paddingLeft + self.paddingRight
+ child.marginLeft + child.marginRight
childLayoutSpec:child.widthSpec
childSize:child.width];
}
DoricMeasureSpec childHeightMeasureSpec;
if (child.heightSpec == DoricLayoutMost) {
childHeightMeasureSpec.size
= MAX(0, self.measuredHeight - self.paddingTop - self.paddingBottom
- child.marginTop - child.marginBottom);
childHeightMeasureSpec.mode = DoricMeasureExactly;
} else {
childHeightMeasureSpec
= [self getChildMeasureSpec:heightMeasureSpec
padding:self.paddingTop + self.paddingBottom
+ child.marginTop + child.marginBottom
childLayoutSpec:child.heightSpec
childSize:child.height];
}
[child measureWidth:childWidthMeasureSpec height:childHeightMeasureSpec];
}
}
}
- (void)scrollerMeasureWidth:(DoricMeasureSpec)widthMeasureSpec
height:(DoricMeasureSpec)heightMeasureSpec {
DoricScrollView *scrollView = (DoricScrollView *) self.view;
DoricLayout *childLayout = scrollView.contentView.doricLayout;
[self measureChild:childLayout
widthSpec:widthMeasureSpec usedWidth:0
heightSpec:heightMeasureSpec usedHeight:0];
CGFloat maxWidth, maxHeight;
maxWidth = childLayout.measuredWidth
+ childLayout.marginLeft + childLayout.marginRight;
maxHeight = childLayout.measuredHeight
+ childLayout.marginTop + childLayout.marginBottom;
maxWidth += self.paddingLeft + self.paddingRight;
maxHeight += self.paddingTop + self.paddingBottom;
maxWidth = MAX(maxWidth, self.minWidth);
maxHeight = MAX(maxHeight, self.minHeight);
DoricSizeAndState widthSizeAndState = [self resolveSizeAndState:maxWidth
spec:widthMeasureSpec
childMeasuredState:0];
DoricSizeAndState heightSizeAndState = [self resolveSizeAndState:maxHeight
spec:heightMeasureSpec
childMeasuredState:0];
self.measuredWidth = widthSizeAndState.size;
self.measuredHeight = heightSizeAndState.size;
if (widthMeasureSpec.mode == DoricMeasureUnspecified
&& heightMeasureSpec.mode == DoricMeasureUnspecified) {
return;
}
CGFloat width = self.measuredWidth - self.paddingLeft - self.paddingRight;
CGFloat height = self.measuredHeight - self.paddingTop - self.paddingBottom;
DoricMeasureSpec childWidthMeasureSpec, childHeightMeasureSpec;
if (childLayout.widthSpec == DoricLayoutMost) {
childWidthMeasureSpec = DoricMeasureSpecMake(DoricMeasureExactly, width);
} else {
widthMeasureSpec = DoricMeasureSpecMake(DoricMeasureUnspecified, 0);
childWidthMeasureSpec = [self getChildMeasureSpec:widthMeasureSpec
padding:self.paddingLeft + self.paddingRight
childLayoutSpec:childLayout.widthSpec
childSize:childLayout.width];
}
if (childLayout.heightSpec == DoricLayoutMost) {
childHeightMeasureSpec = DoricMeasureSpecMake(DoricMeasureExactly, height);
} else {
heightMeasureSpec = DoricMeasureSpecMake(DoricMeasureUnspecified, 0);
childHeightMeasureSpec = [self getChildMeasureSpec:heightMeasureSpec
padding:self.paddingTop + self.paddingBottom
childLayoutSpec:childLayout.heightSpec
childSize:childLayout.height];
}
[childLayout measureWidth:childWidthMeasureSpec height:childHeightMeasureSpec];
}
- (void)flexMeasureWidth:(DoricMeasureSpec)widthMeasureSpec
height:(DoricMeasureSpec)heightMeasureSpec {
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *childDoricLayout = subview.doricLayout;
YGLayout *childYGLayout = subview.yoga;
DoricMeasureSpec childWidthMeasureSpec;
DoricMeasureSpec childHeightMeasureSpec;
if (childDoricLayout.widthSpec == DoricLayoutMost) {
childWidthMeasureSpec = DoricMeasureSpecMake(DoricMeasureAtMost, self.measuredWidth);
} else if (childDoricLayout.widthSpec == DoricLayoutFit) {
childWidthMeasureSpec = DoricMeasureSpecMake(DoricMeasureUnspecified, 0);
} else {
childWidthMeasureSpec = DoricMeasureSpecMake(DoricMeasureExactly, childDoricLayout.width);
}
if (childDoricLayout.heightSpec == DoricLayoutMost) {
childHeightMeasureSpec = DoricMeasureSpecMake(DoricMeasureAtMost, self.measuredHeight);
} else if (childDoricLayout.heightSpec == DoricLayoutFit) {
childHeightMeasureSpec = DoricMeasureSpecMake(DoricMeasureUnspecified, 0);
} else {
childHeightMeasureSpec = DoricMeasureSpecMake(DoricMeasureExactly, childDoricLayout.height);
}
[childDoricLayout measureWidth:childWidthMeasureSpec height:childHeightMeasureSpec];
if (childDoricLayout.layoutType != DoricFlexLayout) {
YGUnit widthUnit = YGUnitAuto;
YGUnit heightUnit = YGUnitAuto;
if (childDoricLayout.flexConfig) {
id widthValue = childDoricLayout.flexConfig[@"width"];
if ([widthValue isKindOfClass:NSNumber.class]) {
widthUnit = YGUnitPoint;
} else if ([widthValue isKindOfClass:NSDictionary.class]) {
id type = widthValue[@"type"];
if ([type isKindOfClass:NSNumber.class]) {
widthUnit = (YGUnit) [type integerValue];
}
}
id heightValue = childDoricLayout.flexConfig[@"height"];
if ([heightValue isKindOfClass:NSNumber.class]) {
heightUnit = YGUnitPoint;
} else if ([heightValue isKindOfClass:NSDictionary.class]) {
id type = heightValue[@"type"];
if ([type isKindOfClass:NSNumber.class]) {
heightUnit = (YGUnit) [type integerValue];
}
}
}
if (widthUnit == YGUnitAuto) {
childYGLayout.width = YGPointValue(childDoricLayout.measuredWidth);
}
if (heightUnit == YGUnitAuto) {
childYGLayout.height = YGPointValue(childDoricLayout.measuredHeight);
}
}
}
YGUnit widthUnit = YGUnitAuto;
YGUnit heightUnit = YGUnitAuto;
if (self.flexConfig) {
id widthValue = self.flexConfig[@"width"];
if ([widthValue isKindOfClass:NSNumber.class]) {
widthUnit = YGUnitPoint;
} else if ([widthValue isKindOfClass:NSDictionary.class]) {
id type = widthValue[@"type"];
if ([type isKindOfClass:NSNumber.class]) {
widthUnit = (YGUnit) [type integerValue];
}
}
id heightValue = self.flexConfig[@"height"];
if ([heightValue isKindOfClass:NSNumber.class]) {
heightUnit = YGUnitPoint;
} else if ([heightValue isKindOfClass:NSDictionary.class]) {
id type = heightValue[@"type"];
if ([type isKindOfClass:NSNumber.class]) {
heightUnit = (YGUnit) [type integerValue];
}
}
}
if (widthUnit == YGUnitAuto) {
self.view.yoga.width = YGValueAuto;
}
if (heightUnit == YGUnitAuto) {
self.view.yoga.height = YGValueAuto;
}
if (self.view.superview.doricLayout.layoutType != DoricFlexLayout) {
if (heightMeasureSpec.mode == DoricMeasureAtMost) {
heightMeasureSpec = DoricMeasureSpecMake(DoricMeasureUnspecified, heightMeasureSpec.size);
}
if (widthMeasureSpec.mode == DoricMeasureAtMost) {
widthMeasureSpec = DoricMeasureSpecMake(DoricMeasureUnspecified, widthMeasureSpec.size);
}
if (heightMeasureSpec.mode == DoricMeasureExactly) {
self.view.yoga.height = YGPointValue(heightMeasureSpec.size);
}
if (widthMeasureSpec.mode == DoricMeasureExactly) {
self.view.yoga.width = YGPointValue(widthMeasureSpec.size);
}
if (heightMeasureSpec.mode == DoricMeasureAtMost) {
self.view.yoga.maxHeight = YGPointValue(heightMeasureSpec.size);
}
if (widthMeasureSpec.mode == DoricMeasureAtMost) {
self.view.yoga.maxWidth = YGPointValue(widthMeasureSpec.size);
}
[self.view.yoga intrinsicSize];
}
DoricSizeAndState widthSizeAndState = [self resolveSizeAndState:YGNodeLayoutGetWidth(self.view.yoga.node)
spec:widthMeasureSpec
childMeasuredState:0];
self.measuredWidth = widthSizeAndState.size;
DoricSizeAndState heightSizeAndState = [self resolveSizeAndState:YGNodeLayoutGetHeight(self.view.yoga.node)
spec:heightMeasureSpec
childMeasuredState:0];
self.measuredHeight = heightSizeAndState.size;
}
- (void)undefinedMeasureWidth:(DoricMeasureSpec)widthMeasureSpec
height:(DoricMeasureSpec)heightMeasureSpec {
CGSize targetSize = CGSizeMake(widthMeasureSpec.size - self.paddingLeft - self.paddingRight,
heightMeasureSpec.size - self.paddingTop - self.paddingBottom);
CGSize measuredSize = [self.view sizeThatFits:targetSize];
CGFloat contentWidth = measuredSize.width;
CGFloat contentHeight = measuredSize.height;
if ([self.view isKindOfClass:UIImageView.class]) {
if (self.widthSpec == DoricLayoutFit
&& self.heightSpec != DoricLayoutFit && measuredSize.height > 0) {
DoricSizeAndState preHeightSizeAndState = [self resolveSizeAndState:contentHeight
+ self.paddingTop + self.paddingBottom
spec:heightMeasureSpec
childMeasuredState:0];
contentWidth = measuredSize.width / measuredSize.height
* (preHeightSizeAndState.size - self.paddingBottom - self.paddingBottom);
self.measuredWidth = contentWidth + self.paddingLeft + self.paddingBottom;
self.measuredHeight = preHeightSizeAndState.size;
self.measuredState = 0;
return;
}
if (self.heightSpec == DoricLayoutFit
&& self.widthSpec != DoricLayoutFit && measuredSize.width > 0) {
DoricSizeAndState preWidthSizeAndState = [self resolveSizeAndState:contentWidth
+ self.paddingLeft + self.paddingRight
spec:widthMeasureSpec
childMeasuredState:0];
contentHeight = measuredSize.height / measuredSize.width
* (preWidthSizeAndState.size - self.paddingLeft - self.paddingRight);
self.measuredWidth = preWidthSizeAndState.size;
self.measuredHeight = contentHeight + self.paddingTop + self.paddingBottom;
self.measuredState = 0;
return;
}
}
DoricSizeAndState widthSizeAndState = [self resolveSizeAndState:contentWidth
+ self.paddingLeft + self.paddingRight
spec:widthMeasureSpec
childMeasuredState:0];
DoricSizeAndState heightSizeAndState = [self resolveSizeAndState:contentHeight
+ self.paddingTop + self.paddingBottom
spec:heightMeasureSpec
childMeasuredState:0];
self.measuredWidth = MAX(widthSizeAndState.size, self.minWidth);
self.measuredHeight = MAX(heightSizeAndState.size, self.minHeight);
self.measuredState = (widthSizeAndState.state
<< DORIC_MEASURED_HEIGHT_STATE_SHIFT) | heightSizeAndState.state;
}
#pragma layout
- (void)layout {
switch (self.layoutType) {
case DoricStack: {
[self layoutStack];
break;
}
case DoricVLayout: {
[self layoutVLayout];
break;
}
case DoricHLayout: {
[self layoutHLayout];
break;
}
case DoricScroller: {
[self layoutScroller];
break;
}
case DoricFlexLayout: {
[self layoutFlex];
break;
}
default: {
break;
}
}
}
#pragma setFrame
- (void)setFrame {
if (self.layoutType == DoricScroller) {
[((DoricScrollView *) self.view).contentView.doricLayout setFrame];
} else if (self.layoutType != DoricUndefined) {
[self.view.subviews forEach:^(__kindof UIView *obj) {
[obj.doricLayout setFrame];
}];
}
CGRect originFrame = CGRectMake(self.measuredX, self.measuredY,
self.measuredWidth, self.measuredHeight);
if (!CGAffineTransformEqualToTransform(self.view.transform, CGAffineTransformIdentity)) {
CGPoint anchor = self.view.layer.anchorPoint;
originFrame = CGRectOffset(originFrame,
-anchor.x * self.measuredWidth - self.measuredX,
-anchor.y * self.measuredHeight - self.measuredY);
originFrame = CGRectApplyAffineTransform(originFrame, self.view.transform);
originFrame = CGRectOffset(originFrame,
anchor.x * self.measuredWidth + self.measuredX,
anchor.y * self.measuredHeight + self.measuredY);
}
BOOL isFrameChange = ![self rect:originFrame equalTo:self.view.frame];
if (isFrameChange) {
if (isnan(originFrame.origin.x) || isinf(originFrame.origin.x)
|| isnan(originFrame.origin.y) || isinf(originFrame.origin.y)
|| isnan(originFrame.size.width) || isinf(originFrame.size.width)
|| isnan(originFrame.size.height) || isinf(originFrame.size.height)
) {
return;
}
self.view.frame = originFrame;
}
if (!UIEdgeInsetsEqualToEdgeInsets(self.corners, UIEdgeInsetsZero)) {
if (self.view.layer.mask) {
if ([self.view.layer.mask isKindOfClass:[DoricShapeLayer class]]) {
DoricShapeLayer *shapeLayer = (DoricShapeLayer *) self.view.layer.mask;
if (!UIEdgeInsetsEqualToEdgeInsets(self.corners, shapeLayer.corners)
|| !CGRectEqualToRect(self.view.bounds, shapeLayer.viewBounds)) {
shapeLayer.corners = self.corners;
shapeLayer.viewBounds = self.view.bounds;
[self configMaskWithLayer:shapeLayer];
}
} else if (isFrameChange) {
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
[self configMaskWithLayer:shapeLayer];
}
} else {
DoricShapeLayer *shapeLayer = [DoricShapeLayer layer];
shapeLayer.corners = self.corners;
shapeLayer.viewBounds = self.view.bounds;
[self configMaskWithLayer:shapeLayer];
}
}
}
- (void)configMaskWithLayer:(CAShapeLayer *)shapeLayer {
CGPathRef path = DoricCreateRoundedRectPath(self.view.bounds,
self.corners.top, self.corners.left, self.corners.bottom, self.corners.right);
shapeLayer.path = path;
if ((self.corners.left != self.corners.right
|| self.corners.left != self.corners.top
|| self.corners.left != self.corners.bottom)
&& self.view.layer.borderWidth > CGFLOAT_MIN) {
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.lineWidth = self.view.layer.borderWidth * 2;
lineLayer.strokeColor = self.view.layer.borderColor;
lineLayer.path = path;
lineLayer.fillColor = nil;
[[self.view.layer sublayers] forEach:^(__kindof CALayer *obj) {
if ([obj isKindOfClass:CAShapeLayer.class]
&& ((CAShapeLayer *) obj).lineWidth > CGFLOAT_MIN) {
[obj removeFromSuperlayer];
}
}];
[self.view.layer addSublayer:lineLayer];
}
CGPathRelease(path);
self.view.layer.mask = shapeLayer;
}
- (void)layoutStack {
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
[layout layout];
DoricGravity gravity = layout.alignment;
if ((gravity & DoricGravityLeft) == DoricGravityLeft) {
layout.measuredX = self.paddingLeft;
} else if ((gravity & DoricGravityRight) == DoricGravityRight) {
layout.measuredX = self.measuredWidth - self.paddingRight - layout.measuredWidth;
} else if ((gravity & DoricGravityCenterX) == DoricGravityCenterX) {
layout.measuredX = self.measuredWidth / 2 - layout.measuredWidth / 2;
} else {
layout.measuredX = self.paddingLeft;
}
if ((gravity & DoricGravityTop) == DoricGravityTop) {
layout.measuredY = self.paddingTop;
} else if ((gravity & DoricGravityBottom) == DoricGravityBottom) {
layout.measuredY = self.measuredHeight - self.paddingBottom - layout.measuredHeight;
} else if ((gravity & DoricGravityCenterY) == DoricGravityCenterY) {
layout.measuredY = self.measuredHeight / 2 - layout.measuredHeight / 2;
} else {
layout.measuredY = self.paddingTop;
}
if (!gravity) {
gravity = DoricGravityLeft | DoricGravityTop;
}
if (layout.marginLeft && !((gravity & DoricGravityRight) == DoricGravityRight)) {
layout.measuredX += layout.marginLeft;
}
if (layout.marginRight && !((gravity & DoricGravityLeft) == DoricGravityLeft)) {
layout.measuredX -= layout.marginRight;
}
if (layout.marginTop && !((gravity & DoricGravityBottom) == DoricGravityBottom)) {
layout.measuredY += layout.marginTop;
}
if (layout.marginBottom && !((gravity & DoricGravityTop) == DoricGravityTop)) {
layout.measuredY -= layout.marginBottom;
}
}
}
- (void)layoutVLayout {
CGFloat yStart = self.paddingTop;
if ((self.gravity & DoricGravityTop) == DoricGravityTop) {
yStart = self.paddingTop;
} else if ((self.gravity & DoricGravityBottom) == DoricGravityBottom) {
yStart = self.measuredHeight - self.totalLength - self.paddingBottom;
} else if ((self.gravity & DoricGravityCenterY) == DoricGravityCenterY) {
yStart = (self.measuredHeight - self.totalLength
- self.paddingTop - self.paddingBottom) / 2 + self.paddingTop;
}
for (UIView *child in self.view.subviews) {
DoricLayout *layout = child.doricLayout;
if (layout.disabled) {
continue;
}
[layout layout];
DoricGravity gravity = layout.alignment | self.gravity;
if ((gravity & DoricGravityLeft) == DoricGravityLeft) {
layout.measuredX = self.paddingLeft;
} else if ((gravity & DoricGravityRight) == DoricGravityRight) {
layout.measuredX = self.measuredWidth - self.paddingRight - layout.measuredWidth;
} else if ((gravity & DoricGravityCenterX) == DoricGravityCenterX) {
layout.measuredX = self.measuredWidth / 2 - layout.measuredWidth / 2;
} else {
layout.measuredX = self.paddingLeft;
}
if (!gravity) {
gravity = DoricGravityLeft;
}
if (layout.marginLeft && !((gravity & DoricGravityRight) == DoricGravityRight)) {
layout.measuredX += layout.marginLeft;
}
if (layout.marginRight && !((gravity & DoricGravityLeft) == DoricGravityLeft)) {
layout.measuredX -= layout.marginRight;
}
layout.measuredY = yStart + layout.marginTop;
yStart += self.spacing + layout.takenHeight;
}
}
- (void)layoutHLayout {
CGFloat xStart = self.paddingLeft;
if ((self.gravity & DoricGravityLeft) == DoricGravityLeft) {
xStart = self.paddingLeft;
} else if ((self.gravity & DoricGravityRight) == DoricGravityRight) {
xStart = self.measuredWidth - self.totalLength - self.paddingRight;
} else if ((self.gravity & DoricGravityCenterX) == DoricGravityCenterX) {
xStart = (self.measuredWidth - self.totalLength
- self.paddingLeft - self.paddingRight) / 2 + self.paddingLeft;
}
for (UIView *child in self.view.subviews) {
DoricLayout *layout = child.doricLayout;
if (layout.disabled) {
continue;
}
[layout layout];
DoricGravity gravity = layout.alignment | self.gravity;
if ((gravity & DoricGravityTop) == DoricGravityTop) {
layout.measuredY = self.paddingTop;
} else if ((gravity & DoricGravityBottom) == DoricGravityBottom) {
layout.measuredY = self.measuredHeight - self.paddingBottom - layout.measuredHeight;
} else if ((gravity & DoricGravityCenterY) == DoricGravityCenterY) {
layout.measuredY = self.measuredHeight / 2 - layout.measuredHeight / 2;
} else {
layout.measuredY = self.paddingTop;
}
if (!gravity) {
gravity = DoricGravityTop;
}
if (layout.marginTop && !((gravity & DoricGravityBottom) == DoricGravityBottom)) {
layout.measuredY += layout.marginTop;
}
if (layout.marginBottom && !((gravity & DoricGravityTop) == DoricGravityTop)) {
layout.measuredY -= layout.marginBottom;
}
layout.measuredX = xStart + layout.marginLeft;
xStart += self.spacing + layout.takenWidth;
}
}
- (void)layoutScroller {
DoricScrollView *scrollView = (DoricScrollView *) self.view;
DoricLayout *layout = scrollView.contentView.doricLayout;
if (layout.disabled) {
return;
}
[layout layout];
}
- (void)layoutFlex {
for (UIView *child in self.view.subviews) {
DoricLayout *layout = child.doricLayout;
if (layout.disabled) {
continue;
}
layout.measuredX = YGNodeLayoutGetLeft(child.yoga.node);
layout.measuredY = YGNodeLayoutGetTop(child.yoga.node);
[layout measureWidth:DoricMeasureSpecMake(DoricMeasureExactly,
YGNodeLayoutGetWidth(child.yoga.node))
height:DoricMeasureSpecMake(DoricMeasureExactly,
YGNodeLayoutGetHeight(child.yoga.node))];
[layout layout];
}
}
@end
@implementation DoricScrollView
- (void)setContentView:(UIView *)contentView {
if (_contentView) {
[_contentView removeFromSuperview];
}
_contentView = contentView;
[self addSubview:contentView];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.contentSize = self.contentView.frame.size;
}
@end