为iOS 6和iOS 7设计界面

原地址:http://blog.bignerdranch.com/4705-designing-interfaces-ios-6-ios-7/

                http://blog.bignerdranch.com/4705-designing-interfaces-ios-6-ios-7/

Although the iOS 7 adoption rate is now at 85 percent, many developers still need to support iOS 6. I’ve seen people struggle with designing their interfaces so that they look at home on both versions of the operating system. This blog post will show you the best practices for achieving this goal.

PROBLEM

The problem initially occurred when developers first updated their apps for iOS 7. Everything looks great on iOS 6, but when you run the app on iOS 7, your subviews get cut off.

Here is an example from my iOS book that illustrates the situation in an application we call Homepwner:

Homepwner Problem

You may have also come across a similar issue with UIScrollView; we’ll talk more about that shortly.

So the question is, how do you design your interfaces such that they look right on both iOS 6 and iOS 7? (Hint: if you aren’t already using Auto Layout, you’re going to want to start using it.)

SOLUTION

We’re going to take a look at three different use cases:

  • interfaces that do not have a UIScrollView
  • interfaces that do use UIScrollView (or a subclass thereof, such as UITableView orUIWebView)
  • interfaces that are built using storyboards
So you don’t have a scroll view

If you aren’t using a scroll view, adjusting the interface to work with both iOS 6 and iOS 7 is very easy.

  1. Use Auto Layout.
  2. Have a single view anchored to the top of your UI using a constraint 1, with all other views anchored either directly or indirectly to that view.
  3. Create an IBOutlet to that top NSLayoutConstraint.
  4. Modify this constraint in viewDidLoad conditionally based on the OS version that the app is running on.

Let’s take a look at each of these steps.

I mentioned earlier that you should be using Auto Layout—and really, you should be using it for all of your interfaces. If you are not, you are doing yourself a huge disservice. Maybe this’ll be the subject of a future blog post, but for now, send me any “why?” questions by tweeting@cbkeur. I hope I don’t have to convince you.

Typically, you’ll have a subview at the top of your interface. I often refer to this view as the anchoring view (if you have a better name for this, please let me know). This subview will be constrained to the top of its superview via a top constraint, and the rest of the subviews will be laid out in some fashion relative to that top view.

Take a look at this screen shot (designed with iOS 7 in mind, unlike the previous screen shot).

Homepwner Constraints

In this example, I’m considering the name label the anchor view. The text field to its right has aligned its baseline with the name label to determine its y-position. As you can see, there is exactly one view that is anchored to the top. This makes updating our interface very easy, and will make fixing our interface for iOS 6 a breeze.

In iOS 6, there is going to be quite a bit of white space above the name label. This is because the top of the view is at the bottom of the navigation bar, as opposed to the top of the screen, like you can see in the Interface Builder screen shot above. To eliminate that white space, we just need to subtract the heights of both the status and navigation bars from the top constraint’s constant.

To do this, we first need to create an outlet to this constraint in code so we can modify it at run time. This looks like:

@property (nonatomic, weak) IBOutlet NSLayoutConstraint *topConstraint;

Make sure to connect this to the NSLayoutConstraint that you have in your XIB file. (If you are doing things programmatically, then you already have a reference to that constraint, so you’re good to go.)

The last thing you need to do is actually update the constraint’s constant based on the OS version. viewDidLoad is the perfect time for this if you are using a XIB (as that is the first time your outlet is actually connected), or loadView if you are building your interface programmatically.

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Check to see if we are running on iOS 6
    if (![self respondsToSelector:@selector(topLayoutGuide)]) {
        self.topConstraint.constant = self.topConstraint.constant - 64;
    } 
}

Let’s walk through this code. The code above uses the topLayoutGuide property of view controllers to determine if you are running on iOS 6 or iOS 7, as it is an iOS 7-only property. This property is closely related to laying out interfaces, so you use this as a reference point. If the view controller does not respond to that selector, you are on iOS 6, so you need to remove the white space that our XIB had at the top. To do this, subtract the height of both the status and navigation bars 2.

