In Xcode, I'm trying to place a label over an image using auto layout with Interface Builder. The point is to have the image scale to the different devices, while also scaling the label, so that it still remains in the same position over the image.
It is a very similar question to this, minus the button:
It can be any size, and can have @2x / @3x sizes... the important thing is that we know its aspect ratio. In this case, my image is 600 x 800
, which is a 3:4
ratio.
We want to setup our layout for the widest expected size -- so we'll use iPhone 13 Pro Max.
Embed the imageView and the label in a "holder view." We'll be setting the label y-position relative to the bottom of the imageView, and this will keep it constant regardless of position on screen.
Make sure the holder view has an aspect-ratio constraint matching the image, and constrain the imageView at Zero to all four sides of the holder view.
Set the font so it fits nicely in the "label area." Constrain its width proportionally to the imageView. In this case, a multiplier of 0.85
works well.
Constrain the label's CenterY to the imageView's bottom, with a multiplier that puts it in place. In this case, 0.25
works.
Enable Autoshrink on the label, with Minimum Font Scale of 0.25 (even though it's unlikely it will ever get that small).
Important: Set the label's Baseline to Align Centers
... that will keep the text vertically aligned where we want it.
Here's how it looks in Storyboard:
and at a couple different widths at run-time:
Here's the source for the Storyboard so you can inspect the constraints and element properties:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Xg6-6D-sKc">
<device id="retina6_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<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>
<!--Timer View Controller-->
<scene sceneID="gRg-mL-Zeo">
<objects>
<viewController id="Xg6-6D-sKc" customClass="TimerViewController" customModule="SW15Scratch" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="wLy-zd-hy6">
<rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XHF-SM-3c4">
<rect key="frame" x="0.0" y="177.66666666666669" width="428" height="570.66666666666652"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bkg" translatesAutoresizingMaskIntoConstraints="NO" id="2Xc-0o-ie7" userLabel="ImageView">
<rect key="frame" x="0.0" y="0.0" width="428" height="570.66666666666663"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="center" lineBreakMode="tailTruncation" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="TYc-HH-6id">
<rect key="frame" x="32" y="100.33333333333334" width="364" height="85"/>
<fontDescription key="fontDescription" name="CourierNewPS-BoldMT" family="Courier New" pointSize="75"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemYellowColor"/>
<constraints>
<constraint firstItem="TYc-HH-6id" firstAttribute="centerX" secondItem="2Xc-0o-ie7" secondAttribute="centerX" id="Mca-tA-1Yt"/>
<constraint firstAttribute="trailing" secondItem="2Xc-0o-ie7" secondAttribute="trailing" id="Vgz-B5-aq3"/>
<constraint firstItem="TYc-HH-6id" firstAttribute="centerY" secondItem="2Xc-0o-ie7" secondAttribute="bottom" multiplier="0.25" id="ZVs-Ut-o2V"/>
<constraint firstItem="TYc-HH-6id" firstAttribute="width" secondItem="2Xc-0o-ie7" secondAttribute="width" multiplier="0.85" id="h5u-Vx-VIZ"/>
<constraint firstAttribute="width" secondItem="XHF-SM-3c4" secondAttribute="height" multiplier="3:4" id="hD5-0n-cot"/>
<constraint firstAttribute="bottom" secondItem="2Xc-0o-ie7" secondAttribute="bottom" id="u7H-o7-HPd"/>
<constraint firstItem="2Xc-0o-ie7" firstAttribute="leading" secondItem="XHF-SM-3c4" secondAttribute="leading" id="v8V-GX-3Aj"/>
<constraint firstItem="2Xc-0o-ie7" firstAttribute="top" secondItem="XHF-SM-3c4" secondAttribute="top" id="xKB-NI-2RP"/>
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="93z-as-uJR"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="XHF-SM-3c4" firstAttribute="centerX" secondItem="93z-as-uJR" secondAttribute="centerX" id="IfB-NL-VTA"/>
<constraint firstItem="XHF-SM-3c4" firstAttribute="width" secondItem="93z-as-uJR" secondAttribute="width" id="Vad-XH-7tx"/>
<constraint firstItem="XHF-SM-3c4" firstAttribute="centerY" secondItem="wLy-zd-hy6" secondAttribute="centerY" id="znr-ky-5ns"/>
</constraints>
</view>
<connections>
<outlet property="holderView" destination="XHF-SM-3c4" id="tIP-go-kGI"/>
<outlet property="hvWidth" destination="Vad-XH-7tx" id="OGg-sX-LqR"/>
<outlet property="theLabel" destination="TYc-HH-6id" id="End-Tr-hNP"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="4eF-f0-IGU" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1536" y="113"/>
</scene>
</scenes>
<resources>
<image name="bkg" width="300" height="400"/>
<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>
and an example controller -- each tap anywhere will reduce the width by 5%:
class TimerViewController: UIViewController {
@IBOutlet var theLabel: UILabel!
@IBOutlet var holderView: UIView!
@IBOutlet var hvWidth: NSLayoutConstraint!
// start Timer Countdown at 2-hours
var seconds: Int = 60 * 60 * 2
override func viewDidLoad() {
super.viewDidLoad()
self.updateLabel()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
self.seconds -= 1
if self.seconds < 0 {
timer.invalidate()
}
self.updateLabel()
}
}
func updateLabel() -> Void {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
if let formattedString = formatter.string(from: TimeInterval(self.seconds)) {
self.theLabel.text = formattedString
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
var m = hvWidth.multiplier
m -= 0.05
if m < 0.25 {
m = 1.0
}
hvWidth.isActive = false
hvWidth = holderView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: m)
hvWidth.isActive = true
}
}