iOS7之后的NavigationBar、Status Bar、Container View Controllers

本文为大地瓜原创,欢迎知识共享,转载请注明出处。
虽然你不注明出处我也没什么精力和你计较。
作者微信号:christgreenlaw


Overall

本文关注的是状态栏(Status Bar,就是那个显示时间、信号、电量的栏)遮盖应用的问题(其实也就是苹果推出的沉浸式设计,即状态栏下也是应用的一部分,而以前传统的状态栏是不显示应用内容的)。严格来说,这是苹果的新设计,不能叫做问题。但有的时候你的产品经理或者美工就是希望你做出一个黑色的状态栏来,而不让应用在状态栏下显示。。

本文同时也解答了一些关于status bar的疑难点。
建议阅读时,尽量跟着大地瓜一起操作一遍,以加深理解。

英文引用的内容来自Stack OverFlow链接,原作者为jaredsinclair
斜体字 是大地瓜为大家翻译的中文啦!

正文来啦

This is cross-posted from a blog post I wrote, but here is the full rundown on status bars, navigation bars, and container view controllers on iOS 7:

  1. There is no way to preserve the iOS 6 style status bar layout. The status bar will always overlap your application on iOS 7.

你无法继续使用iOS 6风格的status bar布局。在iOS 7上,status bar永远都会遮盖你的应用。

  1. Do not confuse status bar appearance with status bar layout. The appearance (light or default) does not affect how the status bar is laid out (frame/height/overlap). It is important to note as well that the system status bar no longer has any background color. When the API refers to UIStatusBarStyleLightContent, they mean white text on a clear background. UIStatusBarStyleDefault is black text on a clear background.

不要把status bar的appearance和status bar的layout搞混了。appearance(light 或者 default)不会影响status bar的布局方式(frame/height/overlap)。重要的是同时要注意系统的status bar不再有任何的背景色了。当API里提到UIStatusBarStyleLightContent的时候,它的意思是指透明背景上有白字(的样式)。UIStatusBarStyleDefault是透明背景上黑字(的样式)。

绕得慌吗?大地瓜给你解释一下

第二点的意思就是说,status bar 的appearance有两种,一个是UIStatusBarStyleLightContent(白字),另一个是UIStatusBarStyleDefault(黑字)。不论你给appearance设置这个枚举量的哪一个值,status bar的背景永远是透明的,你修改的只是上面文字的颜色而已。其实这里变化的颜色是status bar上的全部内容,包括左侧的信号格、中间的时间、右侧的电量等等,凡是在status bar上显示的内容的颜色都是受appearance属性影响的。

  1. Status bar appearance is controlled along one of two mutually-exclusive basis paths: you can either set them programmatically in the traditional manner, or UIKit will update the appearance for you based on some new properties of UIViewController. The latter option is on by default. Check your app’s plist value for “ViewController-Based Status Bar Appearance” to see which one you’re using. If you set this value to YES, every top-level view controller in your app (other than a standard UIKit container view controller) needs to override preferredStatusBarStyle, returning either the default or the light style. If you edit the plist value to NO, then you can manage the status bar appearance using the familiar UIApplication methods.

status bar appearance的控制有两种方式,使用其中一个就可以:要么你用传统方式使用代码来设置,要么UIKit会根据UIViewController的一些新属性来为你更新appearance。默认是使用后者的。检查你的应用的"ViewController-Based Status Bar Appearance"对应的plist值以确定你用的是哪种方式。如果你这个值为YES,应用中的每一个顶级view controller(除了标准的UIKit container view controller以外)都需要重写preferredStatusBarStyle,返回default或者light style中的一个,如果这个值设置为NO,那么你可以使用熟悉的UIApplication方法来设置status bar appearance。

大地瓜的尝试

新建一个SingleView的工程就好。

上面说要在plist文件中检查ViewController-Based Status Bar Appearance,但其实这个属性其实在plist文件中默认是不显示的。那么到底是怎么回事呢?

我们先不去找这个属性,我们直接在AppDelegate如下设置:

    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;

运行后状态栏内容文字是黑的。也就是说,我们的设置并没有起作用。
我们反过来看上文的话,默认是使用后者的。那么我们现在什么都没做,也就是说我们现在就应该是UIKit会根据UIViewController的一些新属性来为你更新appearance

接下来在ViewController中,重写preferredStatusBarStyle方法

- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}

成功了!status bar的内容变为了白色!

那么,我们得出第一点结论:
默认情况下(在不修改此属性的情况下),需要在你想要改变状态栏的VC中,重写preferredStatusBarStyle方法,才能改变状态栏样式。而对于[UIApplication sharedApplication].statusBarStyle的设置是无效的。

好的,接下来我们找到这个属性。

