Home > Net >  How to scale margin based on screen sizes with auto layout?
How to scale margin based on screen sizes with auto layout?

Time:03-24

Imagine I have a UIImageView with a certain aspect ratio constraint (this handles the height/bottom constraint), pinned to superview's top, leading, trailing with 5pt in a 100pt width screen in XIB/storyboard. This way, if the screen size changes, the image view can scale accordingly.

That is 5pt 5pt (10pt) of margin spacing in a 100pt width screen, with image taking up 90pt (90% of the screen).

However I would like the margin spacing to be scaled as well.

How can I make it such that, on a bigger phone, e.g. with width 1000pt (10x previous), the margin will now correspondingly be 50pt 50pt (100pt) with the image taking up the remaining 900pt of the screen, retaining 90%? (This could be further complicated if we are making the horizontal constraints different, e..g. leading be 15pt while the trailing be 45pt.)

If we do it 'normally' without scaling the margin pt, the constraints of leading/trailing would be 5pt (for a total of 10pt in a 1000pt or even a 1000000pt width phone!). This is not what we usually want.

How can we achieve this easily with auto layout? I thought of class sizes but that does not fit what I am trying to do (to cater to all devices of any size).

CodePudding user response:

ASFAIK there is no way to define leading or trailing constraints relatively to another view's width.

To achieve what you want you can set the following constraints on the UIImageView:

  1. A constant top constraint
  2. A centerX constraint to center the image horizontally
  3. A constant aspect ratio constraint with your desired aspect ratio
  4. A width constraint with the same width as the UIImageView's superview and a multiplier of 0.9 (90%)

When you do this, the horizontal margins of the image will always be 10% of the screen size.

enter image description here

CodePudding user response:

Whether laying this out in Storyboard / IB or doing it via code, probably the easiest way is to use a horizontal stack view, with a (clear) "spacer" view on each side.

What you have to decide in advance, though, is the desired proportion.

That is, we say the leading space should be 5-pts based on a total width of 100-pts ... or 25-pts based on 150-pts ... etc.

