How to Use Autolayout to Provide Different Constraints for Landscape and Portrait Orientations

Can I use autolayout to provide different constraints for landscape and portrait orientations?

Edit: Using the new concept of Size Classes introduced in Xcode 6, you can easily setup different constraints for specific size classes in Interface Builder. Most devices (e.g. all current iPhones) have a Compact vertical size class in landscape mode.

Sample Image

This is a much better concept for general layout decisions than determining the device's orientation.

That being said, if you really need to know the orientation, UIDevice.currentDevice().orientation is the way to go.


Original post:

Override the updateViewConstraints method of UIViewController to provide layout constraints for specific situations. This way, the layout is always set up the correct way according to situation. Make sure they form a complete set of constraints with those created within the storyboard. You can use IB to set up your general constraints and mark those subject to change to be removed at runtime.

Sample Image

I use the following implementation to present a different set of constraints for each orientation:

-(void)updateViewConstraints {
[super updateViewConstraints];

// constraints for portrait orientation
// use a property to change a constraint's constant and/or create constraints programmatically, e.g.:
if (!self.layoutConstraintsPortrait) {
UIView *image1 = self.image1;
UIView *image2 = self.image2;
self.layoutConstraintsPortrait = [[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[image1]-[image2]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(image1, image2)] mutableCopy];
[self.layoutConstraintsPortrait addObject:[NSLayoutConstraint constraintWithItem:image1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem: image1.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
[self.layoutConstraintsPortrait addObject:[NSLayoutConstraint constraintWithItem:image2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:image2.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
}

// constraints for landscape orientation
// make sure they don't conflict with and complement the existing constraints
if (!self.layoutConstraintsLandscape) {
UIView *image1 = self.image1;
UIView *image2 = self.image2;
self.layoutConstraintsLandscape = [[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[image1]-[image2]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(image1, image2)] mutableCopy];
[self.layoutConstraintsLandscape addObject:[NSLayoutConstraint constraintWithItem:image1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:image1.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
[self.layoutConstraintsLandscape addObject:[NSLayoutConstraint constraintWithItem:image2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem: image2.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
}

BOOL isPortrait = UIInterfaceOrientationIsPortrait(self.interfaceOrientation);
[self.view removeConstraints:isPortrait ? self.layoutConstraintsLandscape : self.layoutConstraintsPortrait];
[self.view addConstraints:isPortrait ? self.layoutConstraintsPortrait : self.layoutConstraintsLandscape];
}

Now, all you need to do is trigger a constraint update whenever the situation changes. Override willAnimateRotationToInterfaceOrientation:duration: to animate the constraint update on orientation change:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];

[self.view setNeedsUpdateConstraints];

}

Different layout and constraints for portrait and landscape views in iOS?

you may use Size Classes to handle this situation

just pre-define two layout settings, one for portrait mode, and the other for landscape mode. when display mode changes, UIKit would update layout automatically for you.

for more info and step guides, check Apple's Adaptivity and Sizes guide:

Adaptive mode intro

Build adaptive interface using InterfaceBuilder

Different portrait and landscape layouts with one xib?

You can do this by using Vary for Traits - if you're not familiar with that, there are plenty of tutorials out there. This is probably a good starting point: https://www.raywenderlich.com/492-adaptive-layout-tutorial-in-ios-11-getting-started

Essentially, you would start with standard phone portrait orientation: wC hR

  • Set top, leading, trailing and height constraints on Purple view.
  • Set top, leading, width and height constraints of Zero for Red view.
  • Set top, leading, width and height constraints of Zero for Blue view.

Change to landscape orientation: wC hC

  • Add constraints to get your landscape layout, and delete any portrait layout constraints that are no longer needed.

Here is a xib file with the constraints setup to get your desired result. Create a new xib, open it as source code, replace the entire source, and then re-open it as Interface Builder XIB Document:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TraitTestView" customModule="SW4Temp" customModuleProvider="target"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Purple" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="x2I-Wo-fEl">
<rect key="frame" x="0.0" y="0.0" width="375" height="333.5"/>
<color key="backgroundColor" red="0.45490196078431372" green="0.52156862745098043" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="26"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Red" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="w2j-KG-F8a">
<rect key="frame" x="375" y="0.0" width="0.0" height="0.0"/>
<color key="backgroundColor" red="0.97254901960784312" green="0.40392156862745099" blue="0.38039215686274508" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" id="AXD-eQ-1h9"/>
<constraint firstAttribute="width" id="tez-lx-gm1"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="26"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<variation key="heightClass=compact">
<mask key="constraints">
<exclude reference="AXD-eQ-1h9"/>
<exclude reference="tez-lx-gm1"/>
</mask>
</variation>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Blue" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sXo-w2-aKd">
<rect key="frame" x="0.0" y="667" width="0.0" height="0.0"/>
<color key="backgroundColor" red="0.17254901960784313" green="0.59607843137254901" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" id="jIU-Ld-Txe"/>
<constraint firstAttribute="height" id="wOE-gF-bPQ"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="26"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<variation key="heightClass=compact">
<mask key="constraints">
<exclude reference="jIU-Ld-Txe"/>
<exclude reference="wOE-gF-bPQ"/>
</mask>
</variation>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="sXo-w2-aKd" secondAttribute="bottom" id="1T3-Jy-K0Z"/>
<constraint firstItem="x2I-Wo-fEl" firstAttribute="height" secondItem="iN0-l3-epB" secondAttribute="height" multiplier="0.5" id="8cP-nw-Azc"/>
<constraint firstItem="w2j-KG-F8a" firstAttribute="height" secondItem="x2I-Wo-fEl" secondAttribute="height" id="96S-1z-Dye"/>
<constraint firstItem="sXo-w2-aKd" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="Bd2-O2-2Sf"/>
<constraint firstAttribute="trailing" secondItem="x2I-Wo-fEl" secondAttribute="trailing" id="Btf-sR-nFF"/>
<constraint firstItem="w2j-KG-F8a" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="Feh-7O-9tt"/>
<constraint firstAttribute="trailing" secondItem="w2j-KG-F8a" secondAttribute="trailing" id="Gp9-jJ-YcU"/>
<constraint firstItem="x2I-Wo-fEl" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="XM4-sb-5fz"/>
<constraint firstAttribute="trailing" secondItem="sXo-w2-aKd" secondAttribute="trailing" constant="375" id="Ztc-MA-Jq0">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstItem="w2j-KG-F8a" firstAttribute="width" secondItem="x2I-Wo-fEl" secondAttribute="width" id="cYb-Dp-5J7"/>
<constraint firstItem="x2I-Wo-fEl" firstAttribute="height" secondItem="iN0-l3-epB" secondAttribute="height" multiplier="2:3" id="coI-el-yOt"/>
<constraint firstItem="sXo-w2-aKd" firstAttribute="top" secondItem="x2I-Wo-fEl" secondAttribute="bottom" constant="167" id="dbt-2z-a3G">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstItem="w2j-KG-F8a" firstAttribute="leading" secondItem="x2I-Wo-fEl" secondAttribute="trailing" constant="563.5" id="eia-Ec-aAO">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstItem="x2I-Wo-fEl" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="otL-1i-mP0"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<variation key="default">
<mask key="constraints">
<exclude reference="Ztc-MA-Jq0"/>
<exclude reference="coI-el-yOt"/>
<exclude reference="dbt-2z-a3G"/>
<exclude reference="96S-1z-Dye"/>
<exclude reference="cYb-Dp-5J7"/>
<exclude reference="eia-Ec-aAO"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<exclude reference="Btf-sR-nFF"/>
<include reference="Ztc-MA-Jq0"/>
<exclude reference="8cP-nw-Azc"/>
<include reference="coI-el-yOt"/>
<include reference="dbt-2z-a3G"/>
<include reference="96S-1z-Dye"/>
<include reference="cYb-Dp-5J7"/>
<include reference="eia-Ec-aAO"/>
</mask>
</variation>
</view>
</objects>
</document>

Viewed as iPhone 8 Portrait:

Sample Image

Viewed as iPhone 8 Landscape:

Sample Image


Edit:

If you need the same layout design for both iPhone and iPad, you can add this to your view controller class. Not necessarily recommended, but it should do the trick:

override public var traitCollection: UITraitCollection {

var desiredTraits = [UITraitCollection]()

if view.bounds.width < view.bounds.height {
desiredTraits = [UITraitCollection(horizontalSizeClass: .compact), UITraitCollection(verticalSizeClass: .regular)]
} else {
desiredTraits = [UITraitCollection(horizontalSizeClass: .compact), UITraitCollection(verticalSizeClass: .compact)]
}

return UITraitCollection(traitsFrom: desiredTraits)

}

Edit 2:

If you need to work on your layout with the xib sized to iPad dimensions, you can do so.

Open the xib in IB and select one of the phones for "View as:" - I chose the iPhone 8 - and Portrait orientation:

Sample Image

You see it shows wC hR traits, and the view will look like an iPhone 8 size and shape.

Now, in the Attributes Inspector pane, change Size: Inferred to Size: Freeform:

Sample Image
Sample Image

Next, select the Size Inspector pane, and change the size of the view to 768 x 1024:

Sample Image

You now have a Portrait Orientation "iPad" xib to design and set your wC hR constraints.

When ready, select Landscape Orientation for "View as:"

Sample Image

You'll see the Traits change to wC hC -- but you won't see any change in your design view. So, go back to the Size Inspector pane and change the size to 1024 x 768:

Sample Image

Your design view now looks like an iPad in Landscape Orientation, and you can select Vary for Traits to configure your desired wC hC layout.

While designing, you'll need to manually change the size each time you change the orientation, but you will see the trait changes.

Now, Portrait in IB:

Sample Image

and, Landscape in IB:

Sample Image

Xcode autolayout constraint landscape orientation issue where view shrinks to zero size

Different devices have different size classes.

For example, an iPhone 13 Pro Max uses:

wC hR   // portrait orientation
wR hC // landscape orientation

whereas an iPhone 8 uses:

wC hR   // portrait orientation
wC hC // landscape orientation

Note that landscape is wC hC, not wR hC.

Change your "landscape" variation to Width: Any Height: Compact

Sample Image

See the Apple docs here for a list of devices and size classes.

Setting proper Constraints to UIView in Portrait and Landscape

select landscape option (below the screen near to setting auto layout pane) then press vary for traits , now give new constraints that you want in landscape mode then select done varying.

Now you have two different constraints for landscape and portrait mode.

Different layouts in portrait and landscape mode

Note - the answers here are good and do address the problem, but on older versions of iOS.

For iOS11 (Xcode 9) you should consider Adaptive Layouts as referenced here:
https://www.raywenderlich.com/162311/adaptive-layout-tutorial-ios-11-getting-started

Different sets of auto layout constraints for different orientation

The approach you're using is certainly possible, but will involve quite a bit of code to get the layout you're looking for. An easier way to to this would be to delete your controller's view in the storyboard (you can copy and paste it into the xib file if you want), and instead make its views, one each for portrait and landscape, in a xib file. You can do all you constraint setup there, and only have to switch between the two views in code. You can create both views in one xib file -- I created the portrait one first, so it will be the first object in the array you get back from loading the nib.

@interface ViewController ()
@property (strong,nonatomic) UIView *pView;;
@property (strong,nonatomic) UIView *lView;
@property (strong,nonatomic) UILabel *leftLabel;
@property (strong,nonatomic) UILabel *rightLabel;
@end

@implementation ViewController

- (void)loadView {
NSArray *views = [[NSBundle mainBundle] loadNibNamed:@"PortraitAndLandscapeViews" owner:self options:nil];
self.pView = views[0];
self.lView = views[1];
self.view = self.pView;
}

-(void)viewWillLayoutSubviews {
BOOL layoutIsPortrait = UIDeviceOrientationIsPortrait(self.interfaceOrientation);
if (layoutIsPortrait) {
self.view = self.pView;
self.leftLabel = [self.view viewWithTag:11];
self.rightLabel = [self.view viewWithTag:12];
}else{
self.view = self.lView;
self.leftLabel = [self.view viewWithTag:21];
self.rightLabel = [self.view viewWithTag:22];
}
}

Autolayout different constraints for landscape and portrait mode

i need to uninstall and add new constraints after user rotate device from landscape to portrait. I tried rather "brutal" solution, it did change constraints, but, in fraction of second i could see "old" constraints.

But that's the problem: you are changing the constraints after the user rotates the device. So you are the one who is causing the phenomenon you don't like, i.e. that this happens a fraction of a second after the rotation is over.

The correct solution is to change constraints before the user rotates the device. If you do that, the change of constraints will be magically animated to match the rotation and the change will appear absolutely seamless.

For example, if this is an iPhone app, you can implement the view controller's willTransitionToTraitCollection... and do your constraint changes there, and the change will be magically matched to the rotation.



Related Topics



Leave a reply



Submit