在此之前,删除刚才在AppDelegate和ViewController中写的全部代码。
然后进入plist文件。


plist文件.png

在此处手动输入View第一个弹出来的就是了。


输入View后就会如此提示

敲回车,如下图。默认就是NO。
我的工程中这个属性默认值.png

那么这个NO到底是什么意思?是它的默认值吗?
不管,直接运行看看是什么效果。(再强调一遍,请删除AppDelegate和ViewController中刚才加入的一切代码,做实验就是要控制变量,认真脸)
我曹!爸爸的状态栏哪去了?在这个属性找到后,Xcode自动提供了NO值的情况下,我们什么代码都不加,状态栏消失了。。。。。
我们其实现在就可以得出一个结论:这个NO虽然是Xcode自动提供给我们的,但它并不是该属性不显示时的默认值!请大声朗读三遍!Xcode提供的属性值不代表是默认值!
恰恰相反,当你在plist文件中找出一个本来不显示的值时,Xcode会认为你要修改默认值,所以给你另外一个可能的值(YES变为了NO)。
我们将这个值设置为YES,并重复之前的代码,这里就不带着大家操作了,可以看得到,设置为YES后,和原来没显示这个值时的表现是完全吻合的。
我们继续在NO的情况下进行尝试,对ViewController的preferredStatusBarStyle重写是没有效果的。
在AppDelegate中加入如下代码,我们就得到了白色内容的状态栏。

    [UIApplication sharedApplication].statusBarHidden = NO;
    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;

OK, let's wrap it up

默认情况下ViewController-Based Status Bar Appearance这一属性在plist文件中不显示,其默认值为YES,当其为YES时,你需要在每一个VC中重写preferredStatusBarStyle。当其为NO时,你可以通过设置UIApplication的属性来操作整个应用的状态栏。
大地瓜建议,如果你没有动态修改状态栏样式的需求的话,最好将这个属性设置为NO,并在AppDelegate中设置两行即可。否则你需要在每一个需要设置状态栏的VC中都重写一遍preferredStatusBarStyle
OVER

  1. UINavigationController will alter the height of its UINavigationBar to either 44 points or 64 points, depending on a rather strange and undocumented set of constraints. If the UINavigationController detects that the top of its view’s frame is visually contiguous with its UIWindow’s top, then it draws its navigation bar with a height of 64 points. If its view’s top is not contiguous with the UIWindow’s top (even if off by only one point), then it draws its navigation bar in the “traditional” way with a height of 44 points. This logic is performed by UINavigationController even if it is several children down inside the view controller hierarchy of your application. There is no way to prevent this behavior.

UINavigationController会以一种文档中没有记录的奇怪方式来修改UINavigationBar的高度,44pt或64pt。如果UINavigationController检测到它的view的frame顶部与UIWindow的顶部视觉上重合,它就会将navigation bar高度画为64pt。如果其view的顶部和UIWindow的顶部视觉上不重合(哪怕只偏差1pt),它就会将navigation bar高度画为“传统的”44pt。即使在你的应用视图层集中已经很多层往下了,这个逻辑也会由UINavigationController来完成。你无法阻止这个行为。

大地瓜tips