(If you built your interface for iOS 6, and needed to support iOS 7 after the fact, you’d want toadd in the white space instead of removing it, so you’d want to doself.topConstraint.constant = self.topConstraint.constant + 64;. This would fix the problem in the screen shot at the top of this blog post.)

Use Auto Layout; it’ll make your life much easier.

What about the scrolly things?

Say you have a scroll view (or subclass), and for one reason or another, your interface is wonky in iOS 6 or iOS 7. What can you to do fix it?

  1. Use UIViewController‘s automaticallyAdjustsScrollViewInsets if you have a single scroll view that is at the “back” of your interface (I’ll define this below).
  2. Adjust the contentInset and scrollIndicatorInsets properties of the scroll view manually if you have multiple scroll views, or if it’s not at the “back” of the interface.

Let’s take a look at both of these circumstances.

With iOS 7, UIViewControllers have a property called automaticallyAdjustsScrollViewInsets, and it defaults to YES. This property can make your life much easier, provided you understand how it works.

If you have a scroll view that is either the root view of your view controller (such as with aUITableViewControlleror the subview at index 0, then that property will adjust both thecontentInset and the scrollIndicatorInsets. This will allow your scroll view to start its content and scroll indicators below the navigation bar (if your view controller is in a navigation controller). As opposed to manually adjusting the frame of the scroll view to start underneath the navigation bar, this solution will allow your scroll view content to scroll underneath the navigation bar, which is an important aspect of iOS 7.

If your scroll view does not meet the prior criteria (or if you’ve setautomaticallyAdjustsScrollViewInsets to NO), then you’ll need to manually update those two properties:

- (void)viewDidLoad
{
    [super viewDidLoad];

    if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) {
        // For insetting with a navigation bar
        UIEdgeInsets insets = UIEdgeInsetsMake(64, 0, 0, 0);
        self.tableView.contentInset = insets;
        self.tableView.scrollIndicatorInsets = insets;
    }
}

You might want to do this if you have a background image behind your scroll view, or if you have multiple scroll views within one view controller. Since this property does not exist on iOS 6, you get the existing behavior.

Storyboards

Ah, storyboards. Apple loves storyboards and they can do stuff that is seemingly magic. So what do you do for storyboards?

  1. Use Auto Layout.
  2. If you aren’t using a scroll view, create a top constraint to the “Top Layout Guide”.
  3. If you are using a scroll view, you can use the scroll view steps above (and you’re able to set the automaticallyAdjustsScrollViewInsets property from within the Attributes Inspector on the view controller).

Well that was easy. Let’s take a look at both of these.

Creating a constraint to the “Top Layout Guide” is easy, as shown in this screen shot.

Storyboard Top Layout Guide

As long as everything is constrained relative to the top layout guide, then your interface will lay out correctly on iOS 6 and iOS 7.

“But wait, Christian,” you might say. “You said that the top layout guide is an iOS 7-only feature.” And you’d be entirely correct. Apple is working some magic here. On iOS 7, Apple is correctly adding a constraint to the topLayoutGuide, but on iOS 6, Apple is creating a UIView with a frame of {0, 0, 0, 0} and attaching that top constraint to that view instead. I wish it could work as nicely somehow with XIBs, but alas.

For scroll views, follow the same advice as in the previous section. If you need to set theautomaticallyAdjustsScrollViewInsets property, you can do so from the Attributes Inspector on the view controller.

Storyboard Insets

DEFERENCE

So why did Apple make these changes that can require a non-trivial amount of work on behalf of the developer?

One of the key areas that Apple wanted iOS 7 to improve upon was deference, which is defined by my Mac as “humble submission and respect.” Our interfaces should defer to the content, which is at the heart of the experiences within our apps. Apple’s Human Interface Guidelinesstate that “the UI helps users understand and interact with the content, but never competes with it.”

