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
2022-08-05 11:24:46 +08:00

664 lines
24 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 <QuartzCore/QuartzCore.h>
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
@interface DoricLayout ()
@property(nonatomic,assign) BOOL reassignWidth;
@property(nonatomic,assign) BOOL reassignHeight;
@end
@implementation DoricLayout
- (instancetype)init {
if (self = [super init]) {
_widthSpec = DoricLayoutJust;
_heightSpec = DoricLayoutJust;
_maxWidth = CGFLOAT_MAX;
_maxHeight = CGFLOAT_MAX;
_minWidth = CGFLOAT_MIN;
_minHeight = CGFLOAT_MIN;
_reassignWidth = NO;
_reassignHeight = NO;
}
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 measureSelf:targetSize];
[self layout];
}
- (void)measureSelf:(CGSize)targetSize {
CGFloat width;
CGFloat height;
if (self.widthSpec == DoricLayoutMost) {
if (self.view.superview.doricLayout.widthSpec == DoricLayoutFit) {
width = targetSize.width;
} else if (self.view.superview.doricLayout.layoutType == DoricHLayout && self.weight > 0) {
width = self.measuredWidth = 0;
} else {
width = self.measuredWidth = targetSize.width;
}
} else if (self.widthSpec == DoricLayoutJust) {
width = self.measuredWidth = self.width;
} else {
width = targetSize.width;
}
if (self.heightSpec == DoricLayoutMost) {
if (self.view.superview.doricLayout.layoutType == DoricVLayout && self.weight > 0) {
height = self.measuredHeight = 0;
} else {
height = self.measuredHeight = targetSize.height;
}
} else if (self.heightSpec == DoricLayoutJust) {
height = self.measuredHeight = self.height;
} else {
height = targetSize.height;
}
[self measureContent:CGSizeMake(
width - self.paddingLeft - self.paddingRight,
height - self.paddingTop - self.paddingBottom)];
if ([self restrainSize]) {
[self measureContent:CGSizeMake(
self.measuredWidth - self.paddingLeft - self.paddingRight,
self.measuredHeight - self.paddingTop - self.paddingBottom)];
}
[self restrainSize];
}
- (BOOL)restrainSize {
BOOL needRemeasure = NO;
if (self.measuredWidth > self.maxWidth) {
self.measuredWidth = self.maxWidth;
needRemeasure = YES;
}
if (self.measuredHeight > self.maxHeight) {
self.measuredHeight = self.maxHeight;
needRemeasure = YES;
}
if (self.measuredWidth < self.minWidth) {
self.measuredWidth = self.minWidth;
needRemeasure = YES;
}
if (self.measuredHeight < self.minHeight) {
self.measuredHeight = self.minHeight;
needRemeasure = YES;
}
return needRemeasure;
}
- (void)measureContent:(CGSize)targetSize {
self.reassignWidth = NO;
self.reassignHeight = NO;
switch (self.layoutType) {
case DoricStack: {
[self measureStackContent:targetSize];
break;
}
case DoricVLayout: {
[self measureVLayoutContent:targetSize];
break;
}
case DoricHLayout: {
[self measureHLayoutContent:targetSize];
break;
}
default: {
[self measureUndefinedContent:targetSize];
break;
}
}
if (self.view.superview.doricLayout.widthSpec == DoricLayoutFit && self.widthSpec == DoricLayoutMost) {
self.measuredWidth = self.contentWidth + self.paddingLeft + self.paddingRight;
}
if (self.view.superview.doricLayout.heightSpec == DoricLayoutFit && self.heightSpec == DoricLayoutMost) {
self.measuredHeight = self.contentHeight + self.paddingTop + self.paddingBottom;
}
if (self.view.superview.doricLayout.layoutType == DoricHLayout
&& self.view.superview.doricLayout.widthSpec == DoricLayoutFit
&& self.weight > 0
&& self.widthSpec == DoricLayoutJust
) {
self.measuredWidth = self.contentWidth + self.paddingLeft + self.paddingRight + self.width;
self.reassignWidth = YES;
}
if (self.view.superview.doricLayout.layoutType == DoricVLayout
&& self.view.superview.doricLayout.heightSpec == DoricLayoutFit
&& self.weight > 0
&& self.heightSpec == DoricLayoutJust
) {
self.measuredHeight = self.contentHeight + self.paddingTop + self.paddingBottom + self.height;
self.reassignHeight = YES;
}
}
- (void)layout {
switch (self.layoutType) {
case DoricStack: {
[self layoutStack];
break;
}
case DoricVLayout: {
[self layoutVLayout];
break;
}
case DoricHLayout: {
[self layoutHLayout];
break;
}
default: {
break;
}
}
}
- (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;
}
- (void)setFrame {
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)measureUndefinedContent:(CGSize)targetSize {
CGSize measuredSize = [self.view sizeThatFits:targetSize];
if (self.widthSpec == DoricLayoutFit) {
if ([self.view isKindOfClass:[UIImageView class]]
&& self.heightSpec != DoricLayoutFit && measuredSize.height > 0) {
self.measuredWidth = measuredSize.width / measuredSize.height * self.measuredHeight
+ self.paddingLeft + self.paddingRight;
} else {
self.measuredWidth = measuredSize.width + self.paddingLeft + self.paddingRight;
}
}
if (self.heightSpec == DoricLayoutFit) {
if ([self.view isKindOfClass:[UIImageView class]]
&& self.widthSpec != DoricLayoutFit && measuredSize.width > 0) {
self.measuredHeight = measuredSize.height / measuredSize.width * self.measuredWidth
+ self.paddingTop + self.paddingBottom;
} else {
self.measuredHeight = measuredSize.height + self.paddingTop + self.paddingBottom;
}
}
self.contentWidth = measuredSize.width;
self.contentHeight = measuredSize.height;
}
- (CGFloat)takenWidth {
return self.measuredWidth + self.marginLeft + self.marginRight;
}
- (CGFloat)takenHeight {
return self.measuredHeight + self.marginTop + self.marginBottom;
}
- (CGSize)removeMargin:(CGSize)targetSize {
return CGSizeMake(
targetSize.width - self.marginLeft - self.marginRight,
targetSize.height - self.marginTop - self.marginBottom);
}
- (void)measureStackContent:(CGSize)targetSize {
CGFloat contentWidth = 0, contentHeight = 0;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
[layout measure:[layout removeMargin:targetSize]];
contentWidth = MAX(contentWidth, layout.takenWidth);
contentHeight = MAX(contentHeight, layout.takenHeight);
}
if (self.widthSpec == DoricLayoutFit) {
self.measuredWidth = contentWidth + self.paddingLeft + self.paddingRight;
}
if (self.heightSpec == DoricLayoutFit) {
self.measuredHeight = contentHeight + self.paddingTop + self.paddingBottom;
}
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
}
- (void)measureVLayoutContent:(CGSize)targetSize {
CGFloat contentWidth = 0, contentHeight = 0, contentWeight = 0;
BOOL had = NO;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
had = YES;
[layout measure:[layout removeMargin:CGSizeMake(targetSize.width, targetSize.height - contentHeight)]];
contentWidth = MAX(contentWidth, layout.takenWidth);
contentHeight += layout.takenHeight + self.spacing;
contentWeight += layout.weight;
}
if (had) {
contentHeight -= self.spacing;
}
if (contentWeight > 0 && self.heightSpec != DoricLayoutFit) {
CGFloat remaining = targetSize.height - contentHeight;
contentWidth = 0;
contentHeight = 0;
had = NO;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
had = YES;
CGFloat measuredHeight = layout.measuredHeight + remaining / contentWeight * layout.weight;
layout.measuredHeight = measuredHeight;
//Need Remeasure
[layout measureContent:CGSizeMake(
layout.measuredWidth - layout.paddingLeft - layout.paddingRight,
measuredHeight - layout.paddingTop - layout.paddingBottom)];
layout.measuredHeight = measuredHeight;
contentWidth = MAX(contentWidth, layout.takenWidth);
contentHeight += layout.takenHeight + self.spacing;
}
if (had) {
contentHeight -= self.spacing;
}
}
if (self.widthSpec == DoricLayoutFit) {
self.measuredWidth = contentWidth + self.paddingLeft + self.paddingRight;
}
if (self.heightSpec == DoricLayoutFit) {
self.measuredHeight = contentHeight + self.paddingTop + self.paddingBottom;
}
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
}
- (void)measureHLayoutContent:(CGSize)targetSize {
CGFloat contentWidth = 0, contentHeight = 0, contentWeight = 0;;
BOOL had = NO;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
had = YES;
[layout measure:[layout removeMargin:CGSizeMake(targetSize.width - contentWidth, targetSize.height)]];
contentWidth += layout.takenWidth + self.spacing;
contentHeight = MAX(contentHeight, layout.takenHeight);
contentWeight += layout.weight;
}
if (had) {
contentWidth -= self.spacing;
}
if (contentWeight > 0 && self.widthSpec != DoricLayoutFit) {
CGFloat remaining = targetSize.width - contentWidth;
contentWidth = 0;
contentHeight = 0;
had = NO;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
had = YES;
CGFloat measuredWidth = layout.measuredWidth + remaining / contentWeight * layout.weight;
layout.measuredWidth = measuredWidth;
//Need Remeasure
[layout measureContent:CGSizeMake(
measuredWidth - layout.paddingLeft - layout.paddingRight,
layout.measuredHeight - layout.paddingTop - layout.paddingBottom)];
layout.measuredWidth = measuredWidth;
contentWidth += layout.takenWidth + self.spacing;
contentHeight = MAX(contentHeight, layout.takenHeight);
}
if (had) {
contentWidth -= self.spacing;
}
}
if (self.widthSpec == DoricLayoutFit) {
self.measuredWidth = contentWidth + self.paddingLeft + self.paddingRight;
}
if (self.heightSpec == DoricLayoutFit) {
self.measuredHeight = contentHeight + self.paddingTop + self.paddingBottom;
}
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
}
- (void)layoutStack {
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
if ((self.widthSpec == DoricLayoutFit || self.reassignWidth) && layout.widthSpec == DoricLayoutMost) {
layout.measuredWidth = self.contentWidth - layout.marginLeft - layout.marginRight;
}
if ((self.heightSpec == DoricLayoutFit || self.reassignHeight) && layout.heightSpec == DoricLayoutMost) {
layout.measuredHeight = self.contentHeight - layout.marginTop - layout.marginBottom;
}
[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.contentHeight - self.paddingBottom;
} else if ((self.gravity & DoricGravityCenterY) == DoricGravityCenterY) {
yStart = (self.measuredHeight - self.contentHeight - self.paddingTop - self.paddingBottom) / 2 + self.paddingTop;
}
for (UIView *child in self.view.subviews) {
DoricLayout *layout = child.doricLayout;
if (layout.disabled) {
continue;
}
if ((self.widthSpec == DoricLayoutFit || self.reassignWidth) && layout.widthSpec == DoricLayoutMost) {
layout.measuredWidth = self.contentWidth - layout.marginLeft - layout.marginRight;
}
[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.contentWidth - self.paddingRight;
} else if ((self.gravity & DoricGravityCenterX) == DoricGravityCenterX) {
xStart = (self.measuredWidth - self.contentWidth - self.paddingLeft - self.paddingRight) / 2 + self.paddingLeft;
}
for (UIView *child in self.view.subviews) {
DoricLayout *layout = child.doricLayout;
if (layout.disabled) {
continue;
}
if ((self.heightSpec == DoricLayoutFit || self.reassignHeight) && layout.heightSpec == DoricLayoutMost) {
layout.measuredHeight = self.contentHeight - layout.marginTop - layout.marginBottom;
}
[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;
}
}
@end