How to properly design multi-orientation iPad appl

2019-01-23 14:23发布

What's the correct way of designing multi-orientation iPad app nowadays? I've read a lot of Apple docs, web resources and some SO Q&A. Here are my initial requirements:

  • This has to work on iOS 5 and above. No need to create back compatibility with previos versions of iOS.
  • I would like if possible to have portrait and landscape UI defined in different NIB files.
  • My NIB files will have different images for same UI elements in different orientation (like I will have header.png and header-landscape.png UIImageView for example.
  • App will have several screens and I need to be able to switch orientation on each of them.

So what do I do?

  • Create one VC per screen and replace underlying view in willRotate handler?
  • Create one VC per orientation? But then how do you switch them properly?
  • Simply re-arranging elements won't work (I think) because I would have to reload images.
  • Write everything in code (I would really hate this idea)?

What are proper approaches to this issue as of today?

5条回答
姐就是有狂的资本
2楼-- · 2019-01-23 14:42

I've used 3 different techniques, 1 of which I think is "correct" way to handle multiple orientations.

  1. Incorrect: Created two different nib/xibs. Not "bad", but it is a pain to handle context and state when you are constantly reconstructing the UIView in the UIViewController.
  2. Incorrect: Used one nib and repositioned them on rotation based on absolute coordinates. This is the wrong way to do it. I've seen this over and over but it's the worst way you can accomplish handling multiple viewing modes. A symptom of this is UIViews that are device orientation aware. Completely Unnecessary.
  3. Correct: Use the autoresizingMask for elements inside the view to float them to the proper position on device rotation, tweaking what can't be accomplished that way using a CGRect frame. I've only had to do this once to align something. IMHO this is the "correct" way of handling multiple device orientations. The UIView and any subviews don't need to know about rotation. You can use one VC per screen with many subviews and keep it simple. If your layout can be devised in this way, with floating elements, then you are golden and the UI is a breeze.

Just my two cents.

Edit: iOS 6 introduced AutoLayout and that can be used in place of "struts and springs" - https://developer.apple.com/library/ios/documentation/userexperience/conceptual/AutolayoutPG/Introduction/Introduction.html

查看更多
Root(大扎)
3楼-- · 2019-01-23 14:42

Why running after an application that can do quite everything? When you design an application, you decide what is the best orientation for it - landscape or portrait - and even if your app's design will need a reverse orientation (all is in landscape and 1-2 controllers are portrait, or vice versa), you will make the nibs accordingly. Well, in some cases you will need a controller to support all orientations, and you will do it.

But trying to make all controllers to support all orientations... well, I find this idea a bit weird.

Just in case you still wanna do it, you have already listed the possible solutions, and I like the last option.

查看更多
孤傲高冷的网名
4楼-- · 2019-01-23 14:47

It is disappointing to me that Apple does not provide a way of using multiple XIB files (one for each orientation) for a given view controller - this would be an excellent way of handling orientation issues, since autoresizing only rarely handles orientation well (it only works with very simple interfaces).

You can do this instead: create a XIB for portrait and assign each subview in the XIB with a unique tag property. Then duplicate the XIB, and re-do the layout for landscape (so now you have two XIBs, one portrait and one landscape).

When your app starts, you create your view controller initially from whichever XIB is appropriate to the startup orientation. Iterate recursively through all the subviews, and create a dictionary that contains all the subview frames, keyed by the tag property for each subview.

Next, create a temporary view controller from the other orientation's XIB, and do the same recursive iteration, storing all the frames keyed by tag to a second dictionary (each dictionary matches one of the orientations, and should be named accordingly). Then dispose of this second temporary view controller.

In layoutSubviews (actually in viewDidLayoutSubviews, since you're dealing with view controllers and you can do iOS 5+), all you have to do then is iterate recursively through all the subviews, and assign to each subview the frame from the dictionary of frames appropriate for the current orientation, based on the subview's tag property.

This technique will allow you to preserve the state of your view controller without having to reload it anew each time the orientation changes. It also allows you to lay out your view controllers using IB, so you don't have to write a bunch of manual layout code.

查看更多
Ridiculous、
5楼-- · 2019-01-23 14:59

I actually think this is a really complex subject, like most architectural issues. I don't believe you should try to solve the problem with only one technique.

For the sake of not crowding off all the other answers on this page, I posted a full-writeup on my blog and a link to some sample code, too

Summary

It should be preferred to express your UI in .xib files, although the extent to which you allow yourself to diverge from this partly depends on the skill set of the people who will modify your app in the future. It may not just be programmers!

Preferred Option

It should be strongly preferred to implement one logical view with one .xib file, and one UIViewController subclass. Try really hard to do that. Setting autoresizesSubviews=YES on your XIB's root view, and properly adjusting the child views' autoresizingMask to flex with changes in screen size/orientation can go a long way.

But, that won't always be enough. If your layout needs adjusting in landscape orientation, beyond what autoresizing can handle, I use two main options. Which one you should choose depends on your view's content.

Option One

If the layout for portrait vs. landscape is not too different, then I recommend staying with one .xib, and having a little bit of code in your View Controller to adjust layout on rotation.

Option Two

If the landscape vs. portrait differences are really significant, then I recommend having one .xib for each orientation (with a strict naming convention, like MyViewController.xib and MyViewController-landscape.xib). But, both .xib files should connect File's Owner to the same View Controller class! To me, this is key.

If you are ever going to do anything but the preferred alternative, I recommend creating a reusable UIViewController base class to automate this, and keep it consistent. It's more work than you might think at first, and it's silly to keep doing it in every UIViewController subclass that needs rotation handling.


Solution

I created such a base class, and put it in a sample project here. You can see a Hello World example of how I think all three scenarios should be handled:

  • One view controller and one .xib, with autoresizing only (my FirstViewController)
  • One view controller and two .xibs, with switching between them automated (my SecondViewController)
  • One view controller and one .xib, with minor programmatic layout on rotations (ThirdViewController)

The RotatingViewController base class I use is equally applicable to iPhone apps. I actually have a more complicated version that handles maintaining iPad and iPhone, portrait and landscape layouts (for Universal apps).

But, this question was only about iPad, so I stripped it down, to be easier to understand.

My base class also has a utility imageNamed: method that helps load images that are proper for the current orientation (named with a image-landscape.png convention). However, I think stretchable UIImages should be used instead the vast majority of the time.

I didn't do this, but the RotatingViewController could also try to walk its subviews tree and update the image property on UIButton or UIImageView objects, when device orientation changes. I didn't go to that length, but you could.

More rationale for these recommendations is available on the blog post I reference

查看更多
家丑人穷心不美
6楼-- · 2019-01-23 15:05

So, with the lack of answers to this question and time is an issue, I ended up doing the following:

  • Used Interface Builder to create NIB files for all screens in portrait orientation. Binded all controls that requires some layout to outlets.
  • Used IB to create NIB files for landscape orientation as well, but used it only for reference to see where frames for UI elements would be in landscape.
  • Added code to re-arrange all elements to landscape orientation and back to portrait in willAutoRotate handler.

This method gave me flexibility of creating at list majority of UI in IB. I still need to keep code to re-arrange elements to different orientations but usually this only involves setFrame methods for labels, buttons etc, and imageNamed for images. Which is much smaller scope then creating all UI in code directly.

I will open a bounty on this question as soon as SO allows, because I think this question is very important and useful for any developer creating universal and multi-orientation app for iPad/iPhone.

查看更多
登录 后发表回答