这个问题看图会比较直观
竖屏情况下:
屏幕旋转后:
重新设置为竖屏:
再次旋转为横屏:
下面分析一下View的设置
红色背景productView是一个普通UIView
self.clipsToBounds = YES;
self.backgroundColor = [UIColor redColor];
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
版本Label和人气Label直接add到productView上,使用frame来定位
self.versionLabel = [[UILabel alloc] initWithFrame:CGRectMake(25, 0.f, CGRectGetWidth(self.frame) / 2.0f - 25 - 5.f, 16.f)];
self.versionLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
self.usePersonLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.frame) / 2 + 5.f, CGRectGetMinY(self.versionLabel.frame), CGRectGetWidth(self.frame) / 2.0f - 25 - 5.f, CGRectGetHeight(self.versionLabel.frame))];
self.usePersonLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
可以看到其实预期的效果应该是:
问题出在autoresizingMask的局限性:不能实现等分行为
这就造成了无论怎么组合autoresizingMask的属性,都不可能完美达到这个效果
也因此其实在非iPhoneX上这套实现也是存在瑕疵的(表现和iPhoneX第一次旋转时相同,可以明显看到旋屏之后的错位,但不会出现iPhoneX那种严重的重合问题)
iPhoneX第二次旋屏之后的问题十分严重,autoresizingMask的三项组合导致其frame的计算完全错误,Label完全重合在一起
(开发者Label和更新时间Label同理)
两个问题其实都是因为autoresizingMask,因此想到使用autoLayout 取代autoresizingMask进行布局设置
简单讲一下autoLayout的概念。(网上已经有很多资料了)
我个人理解最重要的一点是:每个view的布局不再由自己决定了,而是变为受到父view和其他view的约束
view的属性=约束view的view的属性*multiplier+constant常量
比如原来的view.frame.origin.x,就可以理解为是view左边界和父view左边界,转化为代码
NSLayoutConstraint * leftConstraint;
leftConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual toItem:self
attribute: NSLayoutAttributeLeft multiplier:1.0 constant:view.frame.origin.x];
那等分宽度也就很好理解了,ratio = 1/等分数(二等分就是0.5)
NSLayoutConstraint * widthConstraint;
widthConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual toItem:self
attribute:NSLayoutAttributeWidth multiplier:ratio constant:0];
有一点要注意不过也很好理解,应该把所有view都加进父view再添加约束。
autoLayout和frame+autoresizingMask的理念完全不同,二者不可以对一个view同时使用,但是嵌套并不会有影响,因此考虑写一个baseview,针对需要等分的view替换其原有的布局方式。
#import <UIKit/UIKit.h>
typedef NS_ENUM(UInt32, FLAlignmentDirection) {
FLAlignmentDirection_Horizontal = 0, //横向
FLAlignmentDirection_Vertical = 1, //纵向
};
@interface FLAutoLayoutBaseView : UIView
@property(nonatomic,assign,readonly) BOOL hadAutolayout;//判断是否全部的subview都进行了autolayout
@property(nonatomic,strong,readonly) NSMutableArray<UIView *> *subviews;
- (instancetype)initWithFrame:(CGRect)frame alignmentDirection:(FLAlignmentDirection)direction;//只允许在init时设置frame
//添加subview 添加顺序决定排列顺序
//view.frame的x,y需要设置
//横向排列时width需要设置 纵向排列时height需要设置
- (void)addSubview:(UIView *)view;
//以下autoLayout方式在addSubView之后调用
- (void)autoLayoutByDefault;//采用默认的约束方式 根据横纵向等分宽度或者高度
@end
把baseview写的尽可能简单,仅提供了等分的功能(实际上还能提供完全替代autoresizingMask的接口)
值得一提的是重写addSubview的行为
- (void)addSubview:(UIView *)view //记录add入FLAutoLayoutBaseView的view 只能按序添加
{
[super addSubview:view];
self.maskHadAutoLayout = [self.maskHadAutoLayout stringByAppendingString:@"0"];
[self.subviews addObject:view];
//检查并处理width,height数据
if(self.alignmentDirection == FLAlignmentDirection_Horizontal) {//横向排列则检查高度
if(0 == CGRectGetHeight(view.frame)) //如果没有高度则设置为self的高度
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, self.frame.size.height);
}
else { //纵向排列则检查宽度
if(0 == CGRectGetWidth(view.frame)) //如果没有宽度则设置为self的宽度
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, self.frame.size.width, view.frame.size.height);
}
if(NO == self.callDefaultLayout) {
self.callDefaultLayout = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self autoLayoutByDefault];
});
}
}
最后一个判断是为了保证“应该把所有view都加进父view再添加约束,但是忘了添加约束”的情况下能够进行默认的autoLayout布局。
总的来说autoLayout不进行封装的话代码量其实会多很多,但是功能也的确比autoresizingMask完善,因此我更加偏向于逐步完善对autoLayout的封装,推广使用autoLayout。