Take Safari for example:

Safari

When you first visit a site, you have all of the controls at your disposal. The URL bar and reload button are at the top, and the toolbar at the bottom contains the rest of the buttons needed to interact with the app.

Once you begin to scroll, the UI defers to the content on the screen. The navigation bar at the top shrinks to display only the base URL, and the toolbar at the bottom goes away completely. If a user needs to access these buttons, they can simply tap on the navigation bar at the top, or scroll back up 3.

Some classes were changed in pursuit of deference throughout iOS 7. We’ll focus onUINavigationController, but the same principles can then be applied toUITabBarController.

In iOS 6 and earlier, the content view of a navigation controller extended from just under theUINavigationBar to the bottom of the screen. With iOS 7, on the other hand, the content view extends from the very top of the screen to the very bottom, underlapping the navigation bar.

Take a look at this screen shot of Maps taken on both iOS 6 and iOS 7. Notice that with iOS 7, you can easily see the blurred content under the navigation bar (and the toolbar at the bottom).

Maps

Content is clearly king here. The translucent UI elements hint at the content behind them.

CONCLUSION

  • If you have an interface without a scroll view, use Auto Layout to shift your content up or down to support both iOS 6 and iOS 7.
  • If you are using a scroll view, take advantage of UIViewController‘sautomaticallyAdjustsScrollViewInsets property to due the heavy lifting for you. If, for some reason, you aren’t able to take advantage of that property, then you’ll want to adjust the contentInset and scrollIndicatorInsets properties of the scroll view to achieve a similar result.
  • If you are using storyboards, anchor views to the “Top Layout Guide”.

Today’s blog post comes courtesy of our iOS hero, Auto Layout. I’m a huge proponent of Auto Layout. With Xcode 5 (and even more so with Xcode 5.1), I find it quite easy to work with. You’ll probably see more from me on Auto Layout in the future.


  1. It doesn’t have to be a single view, but you’ll need to duplicate the remaining steps for each additional view that is anchored to the top. 
  2. I hardcoded this value, 20 from the status bar and 44 from the navigation bar, but it might be better to do it dynamically, especially if you support iPhone landscape. Just add the height of the UIApplication‘s statusBarFrame to the height of the navigation controller’s navigationBar
  3. The way iOS handles this is a little interesting. If you scroll to the very top of the page, the navigation controls reappear. If you flick to scroll up with some velocity, the controls will also appear. But if you scroll up without flicking, the controls stay hidden. My gut tells me this is because Apple decided in the latter case you were probably deliberately still reading the page, and thus didn’t want the controls to appear. 

问题
这个问题最初在开发者针对iOS 7更新应用的时候发现,应用中的一切在iOS 6中看起来都很好,但是在iOS 7运行app时,子视图就被切断了。我的iOS书籍《iOS Programming: The Big Nerd Ranch Guide》中用名为“Homepwner”应用程序来说明这一情况,如下图:

你可能在UIScrollView类中也遇到了类似的问题。一会儿我们将进一步讨论。问题是,如何设计界面使其看起来在iOS7和iOS6中都适合?(提示:如果你还没有用Auto Layout,那就开始用吧!)
 
解决办法
接下来我们看一看三个不同的例子:
1.界面没有使用UIScrollView;
2.界面使用UIScrollVIew(或者用子类,如UITableView或者UIWebView);
3.界面是用storyboards创建的。
 
假设没有使用滚动视图
如果你没有用滚动视图,那么是调整界面同时适应iOS6和iOS7是非常简单的。
1.使用自动布局;
2.在UI顶部使用约束来固定一个视图,其他的视图直接或者间接地固定在这个视图上;
3.在NSLayoutConstraint上创建一个IBOutlet;
4.基于应用程序运行的OS版本,有条件的在viewDidLoad上修改这个约束。
 