注意,此处说的是UINavigationController的view,不是它的bar!!

  1. If you supply a custom navigation bar background image that is only 44 points (88 pixels) tall, and the UINavigationController’s view’s bounds matches the UIWindow’s bounds (as discussed in #4), the UINavigationController will draw your image in the frame (0,20,320,44), leaving 20 points of opaque black space above your custom image. This may confuse you into thinking you are a clever developer who bypassed rule #1, but you are mistaken. The navigation bar is still 64 points tall. Embedding a UINavigationController in a slide-to-reveal style view hierarchy makes this abundantly clear.

如果你提供一个只有44pt高的navigation bar background image,且UINavigationController的view和UIWindow的边界重合(就像第四点中所说那样),UINavigationController会将你的image画在frame (0,20,320,44)中,在你的custom image和顶部留出20pt高的黑色不透明空间。你也许误以为你很聪明,绕过了第一点,但你错了。navigation bar仍旧是64pt高。用滑动显示的view hierachy来嵌入一个UINavigationController,你就明白了。

大地瓜tips

如果你尝试着将status bar隐藏掉你就明白了,一个44pt高的navigation bar会紧紧贴在VC的顶端。非常难看。

  1. Beware of the confusingly-named edgesForExtendedLayout property of UIViewController. Adjusting edgesForExtendedLayout does nothing in most cases. The only way UIKit uses this property is if you add a view controller to a UINavigationController, then the UINavigationController uses edgesForExtendedLayout to determine whether or not its child view controller should be visible underneath the navigation bar / status bar area. Setting edgesForExtendedLayout on the UINavigationController itself does nothing to alter whether or not the UINavigationController has a 44 or 64 point high navigation bar area. See #4 for that logic. Similar layout logic applies to the bottom of your view when using a toolbar or UITabBarController.

注意一下UIViewController 的名字具有迷惑性的edgesForExtendedLayout属性。调整edgesForExtendedLayout属性大多数情况下没有任何作用。只有你将一个VC添加到UINavigationController 中,UIKit才会用到这个属性,然后UINavigationController使用edgesForExtendedLayout来决定其子VC在navigation bar/status bar下是否可见。设置UINavigationController 的edgesForExtendedLayout对于UINavigationController的navigation bar是44pt还是64pt没有任何影响。看看第四条的逻辑。对于toolbar或者UITabBarController,逻辑其实是类似的。

  1. If all you are trying to do is prevent your custom child view controller from underlapping the navigation bar when inside a UINavigationController, then set edgesForExtendedLayout to UIRectEdgeNone (or at least a mask that excludes UIRectEdgeTop). Set this value as early as possible in the life cycle of your view controller.

如果你想做的只是不让你自定义的子VC在UINavigationController中被navigation bar覆盖,那么将edgesForExtendedLayout设置为UIRectEdgeNone(或者至少能够超过UIRectEdgeTop的mask)。尽早在你VC的life cycle中设置这个值。

大地瓜tips

这个导航栏遮盖内容的问题其实以前大地瓜也遇到过,但是我用了很久也没做出来这个效果,貌似现在iOS10下已经没有这个问题了???是的,我想让他覆盖都覆盖不了。。。。

  1. UINavigationController and UITabBarController will also try to pad the contentInsets of table views and collection views in its subview hierarchy. It does this in a manner similar to the status bar logic from #4. There is a programmatic way of preventing this, by setting automaticallyAdjustsScrollViewInsets to NO for your table views and collection views (it defaults to YES). This posed some serious problems for Whisper and Riposte, since we use contentInset adjustments to control the layout of table views in response to toolbar and keyboard movements.

这段不好翻译,翻译完了也是逻辑不通,自己看吧。

  1. To reiterate: there is no way to return to iOS 6 style status bar layout logic. In order to approximate this, you have to move all the view controllers of your app into a container view that is offset by 20 points from the top of the screen, leaving an intentionally black view behind the status bar to simulate the old appearance. This is the method we ended up using in Riposte and Whisper.

重新强调一遍:你无法使用iOS6的status bar逻辑。为了接近这个,你需要将所有的VC都移动到一个距离上方20pt的container view中,故意留下20pt的空白来模仿旧的设计。

  1. Apple is pushing very hard to ensure that you don’t try to do #9. They want us to redesign all our apps to underlap the status bar. There are many cogent arguments, however, for both user experience and technical reasons, why this is not always a good idea. You should do what is best for your users and not simply follow the whimsy of the platform.

Apple很努力的推进以保证你不像9那样做。他们想让我们重新设计所有的app来从底部覆盖status bar。然而,在用户体验和技术上都有很多争论(这并不总是一个好的想法)。你应该做对用户最好的,而不是简单的听从平台的BB。

大地瓜唠叨唠叨

作为开发者,如果你没有一个固执的产品经理或美工,还是按照苹果的沉浸式设计来吧。
以上的种种困惑其实都是因为你不想做成沉浸式才产生的,如果你接受了沉浸式设计,其实你什么都不用做。

大地瓜观察了微博、微信、QQ等等主流应用,其实都已经接受了沉浸式设计,在状态栏下也是应用的一部分的!尤其是视频应用,可以清楚地看到视频从状态栏就开始显示了,状态栏下也是视频播放的一部分。但是斗鱼竟然还是非沉浸的!具体怎么实现,还是看情况吧!

斗鱼是非沉浸的,但是截图后状态栏不见!

腾讯新闻是沉浸式的.jpeg

腾讯体育沉浸式的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • /* UIViewController is a generic controller base class th...
    DanDanC阅读 1,793评论 0 2
  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,389评论 0 23
  • 本文为大地瓜原创,欢迎知识共享,转载请注明出处。虽然你不注明出处我也没什么精力和你计较。作者微信号:christg...
    大地瓜123阅读 711评论 0 0
  • 说起《大学》,就不得不提起《论语》,《中庸》,《孟子》。合称为四书五经。其中,大学讲的儒家思想:齐家,修身,平天下...
    李颖儿阅读 1,568评论 4 8
  • 我怀着谦卑与虔诚的心, 期盼那个人能快快知道你的心意, 看到那片久枯的沃土, 恰恰又是用了你的方式, 融化了冰山,...
    大瑾阅读 464评论 0 1