详解UICoordinateSpace和UIScreen在iOS 8上的坐标问题
上一文为大家带来了UIView中关于AutoLayout API的学习笔记,之后我在总结UIViewController关于AutoLayout API的时候,发现了一个坑,先拿出来分享给大家。
我分别在iOS 7 和iOS 8下运行了如下代码:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
BOOL landscape = (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight);
NSLog(@"Currently landscape: %@, width: %.2f, height: %.2f",
(landscape ? @"Yes" : @"No"),
[[UIScreen mainScreen] bounds].size.width,
[[UIScreen mainScreen] bounds].size.height);
发现在iOS 8下运行结果如下:
Currently landscape: No, width: 320.00, height: 568.00
Currently landscape: Yes, width: 568.00, height: 320.00
iOS 7运行结果如下:
Currently landscape: No, width: 320.00, height: 568.00
Currently landscape: Yes, width: 320.00, height: 568.00
UIScreen的bounds随着设备的旋转在iOS 8的情况下发生了变化。之后找了资料(具体请参考 WWDC 2014 View Controller Advancements in iOS 8这一期节目中关于Screen Coordinates
),发现UIScreen在iOS 8中也是进行旋转的,具体有四点改变如下:
- [UIScreen bounds] -> interface-oriented
- [UIScreen applicationFrame] -> interface-oriented
- status bar frame notifications -> interface-oriented
- keyboard frame notificaitons -> interface-oriented
在iOS 8中苹果认为设备的旋转就是当前Screen的Bounds发生了变化,因此引入了Size Class的概念。苹果使用UITraitCollection来描述不同Size Class的信息。当TraitCollection改变时(设备发生了旋转),上一篇文章最后讲到的 - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
将会被调用。首先被触发的就是我们的UIScreen,然后逐层往下传递,具体如下:
UIScreen -> UIWindow -> UIViewController -> ChildViewControllers -> View -> Subviews
因此我们在处理上述几种情况的时候,应该留意iOS 8和iOS 8之前版本的不同,而进行区别编码。
这里分享给大家获得修正过后Screen size的方法(支持新老版本):
+ (CGSize)fixedScreenSize {
CGSize screenSize = [UIScreen mainScreen].bounds.size;
if ((NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
return CGSizeMake(screenSize.height, screenSize.width);
} else {
return screenSize;
}
}
UICoordinateSpace protocol
iOS 8之前,window和screen的坐标是不变的。它们不会进行旋转,而是一直处于竖屏时的坐标体系。我们是在View Controller旋转方法中做对应的处理。
iOS8之后,像上文描述的那样,window和screen的坐标会随着app旋转而改变。也不总是这样的情况,因为View Controller仍然决定了App在当前视图下支持哪几个方向。
在某些时候,我们需要一些修正之后的frame(比如需要存储touch事件在屏幕上的坐标),这个时候新的UICoordinateSpace
就登场了。
@protocol UICoordinateSpace <NSObject>
- (CGPoint)convertPoint:(CGPoint)point toCoordinateSpace:(id <UICoordinateSpace>)coordinateSpace NS_AVAILABLE_IOS(8_0);
- (CGPoint)convertPoint:(CGPoint)point fromCoordinateSpace:(id <UICoordinateSpace>)coordinateSpace NS_AVAILABLE_IOS(8_0);
- (CGRect)convertRect:(CGRect)rect toCoordinateSpace:(id <UICoordinateSpace>)coordinateSpace NS_AVAILABLE_IOS(8_0);
- (CGRect)convertRect:(CGRect)rect fromCoordinateSpace:(id <UICoordinateSpace>)coordinateSpace NS_AVAILABLE_IOS(8_0);
@property (readonly, nonatomic) CGRect bounds NS_AVAILABLE_IOS(8_0);
@end
UICoordinateSpace协议的接口如上,使用这些接口我们可以从当前视图的坐标转化到在screen的坐标体系当中,像这样:
[myView convertPoint:point toCoordinateSpace:myView.window.screen.fixedCoordinateSpace];
这里提到了fixedCoordinateSpace
,在iOS 8上UIScreen提供了两个新的属性,如下:
@property (readonly) id <UICoordinateSpace> coordinateSpace NS_AVAILABLE_IOS(8_0);
@property (readonly) id <UICoordinateSpace> fixedCoordinateSpace NS_AVAILABLE_IOS(8_0);
coordinateSpace
值的是当前screen旋转之后的坐标体系,fixedCoordinateSpace
指的是修正之后的坐标体系。我们同样可以把screen的坐标转换到当前视图的坐标体系,如下:
[myView.window.screen.fixedCoordinateSpace convertPoint:point toCoordinateSpace:myView];
举个例子来说,在iPhone 5的屏幕大小上(320 * 568),myView在竖屏方向的frame是CGRectMake(50, 50, 200, 200),myView的Super View是window。有如下输出结果:
CGPoint point = [myView convertPoint:CGPointMake(0, 0) toCoordinateSpace:[UIScreen mainScreen].fixedCoordinateSpace];
NSLog(@"fixedCoordinateSpace %@", NSStringFromCGPoint(point)); // {50, 50}
point = [myView convertPoint:CGPointMake(0, 0) toCoordinateSpace:[UIScreen mainScreen].coordinateSpace];
NSLog(@"coordinateSpace %@", NSStringFromCGPoint(point)); // {50, 50}
此时我们把设备顺时针旋转90度,在这两种不同的坐标体系下分别输出的结果是:
NSLog(@"fixedCoordinateSpace %@", NSStringFromCGPoint(point)); // {50, 518}
NSLog(@"coordinateSpace %@", NSStringFromCGPoint(point)); // {50, 50}
总结
我们在编码过程中,应该注意这些坐标的改变。尤其要注意不同iOS版本下,对不同行为做相应处理。顺便祈祷公司早点不支持旧版本操作系统吧。