接下来让我们逐一看这些步骤。
 
之前我提到过你应该用Auto Layout。如果不用这种方法将会很麻烦。或许这将是博客未来的主题,现在有任何疑问都可以通过twitter(@cbkeur)联系我。
 
通常,你将在界面的顶端有一个子视图。我通常把这种视图称为anchor view。这个子视图会通过一个top constraint居于父视图的顶端,其余子视图将会以某种方式相对于top view。
 
看看下面的屏幕截图(设计时考虑到iOS 7,与之前的屏幕截图不同)

在这个例子中,我考虑anchor view的name标签。它右边的文本框对准name标签的基准线,以确定它的y轴位置。正如你所见,恰恰有一个视图固定在顶部。这让界面更新变得更简单,并且让修复iOS 6界面变得很轻松。
 
在iOS 6中,在name标签上部将会有很多的空白区域。这是因为相对于屏幕的顶端,视图的顶部是在导航栏的底部,正如你在上面Interface Builder中看到的屏幕截图一样。为了消除这些空白,我们只需要从顶部约束常量中减去状态栏和导航栏的高度即可。
 
为此,我们需要在代码中为约束创建一个outlet,以便我们能够在运行时进行调整。如下:
   
   
  1. @property (nonatomic, weak) IBOutlet NSLayoutConstraint *topConstraint; 
 
确保将这个连接到XIB文件中的NSLayoutConstraint(如果你用编程方式做,那么你已经有了一个引用约束。)
 
最后你需要做的事情就是根据OS版本来更新约束常量。如果你使用的是XIB(首次连接outlet),那么viewDidLoad非常适合;或者使用loadView,如果你以编程方式搭建界面。
 
在上面的代码中,使用视图控制器的topLayoutGuide 属性来决定是运行在iOS 6上还是iOS 7上,只有iOS 7中有这个属性。这个属性与布局界面十分相关,你可以它作为一个参考点。如果视图控制器没有响应那个选择器,那么你是在iOS 6上运行,因此你需要在顶部删除XIB中的空白区域。为此,要去掉状态栏和导航栏的高度。(如果你在iOS 6上创建界面,并且需要运行在iOS 7上,那么你就要添加空白区域而不是删除它,因此需要“self.topConstraint.constant = self.topConstraint.constant + 64;”。这将解决文章开头所用截图中存在的问题。
 
使用Auto Layout,它将使你的工作更简单。
 
假设使用了滚动视图呢?
假定你的界面有滚动视图(或者其子类),出于一个或者其他多个原因,你的界面不能在iOS 6或者iOS 7上稳定运行,你该怎样修复这个问题呢?
 
1.如果你有在界面后边的单个滚动视图,可使用UIViewController的automaticallyAdjustsScrollViewInsets(我会解释这一点)
2.如果你有多个滚动视图,或者视图不在界面后边,那可以手动调整滚动视图的contentInset和scrollIndicatorInsets属性。
 
让我们来看看这两种情况。
 
在iOS 7中,UIViewControllers有一个属性automaticallyAdjustsScrollViewInsets,默认为YES。该属性会简单化你的工作,让你理解它是如何工作的。
 
如果你有一个滚动视图,要么是视图控制器(比如UITableViewController)的根视图,要么是索引为0的子视图,那么这个属性将调整contentInset和scrollIndicatorInsets。这将允许在导航栏下启动滚动视图内容和滚动指示器(如果你的视图控制器是一个导航控制器)。与在导航栏下面手动调整滚动视图的框架不同,这个方法允许你的滚动视图内容在导航栏下面滚动,这iOS 7很重要的一个方面。
 
如果你的滚动视图不符合之前的标准(或者你把automaticallyAdjustsScrollViewInsets设为NO),那么你需要手动更改这两个属性:
   
   
  1. - (void)viewDidLoad 
  2.     [super viewDidLoad]; 
  3.   
  4.     if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) { 
  5.         // For insetting with a navigation bar 
  6.         UIEdgeInsets insets = UIEdgeInsetsMake(64, 0, 0, 0); 
  7.         self.tableView.contentInset = insets; 
  8.         self.tableView.scrollIndicatorInsets = insets; 
  9.     } 
