iOS: refact layout logic

This commit is contained in:
pengfei.zhou 2022-08-18 19:57:22 +08:00 committed by osborn
parent f23159e6bb
commit 3da39cff48

View File

@ -92,8 +92,6 @@ - (DoricLayout *)doricLayout {
@end
@interface DoricLayout ()
@property(nonatomic, assign) BOOL reassignWidth;
@property(nonatomic, assign) BOOL reassignHeight;
@end
@implementation DoricLayout
@ -103,10 +101,8 @@ - (instancetype)init {
_heightSpec = DoricLayoutJust;
_maxWidth = CGFLOAT_MAX;
_maxHeight = CGFLOAT_MAX;
_minWidth = CGFLOAT_MIN;
_minHeight = CGFLOAT_MIN;
_reassignWidth = NO;
_reassignHeight = NO;
_minWidth = -1;
_minHeight = -1;
}
return self;
}
@ -226,7 +222,7 @@ - (CGSize)removeMargin:(CGSize)targetSize {
targetSize.height - self.marginTop - self.marginBottom);
}
- (BOOL)restrainSize {
- (BOOL)restrain:(CGSize)limit {
BOOL needRemeasure = NO;
if (self.measuredWidth > self.maxWidth) {
self.measuredWidth = self.maxWidth;
@ -244,6 +240,17 @@ - (BOOL)restrainSize {
self.measuredHeight = self.minHeight;
needRemeasure = YES;
}
if (self.measuredWidth > limit.width && !self.hasWidthWeight && !self.inScrollable) {
self.measuredWidth = MIN(limit.width, self.measuredWidth);
needRemeasure = YES;
}
if (self.measuredHeight > limit.height && !self.hasHeightWeight && !self.inScrollable) {
self.measuredHeight = MIN(limit.height, self.measuredHeight);
needRemeasure = YES;
}
return needRemeasure;
}
@ -254,174 +261,208 @@ - (BOOL)rect:(CGRect)rect1 equalTo:(CGRect)rect2 {
&& ABS(rect1.size.height - rect2.size.height) < 0.00001f;
}
- (CGSize)removeSizePadding:(CGSize)size {
return CGSizeMake([self removeWidthPadding:size.width],
[self removeHeightPadding:size.height]);
}
- (CGFloat)removeWidthPadding:(CGFloat)size {
return size - self.paddingLeft - self.paddingRight;
}
- (CGFloat)removeHeightPadding:(CGFloat)size {
return size - self.paddingTop - self.paddingBottom;
}
- (CGFloat)addWidthPadding:(CGFloat)size {
return size + self.paddingLeft + self.paddingRight;
}
- (CGFloat)addHeightPadding:(CGFloat)size {
return size + self.paddingTop + self.paddingBottom;
}
- (bool)needFitWidth {
return self.widthSpec == DoricLayoutFit
|| (self.widthSpec == DoricLayoutMost && self.superLayout.needFitWidth)
|| (self.widthSpec == DoricLayoutJust && self.hasWidthWeight);
}
- (bool)needFitHeight {
return self.heightSpec == DoricLayoutFit
|| (self.heightSpec == DoricLayoutMost && self.superLayout.needFitHeight)
|| (self.heightSpec == DoricLayoutJust && self.hasHeightWeight);
}
- (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 measureSelf
- (void)measureSelf:(CGSize)targetSize {
CGFloat width;
CGFloat height;
- (void)measureSelf:(CGSize)remainingSize {
CGFloat limitWidth = remainingSize.width;
CGFloat limitHeight = remainingSize.height;
if (self.mostWidth) {
if (self.superFitWidth) {
width = targetSize.width;
} else {
width = self.measuredWidth = targetSize.width;
if (self.inScrollable) {
if (self.fitWidth) {
limitWidth = CGFLOAT_MAX;
}
} else if (self.justWidth) {
if (self.fitHeight) {
limitHeight = CGFLOAT_MAX;
}
}
[self measureSelf:remainingSize
limitTo:CGSizeMake(limitWidth, limitHeight)];
}
- (void)measureSelf:(CGSize)remainingSize limitTo:(CGSize)limitSize {
CGFloat remainingWidth = remainingSize.width;
CGFloat remainingHeight = remainingSize.height;
CGFloat limitWidth = limitSize.width;
CGFloat limitHeight = limitSize.height;
if (self.justWidth && !self.hasWidthWeight) {
remainingWidth = limitWidth = self.width;
}
if (self.justHeight && !self.hasHeightWeight) {
remainingHeight = limitHeight = self.height;
}
[self measureSelf:CGSizeMake(remainingWidth, remainingHeight)
limitTo:CGSizeMake(limitWidth, limitHeight)
restrain:YES];
}
- (void)measureSelf:(CGSize)remainingSize limitTo:(CGSize)limitSize restrain:(bool)needRestrain {
[self measureContent:[self removeSizePadding:remainingSize]
limitTo:[self removeSizePadding:limitSize]];
if (self.needFitWidth) {
if ([self.view isKindOfClass:[UIImageView class]]
&& self.heightSpec != DoricLayoutFit && self.contentHeight > 0) {
self.measuredWidth = self.contentWidth / self.contentHeight * self.measuredHeight
+ self.paddingLeft + self.paddingRight;
} else {
self.measuredWidth = self.contentWidth
+ self.paddingLeft + self.paddingRight
+ (self.justWidth ? self.width : 0);
}
} else if (self.mostWidth) {
self.measuredWidth = remainingSize.width;
} else {
self.measuredWidth = self.width;
if (self.hasWidthWeight) {
width = targetSize.width;
} else {
width = self.width;
}
} else {
if (self.inScrollable) {
width = CGFLOAT_MAX;
} else {
width = targetSize.width;
}
}
if (self.mostHeight) {
if (self.superFitHeight) {
height = targetSize.height;
if (self.needFitHeight) {
if ([self.view isKindOfClass:[UIImageView class]]
&& self.widthSpec != DoricLayoutFit && self.contentHeight > 0) {
self.measuredHeight = self.contentHeight / self.contentWidth * self.measuredWidth
+ self.paddingLeft + self.paddingRight;
} else {
height = self.measuredHeight = targetSize.height;
}
} else if (self.justHeight) {
if (self.hasHeightWeight) {
height = targetSize.height;
} else {
height = self.height;
self.measuredHeight = self.contentHeight
+ self.paddingTop + self.paddingBottom
+ (self.justHeight ? self.height : 0);
}
} else if (self.mostHeight) {
self.measuredHeight = remainingSize.height;
} else {
self.measuredHeight = self.height;
} else {
if (self.inScrollable) {
height = CGFLOAT_MAX;
} 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)];
if (needRestrain && [self restrain:limitSize] && self.layoutType != DoricUndefined) {
CGSize size = [self removeSizePadding:CGSizeMake(
self.measuredWidth,
self.measuredHeight)];
[self measureSelf:size limitTo:size restrain:NO];
}
if (self.measuredWidth > width && !self.hasWidthWeight) {
self.measuredWidth = MIN(width, self.measuredWidth);
}
if (self.measuredHeight > height && !self.hasHeightWeight) {
self.measuredHeight = MIN(height, self.measuredHeight);
}
[self restrainSize];
}
#pragma measureContent
- (void)measureContent:(CGSize)targetSize {
self.reassignWidth = NO;
self.reassignHeight = NO;
- (void)measureContent:(CGSize)remaining limitTo:(CGSize)limit {
switch (self.layoutType) {
case DoricStack: {
[self measureStackContent:targetSize];
[self measureStackContent:remaining limitTo:limit];
break;
}
case DoricVLayout: {
[self measureVLayoutContent:targetSize];
[self measureVLayoutContent:remaining limitTo:limit];
break;
}
case DoricHLayout: {
[self measureHLayoutContent:targetSize];
[self measureHLayoutContent:remaining limitTo:limit];
break;
}
default: {
[self measureUndefinedContent:targetSize];
[self measureUndefinedContent:remaining];
break;
}
}
if ((self.superFitWidth || self.superLayout.hasWidthWeight) && self.mostWidth) {
self.measuredWidth = self.contentWidth + self.paddingLeft + self.paddingRight;
}
if ((self.superFitHeight || self.superLayout.hasHeightWeight) && self.mostHeight) {
self.measuredHeight = self.contentHeight + self.paddingTop + self.paddingBottom;
}
if (self.superFitWidth
&& self.hasWidthWeight
&& self.justWidth) {
self.measuredWidth = self.contentWidth + self.paddingLeft + self.paddingRight + self.width;
self.reassignWidth = YES;
}
if (self.superFitHeight
&& self.hasHeightWeight
&& self.justHeight) {
self.measuredHeight = self.contentHeight + self.paddingTop + self.paddingBottom + self.height;
self.reassignHeight = YES;
}
}
- (void)measureUndefinedContent:(CGSize)targetSize {
CGSize measuredSize = [self.view sizeThatFits:targetSize];
if (self.fitWidth) {
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.fitHeight) {
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;
}
- (void)measureStackContent:(CGSize)targetSize {
- (void)measureStackContent:(CGSize)remaining limitTo:(CGSize)limit {
CGFloat contentWidth = 0, contentHeight = 0;
for (__kindof UIView *subview in self.view.subviews) {
DoricLayout *layout = subview.doricLayout;
if (layout.disabled) {
continue;
}
[layout measureSelf:[layout removeMargin:targetSize]];
CGSize childRemaining = [layout removeMargin:remaining];
CGSize childLimit = [layout removeMargin:limit];
[layout measureSelf:childRemaining limitTo:childLimit];
contentWidth = MAX(contentWidth, layout.takenWidth);
contentHeight = MAX(contentHeight, layout.takenHeight);
}
if (self.fitWidth) {
self.measuredWidth = contentWidth + self.paddingLeft + self.paddingRight;
}
if (self.fitHeight) {
self.measuredHeight = contentHeight + self.paddingTop + self.paddingBottom;
}
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
}
- (void)measureVLayoutContent:(CGSize)targetSize {
- (void)measureVLayoutContent:(CGSize)remaining limitTo:(CGSize)limit {
CGFloat contentWidth = 0, contentHeight = 0, contentWeight = 0;
BOOL had = NO;
for (__kindof UIView *subview in self.view.subviews) {
@ -430,9 +471,14 @@ - (void)measureVLayoutContent:(CGSize)targetSize {
continue;
}
had = YES;
[layout measureSelf:[layout removeMargin:CGSizeMake(
targetSize.width,
layout.weight > 0 ? targetSize.height : targetSize.height - contentHeight)]];
CGSize childRemaining = [layout removeMargin:CGSizeMake(
remaining.width,
layout.hasHeightWeight ? remaining.height : remaining.height - contentHeight)];
CGSize childLimit = [layout removeMargin:CGSizeMake(
limit.width,
layout.hasHeightWeight ? limit.height : limit.height - contentHeight)];
[layout measureSelf:childRemaining limitTo:childLimit];
contentWidth = MAX(contentWidth, layout.takenWidth);
contentHeight += layout.takenHeight + self.spacing;
contentWeight += layout.weight;
@ -443,7 +489,7 @@ - (void)measureVLayoutContent:(CGSize)targetSize {
}
if (contentWeight > 0 && !self.fitHeight) {
CGFloat remaining = targetSize.height - contentHeight;
CGFloat extra = remaining.height - contentHeight;
contentWidth = 0;
contentHeight = 0;
had = NO;
@ -453,13 +499,14 @@ - (void)measureVLayoutContent:(CGSize)targetSize {
continue;
}
had = YES;
CGFloat measuredHeight = layout.measuredHeight + remaining / contentWeight * layout.weight;
CGFloat measuredHeight = layout.measuredHeight + extra / 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;
CGSize childRemaining = CGSizeMake(
[layout removeWidthPadding:layout.measuredWidth],
[layout removeHeightPadding:measuredHeight]);
[layout measureContent:childRemaining limitTo:childRemaining];
contentWidth = MAX(contentWidth, layout.takenWidth);
contentHeight += layout.takenHeight + self.spacing;
}
@ -468,20 +515,12 @@ - (void)measureVLayoutContent:(CGSize)targetSize {
}
}
if (self.fitWidth) {
self.measuredWidth = contentWidth + self.paddingLeft + self.paddingRight;
}
if (self.fitHeight) {
self.measuredHeight = contentHeight + self.paddingTop + self.paddingBottom;
}
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
}
- (void)measureHLayoutContent:(CGSize)targetSize {
- (void)measureHLayoutContent:(CGSize)remaining limitTo:(CGSize)limit {
CGFloat contentWidth = 0, contentHeight = 0, contentWeight = 0;;
BOOL had = NO;
for (__kindof UIView *subview in self.view.subviews) {
@ -490,9 +529,13 @@ - (void)measureHLayoutContent:(CGSize)targetSize {
continue;
}
had = YES;
[layout measureSelf:[layout removeMargin:CGSizeMake(
layout.weight > 0 ? targetSize.width : targetSize.width - contentWidth,
targetSize.height)]];
CGSize childRemaining = [layout removeMargin:CGSizeMake(
layout.hasWidthWeight ? remaining.width : remaining.width - contentWidth,
remaining.height)];
CGSize childLimit = [layout removeMargin:CGSizeMake(
layout.hasWidthWeight ? limit.width : limit.width - contentWidth,
limit.height)];
[layout measureSelf:childRemaining limitTo:childLimit];
contentWidth += layout.takenWidth + self.spacing;
contentHeight = MAX(contentHeight, layout.takenHeight);
contentWeight += layout.weight;
@ -503,7 +546,7 @@ - (void)measureHLayoutContent:(CGSize)targetSize {
}
if (contentWeight > 0 && !self.fitWidth) {
CGFloat remaining = targetSize.width - contentWidth;
CGFloat extra = remaining.width - contentWidth;
contentWidth = 0;
contentHeight = 0;
had = NO;
@ -513,13 +556,13 @@ - (void)measureHLayoutContent:(CGSize)targetSize {
continue;
}
had = YES;
CGFloat measuredWidth = layout.measuredWidth + remaining / contentWeight * layout.weight;
CGFloat measuredWidth = layout.measuredWidth + extra / 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;
CGSize childRemaining = CGSizeMake(
[layout removeWidthPadding:measuredWidth],
[layout removeHeightPadding:layout.measuredHeight]);
[layout measureContent:childRemaining limitTo:childRemaining];
contentWidth += layout.takenWidth + self.spacing;
contentHeight = MAX(contentHeight, layout.takenHeight);
}
@ -528,16 +571,7 @@ - (void)measureHLayoutContent:(CGSize)targetSize {
}
}
if (self.fitWidth) {
self.measuredWidth = contentWidth + self.paddingLeft + self.paddingRight;
}
if (self.fitHeight) {
self.measuredHeight = contentHeight + self.paddingTop + self.paddingBottom;
}
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
}
@ -643,10 +677,10 @@ - (void)layoutStack {
if (layout.disabled) {
continue;
}
if ((self.fitWidth || self.reassignWidth) && layout.mostWidth) {
if (self.needFitWidth && layout.mostWidth) {
layout.measuredWidth = self.contentWidth - layout.marginLeft - layout.marginRight;
}
if ((self.fitHeight || self.reassignHeight) && layout.mostHeight) {
if (self.needFitHeight && layout.mostHeight) {
layout.measuredHeight = self.contentHeight - layout.marginTop - layout.marginBottom;
}
[layout layout];
@ -702,7 +736,7 @@ - (void)layoutVLayout {
if (layout.disabled) {
continue;
}
if ((self.fitWidth || self.reassignWidth) && layout.mostWidth) {
if (self.needFitWidth && layout.mostWidth) {
layout.measuredWidth = self.contentWidth - layout.marginLeft - layout.marginRight;
}
[layout layout];
@ -745,7 +779,7 @@ - (void)layoutHLayout {
continue;
}
if ((self.fitHeight || self.reassignHeight) && layout.mostHeight) {
if (self.needFitHeight && layout.mostHeight) {
layout.measuredHeight = self.contentHeight - layout.marginTop - layout.marginBottom;
}