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.
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.
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:
Viewed as iPhone 8 Landscape:
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:
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
:
Next, select the Size Inspector pane, and change the size of the view to 768 x 1024
:
You now have a Portrait Orientation "iPad" xib to design and set your wC hR
constraints.
When ready, select Landscape Orientation for "View as:"
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
:
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:
and, Landscape in IB:
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
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
How to Create a String with Format
iOS Scrollview Needs Constraint for Y Position or Height
How to Delay a Method Call for 1 Second
How to Add 2 Buttons into the Uinavigationbar on the Right Side Without Ib
When Do I Need to Call Setneedsdisplay in iOS
Uibutton Touch Is Delayed When in Uiscrollview
Swift Modal View Controller with Transparent Background
How to Open Maps App Programmatically with Coordinates in Swift
iOS Convert Large Numbers to Smaller Format
Xcode 6 with Swift Super Slow Typing and Autocompletion
Uiscrollview with Centered Uiimageview, Like Photos App
Add Shadow on Uiview Using Swift 3
Xcode Suddenly Stopped Running Project on Hardware: "Could Not Launch Xxx.App: .. No Such File.."
Getting the Value of a Uitextfield as Keystrokes Are Entered
Swift Formatting String to Have 2 Decimal Numbers If It Is Not a Whole Number