存在的问题
这种适配问题一直存在于iOS开发中,通常的解决方案有:
- 基于代码的布局,通过判断硬件来添加合适约束
- 基于xib的布局,给对应的控件的大小和位置添加NSLayoutConstraint outlet变量,通过判断不同的硬件来修改对应的约束。
- 实现多套xib文件,根据硬件来加载不同的xib文件
- 在xib文件中所有的约束都是基于百分比实现
- 只设置一张启动图,不同硬件都基于张图进行拉伸处理
1、2、3的实现都很繁琐,大量重复性工作。4实现应该是没问题,主要当前视觉给的图基本都是基于像素点的,做的时候需要将像素点转换成对应的百分比,需要做一步计算,很不直观。5的方法会造成在大屏幕上进行了模糊拉伸,显示会比较模糊,影响效果。
我的解决方案
原理是:视图的绘制会调用layoutSubviews
,我们通过在此方法中修改所有view的constraints,以适配当前的设备。
寻找合适的约束值(constraints
)
设备 | 宽 | 高 | 对角线 | 逻辑分辨率 | scale factor | 设备分辨率 | PPI | 高宽比 |
---|---|---|---|---|---|---|---|---|
4(s) | 2.31 inches | 4.5 inches | 3.5 inch | 320x480 | @2x | 640x960 | 326 | 1.5 |
5c | 2.33 inches | 4.9 inches | 4 inch | 320x568 | @2x | 640x1136 | 326 | 1.775 |
5(s) | 2.31 inches | 4.87 inches | 4 inch | 320x568 | @2x | 640x1136 | 326 | 1.775 |
6 | 2.64 inches | 5.44 inches | 4.7 inch | 375x667 | @2x | 750x1334 | 326 | 1.779 |
6+ | 3.06 inches | 6.22 inches | 5.5 inch | 414x736 | @3x | 1080x1920 | 401 | 1.778 |
从表中的数据可以得出结论:5(s)、6、6+的宽高比基本相同,可以认为只是进行了等比放缩,因此适配的时候只需要对view的尺寸进行对比放缩即可。
通常视觉给的图都是以6的尺寸为模板,我们也以6的尺寸为基本尺寸,则有:
5(s)上view 的尺寸应该是6上尺寸的0.853,也就是6的尺寸是5(s)的1.172倍
6+上view 的尺寸应该是6上尺寸的1.104
修改约束
子视图的添加(addSubview
)以及frame
的修改都会调用layoutSubviews
,因此我们可以在layoutSubviews
中修改所有的子视图的NSLayoutConstraints
的值。
- (void)layoutSubviews
{
for (UIView *view in self.subviews) {
for (int i = 0; i < view.constraints.count; ++ i) {
NSLayoutConstraint *con = view.constraints[i];
//获取当前约束值
CGFloat size = con.constant;
//修改为合适的约束值
size = [ZDGAutoSize fitSizeToDevices:size];
con.constant = size;
}
}
}
+(CGFloat) fitSizeToDevices:(CGFloat) length
{
//iphone6比iPhone5系列扩大了1.175倍。
return [self fitSizeToDevices:length withRatio:1.175f];
}
+(CGFloat) fitSizeToDevices:(CGFloat) length withRatio:(CGFloat)ratio
{
CGFloat fitLength = length;
if (![self isDeviceAboveIphone5]) {
fitLength = length / ratio;
}else if([self isDevicePlus]){
fitLength = length * 1.104;
}
return fitLength;
}
批量修改约束
可以看出,所有的修改都在layoutSubviews
函数里面。如果对每一个需要自适应的view
都重写layoutSubviews
方法会非常烦琐,因此我们可以通过runtime
方法来完成对layoutSubviews
方法的hook
,给所有的view
添加上我们的修改函数。
// UIView+AutoSizeToDevice.m
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(layoutSubviews);
SEL swizzledSelector = @selector(dg_layoutSubviews);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)dg_layoutSubviews
{
// Forward to primary implementation.
[self dg_layoutSubviews];
if (self.dg_viewAutoSizeToDevice) {
[self autoSizeToDeviceFromRootView:self];
self.dg_viewAutoSizeToDevice = NO;
}
}
通过hook
操作,我们修改了layoutSubviews
的实现,完成了所有视图约束的修改。
项目中的应用
该方法的源码已上传到github上,下载地址AutoSizeToDevice
使用方法
1.在需要自适应的视图上加上头文件
@import "UIView+AutoSizeToDevice.h"
2.在视图的初始化函数里面设置属性dg_viewAutoSizeToDevice = YES;
示例
1.UIViewController
中使用
@import "UIViewController.h"
@import "UIVew+AutoSizeToDevice.h"
- (void)viewDidLoad()
{
[super viewDidLoad];
self.view.dg_viewAutoSizeToDevice = YES;
}
2.UIView
中使用
@import "UIView.h"
@import "UIView+AutoSizeToDevice.h"
- (void)awakeFromNib
{
self.dg_viewAutoSizeToDevice = YES;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self){
self.dg_viewAutoSizeToDevice = YES;
}
return self;
}