前言
iOS7是目前iOS史上最颠覆的一次改版。
它的出现令人兴奋,因为它将会带我们进入一个全新的时代;
它的到来也让我们忧心,因为它颠覆了一切,包括我们过去做过的很多努力。
但是,相信大家乐意为这个全新时代做更多的努力,因为,它值得期待。
最近打算写一系列针对iOS7新特性的文章,今天就从最基本和简单的页面布局开始吧。
从头说起
当我看见iOS7那个半透明设计的navigationBar,已经有种不详的预感。
因为要透过navigationBar看到底下滚动的内容,那么底下的内容必须从顶部开始布局,并且需要设定相应的内边距以让初始内容显示在合适的位置上。
要对应用适配这种凶残设计,其工作量绝比适配iPhone5屏幕还要多许多。
我马上翻了一下iOS7相关的文档,在《iOS 7 UI Transition Guide》的 Bar and Bar Buttons一节中得到了证实 ——
In iOS 7, the status bar is transparent, and other bars—that is, navigation bars, tab bars, toolbars, search bars, and scope bars—are translucent. As a general rule, you want to make sure that content fills the area behind the bars in your app.
在iOS7中,状态栏是完全透明的,而其他bar,即navigation bars, tab bars, toolbars, search bars和scope bars都是半透明的。开发者需要保证页面内容能覆盖到这些bar的后面。
事实上,iOS7中的状态栏不仅变完全透明了,而且完全不占空间。
有码有真相 —— 新建一个UIViewController,再viewDidLoad里面输入以下代码,作为rootViewController启动应用:
05 |
self .view.backgroundColor = [ UIColor whiteColor]; |
07 |
UILabel *label = [[[ UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 20)]autorelease]; |
08 |
label.text = @ "I am a label" ; |
09 |
[ self .view addSubview:label]; |
应用效果:
可以看到的是label和status bar悲催地重叠了。
我们再套一个UINavigationController,可以看到更悲催的事情:
label活生生地被navigationBar盖住了。
可以说,苹果这次在iOS7上的redesign对开发者来说是惨绝人寰的。
不过苹果还是有节操的,在iOS7上运行iOS7 SDK以下开发的应用时,保留了原先的页面结构布局,并且做了不少向下兼容策略。
而且,iOS7 SDK提供了一系列接口和策略方案,下文将会一一介绍并顺带剖析一下iOS7上的页面结构框架。
Realtime Debug Protal
在讨论如何应对凶残的iOS7之前,我首先介绍一个自己之前捣腾的小工具,可以方便我们进行学习。
它的小名叫RDP,是一个类似Web Inspector的工具,把这个工具引入我们的项目工程,并做一些简单的配置,然后运行真机或者模拟器。
应用启动后,在浏览器输入手机的IP地址,就可以看到UIView的树状结构和Log信息,还可以在浏览器中对View进行移动,隐藏,选中高亮等操作。
这是iOS7上面的页面结构,对比一下iOS6和iOS5的,就这样已经可以看出区别来了。
不过今天的重点不是横向对比,而是在iOS7上的纵向对比,请看下文。
状态栏
在iOS7中,状态栏是透明的,就是说,状态栏只有文字没有背景。
这个改动让我颇为意外,因为一直印象中苹果很care状态栏的,之前也曾听说过有应用因为遮挡了状态栏而被Appstore拒绝。
而且苹果之前状态栏提供的三种样式都是以深色底白色字呈现,保证了状态栏的内容清晰易读。
而变透明之后就很容易和后面的内容混淆,虽说一般应用不会把内容和状态栏叠合在一起,但是至少,现在的情况是,默认是会叠合的,开发需要从20px像素以下开始布局页面元素才能避免。
苹果为了让深色浅色背景均能让状态栏内容清晰显示,提供两种状态栏样式:
UIStatusBarStyleDefault = 0 黑色文字,浅色背景时使用
UIStatusBarStyleLightCon tent = 1 白色文字,深色背景时使用
而以下两个旧状态栏样式将被废弃:
UIStatusBarStyleBlackTra nslucent = 1
UIStatusBarStyleLightCon tent = 2
还有,iOS7中我们通过ViewController重载方法返回枚举值的方法来控制状态栏的隐藏和样式。
首先,需要在Info.plist配置文件中,增加键:
UIViewControllerBasedStatusBarAppearance,并设置为YES;
然后,在UIViewController子类中实现以下两个方法:
1 |
- ( UIStatusBarStyle )preferredStatusBarStyle |
3 |
return UIStatusBarStyleLightContent ; |
6 |
- ( BOOL )prefersStatusBarHidden |
最后,在需要刷新状态栏样式的时候,调用[self setNeedsStatusBarAppeara nceUpdate]方法即可刷新,若果需要以动画形式切换状态栏样式,则用以下方式调用即可:
1 |
[ UIView animateWithDuration:0. animations:^{ |
2 |
[ self setNeedsStatusBarAppearanceUpdate]; |
导航栏
在iOS7,由于状态栏背景透明,那么,导航栏背景就可能要兼职充当状态栏背景了。
iOS7默认导航栏样式就是这么做的,见下图:
虽然用户看来,iOS7默认样式的状态栏和导航栏时连在一起的,但是实际上导航栏的位置和大小是和之前系统版本一样的,依然是贴在状态栏下面,依然是高44px;之所以用户看来它们是连在一起,这是因为UINavigationBar里面的_UINavigationBarBackgroun d定位在y方向-20px的位置,然后高度增加到64px,这样就可以同时充当了两者的背景。
关于这些定位,苹果做了很多工作,后面也会谈到不少。不关心的同学可以略过,其实这些细节,个人觉得,即使对于开发者来说,也不是必需知道的,我们只需要知道怎么调用相关API就足够了。
实际情况下,我们会自定义导航栏背景,过去,我们也许会使用如下代码把一张高44像素(retina/88像素)的图片来平铺作为导航栏背景。
1 |
[navCtrl.navigationBar setBackgroundImage:[ UIImage imageNamed:@ "nav_background" ] forBarMetrics: UIBarMetricsDefault ]; |
启动应用,出现了意想不到的效果和久违的界面 —— 黑底白字的状态栏,不再被navigationBar盖住的label。
这里两个点需要解释一下:
- 若我们使用自定义图片作为导航栏的背景,那么UIViewController的view(下面称为视图)就不会延伸到navigationBar的顶部,而是从它的底部开始——正如往常一样。
- 若我们使用一张高44像素(retina/88像素)的图片作为导航栏背景,那么状态栏就会保持黑色,图片只会在导航栏区域平铺。
另外,iOS7 SDK中新增了一个设置背景图片的方法(setBackgroundImage:forBarPosition:barMetrics:),比原有的方法多了一个UIBarPosition枚举参数,用于设置背景图片拉伸的策略。
针对不同的拉伸设置和背景图片尺寸,在《iOS 7 UI Transition Guide》的 Bar and Bar Buttons一节中
中有详细说明:
PS:原文中的"resize"翻译为“调整”,表述比较含糊,根据实际操作结果看,水平方向调整一般是平铺,垂直方向调整一般是局部拉伸。
页面布局
在 《iOS 7 UI Transition Guide》的 Layout and Appearance 一节中也提到 —— 在iOS7中,view controllers使用全屏布局 (In iOS 7, view controllers use full-screen layout)。
通过上面的讨论我们也知道,除非导航栏设置了自定义的背景图片,否则每个视图都会延伸到屏幕一样大小的。
所以,像上面第二张图片中出现导航栏遮盖label的情况也是正常的现象。
如果我们要让label从导航栏以下位置显示,可以通过修改UIViewController的
edgesForExtendedLayout这个属性来实现。
edgesForExtendedLayout是一个类型为UIExtendedEdge的属性,指定边缘要延伸的方向。
因为iOS7鼓励全屏布局,它的默认值很自然地是UIRectEdgeAll,四周边缘均延伸,就是说,如果即使视图中上有navigationBar,下有tabBar,那么视图仍会延伸覆盖到四周的区域。
如果把视图做如下设置,那么视图就不会延伸到这些bar的后面了,于是label又出来了。
1 |
self .edgesForExtendedLayout = UIExtendedEdgeNone ; |
也许,这时候你会想,那为什么不把UIExtendedEdgeNone作为默认态呢?
iOS7鼓励全屏,它希望用户在使用可滚动视图的时候可以透过半透明的bar还可以看到一些模模糊糊的内容。
为了保持设计的优雅,同时避免给开发者太多的困扰,iOS7在Conttoller中新增了这个属性:
automaticallyAdjustsScrollViewInsets,当设置为YES时(默认YES),如果视图里面存在唯一一个UIScrollView或其子类View,那么它会自动设置相应的内边距,这样可以让scroll占据整个视图,又不会让导航栏遮盖,如以下例子:
要注意的是,这个例子中我们没有设置edgesForExtendedLayout,即视图是延伸至全屏的。
我们可以从UIView树状图看到,tableview的bounds值中有64像素的偏移值,它作为一个内边距来保持内容显示在导航栏以下,而滚动时仍可以透过半透明的导航栏看到模糊的内容。
最后一个介绍的新属性是
extendedLayoutIncludesOpaqueBars,这个属性指定了当Bar使用了不透明图片时,视图是否延伸至Bar所在区域,默认值时NO。
所以我们如果自定义了导航栏的背景图片,那么视图会从导航栏以下开始,不会延伸到导航栏区域。
如果把这个属性设置为YES,那么视图将会延伸至导航栏区域,即使我们把导航栏设置成了自定义背景,如下图:
视图延伸之后,label又被导航栏覆盖住了,正如我们意料。
好了,这次就介绍到这里了。
下次我们讨论一下如何写兼容iOS7和iOS5、6的页面布局。
self.edgesForExtendedLayout = UIRectEdgeNone; self.automaticallyAdjustsScrollViewInsets =NO;
OR
self.edgesForExtendedLayout = UIRectEdgeNone; self.extendedLayoutIncludesOpaqueBars =YES;