如果滚动视图后面有一个背景图片,或者在一个视图控制器中有多个滚动视图,你可能会想这么做。由于iOS 6中没有这个属性,那你只能用现有的方法了。
 
Storyboards
苹果喜欢storyboards,他们能做出看上去很神奇的东西。所以你如何使用storyboards?
 
1.使用Auto Layout
2.如果不用滚动视图,那创建一个“Top Layout Guide”的顶级约束。
3.如果使用滚动视图,那么你可用上面的滚动视图步骤(并且你可以在视图控制器上的属性面板中设置automaticallyAdjustsScrollViewInsets 的属性)。
 
这很简单,让我们一下看看这两点。
 
创建一个“Top Layout Guide”的顶级约束很简单,如下屏幕截图所示:

只要一切都相对于“Top Layout Guide”进行了约束,那么你的界面在iOS 6和iOS 7中就会正确显示。
 
你可能会说,“等一下,Christian。你说过Top Layout Guide是只有iOS 7才有的特性。”你说的没错。苹果开发者就是在这里施展魔术的。在iOS 7中,苹果给topLayoutGuide正确地添加了约束,但是在iOS 6中,苹果用{0,0,0,0}框架创建了UIView,将顶级约束添加到这个视图上。我希望它像XIB文件一样正常运行,但很可惜。
 
对于滚动视图,参考上一小节中的建议。如果你需要设置automaticallyAdjustsScrollViewInsets属性,你可以在视图控制器上的属性面板中设置。

依从
那么为什么苹果开发者要做这些需要大量工作的改变呢?
 
苹果希望iOS 7改进的一个关键点就是“依从”,my Mac给它的定义是“依从和尊重”。我们的界面应该依从内容,这正是app的核心体验。苹果的《人机界面指南》指出“用户界面帮助用户理解并与内容交互,但从不与内容竞争”。
 
例如“Safari”: 

当你第一次访问网站是,你可以任意使用所有的控件。URL栏和刷新按钮在顶部,底部的工具栏包含其他需要可与app交互的按钮。当你开始滚动页面时,UI将内容呈现在屏幕上。顶部的导航栏收缩只显示基本的URL,底部的工具栏完全隐藏了。如果用户需要用这些按钮,只需要点击上部的导航栏或者向后滚动。
 
为了遵循iOS 7的依从要求,一些类也发生了改变。我们来看下UINavigationController,而同样的规则也应用于UITabBarController中。
 
iOS 6及之前的版本中,导航控制器的内容视图仅仅是从UINavigationBar的下面扩展到屏幕底部。然而,iOS 7中内容视图从屏幕的最顶部扩展到最底部,从导航栏下面显示。
 
看看下面的iOS 6和iOS 7中地图的屏幕截屏。注意到,在iOS 7中你可以很容易看到导航栏(以及底部的工具栏)下面的模糊内容。
 
结论
1.如果你的界面没有使用滚动视图,那可以使用Auto Layout向上或向下移动内容以同时支持iOS 6和iOS 7。
 
2.如果你使用滚动视图,充分利用UIViewController‘s 和automaticallyAdjustsScrollViewInsets的特性来解决你的困难。如果出于某种原因,你没能利用这个属性,那
 
么你需要调整滚动视图的contentInset和scrollIndicatorInsets属性来达到相似的结果。
 
3.如果你使用storyboards,将视图固定到“Top Layout Guide”。
 
今天的博文来自我们iOS英雄--Auto Layout。我是Auto Layout的强力支持者。通过Xcode 5(甚至是Xcode 5.1),我发现它十分好用。将来你可能会看到我更多关于Auto Layout的文章。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值