So our stack view will have 3 arranged subviews:

  • "LeftSpacer"
  • imageView (with, let's say, a 640:360 ratio)
  • "RightSpacer"

Constrain the Width of the LeftSpacer to the width of the stack view, with a multiplier of (left space)/100.

Constrain the Width of the RightSpacer to the width of the stack view, with a multiplier of (right space)/100.

Here are two examples.

The Top stack view has both left and right spacers set to 5/100. The Bottom stack view has Left: 5/100 and Right: 45/100. In both stack views, the image view is set to 640:360 aspect ratio (because that's the image I had handy).

This is how it looks with stack view Width: 100:

enter image description here

Top example - both spacer views are 5-pts, image view is 90-pts

Bottom example - left spacer: 5-pts, right: 45-pts, image view: 50-pts

and stack view Width: 200:

enter image description here

Top example - both spacer views are 10-pts, image view is 180-pts

Bottom example - left spacer: 10-pts, right: 90-pts, image view: 100-pts

and stack view Width: 300:

enter image description here

Top example - both spacer views are 15-pts, image view is 270-pts

Bottom example - left spacer: 15-pts, right: 135-pts, image view: 150-pts

This is the Document Outline:

enter image description here

and the Storyboard source if you want to inspect it:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina4_0" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--SomeVC-->
        <scene sceneID="YpP-O8-TWi">
            <objects>
                <viewController id="P90-ov-dhs" customClass="SomeVC" customModule="TabBased" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="Afn-4E-CtW">
                        <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="28" translatesAutoresizingMaskIntoConstraints="NO" id="EZB-9U-TcY">
                                <rect key="frame" x="10" y="20" width="300" height="264.5"/>
                                <subviews>
                                    <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wTh-HR-1cy">
                                        <rect key="frame" x="0.0" y="0.0" width="300" height="152"/>
                                        <subviews>
                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WVJ-wZ-t9Z" userLabel="LeftSpacerView">
                                                <rect key="frame" x="0.0" y="0.0" width="15" height="152"/>
                                                <color key="backgroundColor" systemColor="systemYellowColor"/>
                                            </view>
                                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bkg640x360" translatesAutoresizingMaskIntoConstraints="NO" id="39U-sA-bbn">
                                                <rect key="frame" x="15" y="0.0" width="270" height="152"/>
                                                <constraints>
                                                    <constraint firstAttribute="width" secondItem="39U-sA-bbn" secondAttribute="height" multiplier="640:360" id="soT-yc-UP0"/>
                                                </constraints>
                                            </imageView>
                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Tww-ar-yol" userLabel="RightSpacerView">
                                                <rect key="frame" x="285" y="0.0" width="15" height="152"/>
                                                <color key="backgroundColor" systemColor="systemYellowColor"/>
                                            </view>
                                        </subviews>
                                        <constraints>
                                            <constraint firstItem="Tww-ar-yol" firstAttribute="width" secondItem="wTh-HR-1cy" secondAttribute="width" multiplier="5/100" id="DLh-N4-vfJ"/>
                                            <constraint firstItem="WVJ-wZ-t9Z" firstAttribute="width" secondItem="wTh-HR-1cy" secondAttribute="width" multiplier="5/100" id="qYi-Rk-ZOI"/>
                                        </constraints>
                                    </stackView>
                                    <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TsK-Kw-YRa">
                                        <rect key="frame" x="0.0" y="180" width="300" height="84.5"/>
                                        <subviews>
                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="iw4-he-3pJ" userLabel="LeftSpacerView">
                                                <rect key="frame" x="0.0" y="0.0" width="15" height="84.5"/>
                                                <color key="backgroundColor" systemColor="systemYellowColor"/>
                                            </view>
                                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bkg640x360" translatesAutoresizingMaskIntoConstraints="NO" id="P4f-Bc-9F2">
                                                <rect key="frame" x="15" y="0.0" width="150" height="84.5"/>
                                                <constraints>
                                                    <constraint firstAttribute="width" secondItem="P4f-Bc-9F2" secondAttribute="height" multiplier="640:360" id="ZqY-ss-ToB"/>
                                                </constraints>
                                            </imageView>
                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Fs5-7r-dME" userLabel="RightSpacerView">
                                                <rect key="frame" x="165" y="0.0" width="135" height="84.5"/>
                                                <color key="backgroundColor" systemColor="systemYellowColor"/>
                                            </view>
                                        </subviews>
                                        <constraints>
                                            <constraint firstItem="iw4-he-3pJ" firstAttribute="width" secondItem="TsK-Kw-YRa" secondAttribute="width" multiplier="5/100" id="D5C-Yo-YOh"/>
                                            <constraint firstItem="Fs5-7r-dME" firstAttribute="width" secondItem="TsK-Kw-YRa" secondAttribute="width" multiplier="45/100" id="ajJ-GY-bZW"/>
                                        </constraints>
                                    </stackView>
                                </subviews>
                                <constraints>
                                    <constraint firstAttribute="width" constant="300" id="Hel-XG-PhB"/>
                                </constraints>
                            </stackView>
                        </subviews>
                        <viewLayoutGuide key="safeArea" id="Ur7-hW-kek"/>
                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                        <constraints>
                            <constraint firstItem="EZB-9U-TcY" firstAttribute="centerX" secondItem="Afn-4E-CtW" secondAttribute="centerX" id="5lZ-RK-pgy"/>
                            <constraint firstItem="EZB-9U-TcY" firstAttribute="top" secondItem="Ur7-hW-kek" secondAttribute="top" constant="20" id="bg6-Jx-gi9"/>
                        </constraints>
                    </view>
                    <navigationItem key="navigationItem" id="ZhN-1l-tl9"/>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="Rgh-e1-NRf" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-118.125" y="1365"/>
        </scene>
    </scenes>
    <resources>
        <image name="bkg640x360" width="640" height="360"/>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <systemColor name="systemYellowColor">
            <color red="1" green="0.80000000000000004" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
        </systemColor>
    </resources>
</document>

Obviously, that would be straightforward to replicate in code.

If, for some reason, you don't want to use stack views, you could do essentially the same thing with leading/trailing and proportional width constraints on two "side spacer views" or, to make it just a little more light-weight, use ConstraintLayoutGuide instead of a clear view.

  • Related