Suppose I have two Text View.
In portrait mode, I want these one below another &
In landscape mode, I want these to be side by side
Is it possible to do that using layout constraints in storyboard using autolayout?
If yes, then how?
If not, then what would be the other better solution to achieve this.
ios6 is my target version
Here's how you might go about it in code.
Basically you need to:
a) configure the appropriate NSLayoutConstraint
s for the given orientation in updateViewConstraints
in your UIViewController
.
b) call [self.view setNeedsUpdateConstraints]
when the interface rotates.
Below is a ViewController implementation and a category on UIView with helper methods.
@interface ConstraintsViewController ()
@property (nonatomic, weak) IBOutlet UIView *upperOrLeftView, *lowerOrRightView;
@end
@implementation ConstraintsViewController
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self.view setNeedsUpdateConstraints];
}
-(void)updateViewConstraints {
[super updateViewConstraints];
[self.view removeConstraintsRelatingToItems:@[self.upperOrLeftView,self.lowerOrRightView]];
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) {
[self.view constrainSubview:self.upperOrLeftView usingEdgeInsets:UIEdgeInsetsMake(0, 0, -1, 0)];
[self.view constrainSubview:self.lowerOrRightView usingEdgeInsets:UIEdgeInsetsMake(-1, 0, 0, 0)];
[self.view constrainSubviewsTopToBottom:@[self.upperOrLeftView, self.lowerOrRightView]];
}
else {
[self.view constrainSubview:self.upperOrLeftView usingEdgeInsets:UIEdgeInsetsMake(0, 0, 0, -1)];
[self.view constrainSubview:self.lowerOrRightView usingEdgeInsets:UIEdgeInsetsMake(0, -1, 0, 0)];
[self.view constrainSubviewsLeftToRight:@[self.upperOrLeftView, self.lowerOrRightView]];
}
}
@end
Put this in UIView+Constraints.h
@interface UIView (Constraints)
-(void)removeConstraintsRelatingToItems:(NSArray*)items;
-(void)constrainSubview:(UIView*)subview usingEdgeInsets:(UIEdgeInsets)insets;
-(void)constrainSubviewsLeftToRight:(NSArray*)subviews;
-(void)constrainSubviewsTopToBottom:(NSArray*)subviews;
@end
This is UIView+Constraints.m
@implementation UIView (Constraints)
-(void)removeConstraintsRelatingToItems:(NSArray *)items {
for(NSLayoutConstraint *constraint in self.constraints) {
if([items containsObject:constraint.firstItem] || [items containsObject:constraint.secondItem]) {
[self removeConstraint:constraint];
}
}
}
/** Set up constraints to flow the subviews from top to bottom and with equal heights */
-(void)constrainSubviewsTopToBottom:(NSArray*)subviews {
if(subviews.count > 1) {
UIView *anchorView = subviews[0];
for(int i = 1; i < subviews.count; i++) {
UIView *view = subviews[i];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:anchorView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0];
NSLayoutConstraint *edgesConstraint = [NSLayoutConstraint constraintWithItem:anchorView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0];
[self addConstraints:@[heightConstraint, edgesConstraint]];
anchorView = view;
}
}
}
/** Set up constraints to flow the subviews from left to right and with equal widths */
-(void)constrainSubviewsLeftToRight:(NSArray*)subviews {
if(subviews.count > 1) {
UIView *anchorView = subviews[0];
for(int i = 1; i < subviews.count; i++) {
UIView *view = subviews[i];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:anchorView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];
NSLayoutConstraint *edgesConstraint = [NSLayoutConstraint constraintWithItem:anchorView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0];
[self addConstraints:@[widthConstraint, edgesConstraint]];
anchorView = view;
}
}
}
/**
Set up constraints to anchor the various edges of the subview to it's superview (this view) using the provided insets.
Any inset set to < 0.0 means that edge is ignored;
*/
-(void)constrainSubview:(UIView*)subview usingEdgeInsets:(UIEdgeInsets)insets {
if(insets.top >= 0.0) {
[self addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:insets.top]];
}
if(insets.right >= 0.0) {
[self addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:-insets.right]];
}
if(insets.bottom >= 0.0) {
[self addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-insets.bottom]];
}
if(insets.left >= 0.0) {
[self addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:insets.left]];
}
}
@end
In my opinion the best way to layout viewController's views in more than one orientation is to create few views for each orientation.
Here i have found this:
"When you add a view controller to the storyboard it comes with a view. Call that the container view. Add two views to the container view: a portrait view and a landscape view. Set the dimension of the portrait view and the landscape view appropriately using the size inspector. Add buttons, more views, labels or whatever to the portrait and landscape views as needed for your application. Then when the orientation changes hide one view and show the other."
You could achieve such behaviour by using Interface Builder only. You need to set up some constraints with different priorities.
See my more detailed answer on the topic here. Also there are a screencast and a link to an example app I've created.