作为iOS UI开发人员,为了适配多种机型以及横竖屏,【布局】的概念是再熟悉不过的,iOS的布局发展到今天主要有以下几种方式:
1.Absolutely Position
2.AutoResizing
3.NSLayoutConstraint
4.UIStackView
为了布局,就要考虑App支持的设备方向和各控件间的位置关系。根据这两个限制条件来讨论下面的技术点:
Absolutely Position
如果控件的位置是可以根据固定的控件可计算的,那么使用绝对位置是没有什么问题的,例如代码:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.bounds = CGRectMake(0, 0, 50, 50);
[self.view addSubview:button];
那么,其他一些控件可以根据这个button的frame属性来动态适配frame
优点:
简单而快速
缺点:
修改比较困难
AutoResizing
这是UIView类的属性,当父控件发生变化,这个属性可以决定子控件如何调整自己的size。它是一个整数位掩码。
它的枚举结构:
typedef enum UIViewAutoresizing : NSUInteger {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
} UIViewAutoresizing;
每一个单词中都有“Flexible”这个单词,意味着,你用了某个枚举,当父控件发生变化之后,子空间对应的属性可以resize self去适配父控件,举例如下:(OC)
self.testAutoSizingMaskParentView = [[UIView alloc] initWithFrame:CGRectMake(105, 205, 100, 100)];
[self.view addSubview:_testAutoSizingMaskParentView];
UIView *testAutoSizingMaskView = [[UIView alloc] initWithFrame:CGRectMake(5, 5, 80, 80)];
testAutoSizingMaskView.backgroundColor = [UIColor grayColor];
// 这表明,当父控件发生变化的时候,允许resize self的宽度和高度以适应父控件的变化
testAutoSizingMaskView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[self.testAutoSizingMaskParentView addSubview:testAutoSizingMaskView];
// iOS 8之后的设备方向发生变化的回调
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// 修改父控件frame
if (size.width > size.height) {
self.testAutoSizingMaskParentView.frame = CGRectMake(105, 205, 50, 60);
} else {
self.testAutoSizingMaskParentView.frame = CGRectMake(105, 205, 100, 100);
}
}
NSLayoutConstraint
这个是iOS6之后的新特性,约束,定义两个用户界面对象之间必须满足基于约束的布局系统的关系
使用Layout布局的前提要关闭控件的translatesAutoresizingMaskIntoConstraints属性,以防止系统将autoresizing mask转换到Auto Layout constraints,从而让布局出现问题。
使用NSLayoutConstraint有两种方式:
1.VFL(Visual Formate Language)
+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary<NSString *,id> *)metrics views:(NSDictionary<NSString *,id> *)views;
功能 表达式
水平方向 H:
垂直方向 V:
Views [view]
SuperView |
关系 >=,==,<=
间隔 -
优先级 @value
//这个地方不要定义Frame,下面的Visual Format才是定义Frame相关的地方
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
//为了不让Constraint与View本身的autoresize冲突
[redView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:redView];
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
[blueView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:blueView];
//绑定两个View相关
NSDictionary *views = NSDictionaryOfVariableBindings(redView, blueView);
//此处的constraint是为了定义Frame水平方向相关(x, width)
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=10)-[redView(200)]" options:0 metrics:nil views:views]];
//此处的constraint是为了定义Frame竖直方向相关(y, height)
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=240)-[redView(100)]" options:0 metrics:nil views:views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:[blueView(==redView)]" options:0 metrics:nil views:views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(40)-[blueView(==redView)]" options:0 metrics:nil views:views]];
// 实现子空间居中
NSDictionary *dic = @{@"centerLabel":centerLabel, @"superView":label};
[label addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[centerLabel(10)]-(<=1)-[superView]" options:NSLayoutFormatAlignAllCenterY metrics:nil views:dic]];
[label addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[centerLabel(10)]-(<=1)-[superView]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:dic]];
// 快速实现关联关系的控件的大小一样
[label(button)],这表明label的大小与button一样
2.指定属性关系
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;
这个API实际就是一个数学公式:
view1.attr1 <relation> multiplier × view2.attr2 + c
其中支持Layout的属性的枚举如下:
typedef enum NSLayoutAttribute : NSInteger {
NSLayoutAttributeLeft = 1,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeLastBaseline,
NSLayoutAttributeBaseline = NSLayoutAttributeLastBaseline,
NSLayoutAttributeFirstBaseline,
NSLayoutAttributeLeftMargin,
NSLayoutAttributeRightMargin,
NSLayoutAttributeTopMargin,
NSLayoutAttributeBottomMargin,
NSLayoutAttributeLeadingMargin,
NSLayoutAttributeTrailingMargin,
NSLayoutAttributeCenterXWithinMargins,
NSLayoutAttributeCenterYWithinMargins,
NSLayoutAttributeNotAnAttribute = 0
} NSLayoutAttribute;
其中支持Layout的Relation的枚举如下:
typedef enum NSLayoutRelation : NSInteger {
NSLayoutRelationLessThanOrEqual = -1, // <=
NSLayoutRelationEqual = 0, // ==
NSLayoutRelationGreaterThanOrEqual = 1 // >=
} NSLayoutRelation;
UIStackView
为UI提供了线性布局,不管是以行Or列的形式,很像Android的布局类。可以将其看作一个布局类。增强了Auto Layout的能力.
通过Arranged Subviews来管理控件,通过设置 axis(坐标轴,水平或者是垂直布局), distribution(分布方式,类似填充样式), alignment, spacing,isLayoutMarginsRelativeArrangement,isBaselineRelativeArrangement属性来实现布局的控制。
举例如下:(swift)
class ULable: UILabel {
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize.init(width: 100, height: 100)
}
}
let label1 = ULable()
label1.textAlignment = .center
label1.sizeToFit()
label1.text = "1111111"
label1.backgroundColor = UIColor.brown
let label2 = UILabel()
label2.text = "22222222"
label2.backgroundColor = UIColor.yellow
let sv1 = UIStackView(arrangedSubviews: [label1,label2])
sv1.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 200)
sv1.axis = .vertical
sv1.spacing = 10
sv1.alignment = .center
sv1.distribution = .fillEqually
self.view.addSubview(sv1)
补充:
也可以使用如下方式实现控件的自适应,但不推荐:
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
+ (void)autoLayoutView:(UIView *)view;
@end
#import "AppDelegate.h"
#define kDeviceScreenWidth [[UIScreen mainScreen] bounds].size.width
#define kDeviceScreenHeight [[UIScreen mainScreen] bounds].size.height
@interface AppDelegate ()
@property CGFloat autoSizeScaleX;
@property CGFloat autoSizeScaleY;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if (kDeviceScreenHeight > 480) {
self.autoSizeScaleX = kDeviceScreenWidth/320;
self.autoSizeScaleY = kDeviceScreenHeight/568;
} else {
self.autoSizeScaleX = 1.0f;
self.autoSizeScaleY = 1.0f;
}
return YES;
}
+ (void)autoLayoutView:(UIView *)view {
for (UIView *v in view.subviews) {
v.frame = autoLayoutCGRectMake(v.frame.origin.x, v.frame.origin.y, v.bounds.size.width, v.bounds.size.height);
if (v.subviews.count > 0) {
[AppDelegate autoLayoutView:v];
}
}
}
CG_INLINE CGRect autoLayoutCGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
CGRect rect;
rect.origin.x = x*appDelegate.autoSizeScaleX;
rect.origin.y = y*appDelegate.autoSizeScaleY;
rect.size.width = width*appDelegate.autoSizeScaleX;
rect.size.height = height*appDelegate.autoSizeScaleY;
return rect;
}
@end
以上就是iOS的布局方式的汇总,请根据情况使用。