Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
200 views
in Technique[技术] by (71.8m points)

ios - How can I rearrange views when autorotating with autolayout?

I frequently run into this problem where I want my views arranged in one manner for portrait and a somewhat drastically different manner for landscape.

For a simplified example, consider the following:

Portrait:

image

Landscape:

image

Notice how in portrait the images are stacked vertically, but in landscape, they are side-by-side?

Of course, I can manually calculate the positions and sizes, but for more complicated views, this quickly becomes complex. Plus, I'd prefer to use Autolayout so there's less device-specific code (if any at all). But that seems like a lot of Autolayout code to write. Is there a way to do set up the constraints for this sort of stuff through the storyboard?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There are two underused features of Xcode's interface builder we can make use of here to make exactly this sort of thing a synch.

  1. You can create IBOutlet connections for NSLayoutConstraints.
  2. You can hook up "outlet collections", which is an array of IBOutlet objects.

So with these two things in mind, the gist of what we want to do is create all of our autolayout constraints for one orientation, hook them all up in an outlet collection. Now, for each of these constraints, uncheck the "Installed" option on interface builder. Then make all of our outlet collections for another layout, and hook them up to another outlet collection. We can create as many of these layout groups as we want.

It's important to note that we will need a reference to any UI element which has constraints installed on it directly, and we will need a seperate outlet collection not just for each layout we want, but for each UI object which has constraints installed on it directly.

Let's take a look at the fairly simplified example from your question.

enter image description here

If you look in the constraints list on the left, you can see half of them are grayed-out. The grayed-out constraints are the landscape constraints. I created all of these, then unchecked "Installed" for each of them:

enter image description here

Meanwhile, the ungrayed, normal looking constraints are the portrait constraints. For these, I left them "Installed". It's completely unnecessary to leave any of them installed, but you will run into problems if you leave multiple sets installed (they most likely conflict).

enter image description here

Be sure not to check "Remove at build time" for any of these. We don't want the constraints "removed" at build time. This simply means the constraint isn't created at all (so we'll lose the reference to it). If we leave this check marked while we have an IBOutlet to the constraint, Xcode will generate a warning:

Unsupported Configuration
Connection to placeholder constraint. Constraints marked as placeholders in IB should not have any connections since these constraints are not compiled into the document and will not exist at runtime.

Anyway, so now we need to hook up the constraints to an outlet so we can access them at run time.

Hold Ctrl and click and drag from one of the constraints to your source code file, just as you would connect any other UI element. On the dialog that pops up, choose Outlet Collection and a descriptive name:

enter image description here

Now hook up all of the other constraints that match that constraint group into the same outlet collection:

enter image description here

Once we've finished hooking up all of our constraints, it's just a matter of removing/adding them at the appropriate time.

For such a simple example as the scenario described in the question, we can override updateViewConstraints with some code like this:

Swift

class ViewController: UIViewController {
    @IBOutlet var landscapeConstraints: [NSLayoutConstraint]!
    @IBOutlet var portraitConstraints: [NSLayoutConstraint]!

    override func updateViewConstraints() {
        let isPortrait = self.view.bounds.width < self.view.bounds.height

        self.view.removeConstraints(self.portraitConstraints)
        self.view.removeConstraints(self.landscapeConstraints)
        self.view.addConstraints(isPortrait ? self.portraitConstraints : self.landscapeConstraints)

        super.updateViewConstraints()
    }
}

Objective-C

@interface ViewController()

@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints;

@end

@implementation ViewController

- (void)updateViewConstraints {
    BOOL isPortrait = self.view.bounds.size.width < self.view.boudns.size.height;

    [self.view removeConstraints:self.portraitConstraints];
    [self.view removeConstraints:self.landscapeConstraints];
    [self.view addConstraints:(isPortrait ? self.portraitConstraints : self.landscapeConstraints)];

    [super updateViewConstraints];
}

@end

We're not checking which set of constraints the view previously had, so just remove both of our constraint sets, and then add the appropriate set we want to use.

This is all the code we need to manage completely changing out an entire set of constraints on an object at run time. This allows to work in the interface builder to set all of our constraints up instead of having to do it programmatically (which I find a little more tedious and error-prone).

The end result? Very nice looking autorotation rearrangement without pulling your hair out getting the autolayout code done perfectly:

image


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...