Home > Back-end >  Autolayout problem with multiline buttons
Autolayout problem with multiline buttons

Time:11-11

I have a UIScrollview with a contentview that contains 1 UILabel and 4 UIButtons of dynamic heights based on their content. Please see image below.

enter image description here

Now using Autolayout in interface builder, I want to position the UILabel at the top of the contentView and the group of buttons at the bottom of the contentView. Keeping that in mind, I want to allow vertical spacing of >=70px between UILabel and the first UIButton and let the scrolling kick in if the label or buttons have large amount of text that goes beyond the visible area of UIScrollView.

I have tried to achieve this with label and buttons as subviews of the contentview and also with a UIStackView as a subview of the UIScrollview that contains UILabel as first element and an Inner Stackview for buttons as another element. However, I am not sure how the >=70 vertical spacing can be applied. Thanks in advance.

CodePudding user response:

The way to do this...

  • constrain the Top Label to the Top of the "contentView"
  • constrain the stackView with the buttons to the Bottom of the "contentView"
  • constraint the stackView Top >= 70 from the Top Label Bottom
  • constrain the Height of the "contentView" to >= Height of the scroll view Frame Layout Guide
  • also constrain the Height of the "contentView" equal to Height of the scroll view Frame Layout Guide, but with a low Priority
  • all other constraints as you would normally set them

Here is how it might look:

enter image description here

and, at run-time, with a couple different sets of text / titles:

enter image description here enter image description here

enter image description here enter image description here

Here is Storyboard source for that controller:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="r96-gX-wwR">
    <device id="retina4_7" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
        <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>
        <!--Scroll Example View Controller-->
        <scene sceneID="4Qx-lW-r6i">
            <objects>
                <viewController id="r96-gX-wwR" customClass="ScrollExampleViewController" customModule="QuickTest" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="S6F-6v-wFA">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fms-ps-iLx">
                                <rect key="frame" x="20" y="103.5" width="335" height="460"/>
                                <subviews>
                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="n1H-Aa-rGZ" userLabel="ContentView">
                                        <rect key="frame" x="0.0" y="0.0" width="335" height="460"/>
                                        <subviews>
                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1fs-iq-BVa">
                                                <rect key="frame" x="20" y="20" width="295" height="41"/>
                                                <color key="backgroundColor" red="0.83741801979999997" green="0.83743780850000005" blue="0.83742713930000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                <string key="text">UILabel
(number of lines = 0, wordwrap)</string>
                                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                <nil key="textColor"/>
                                                <nil key="highlightedColor"/>
                                            </label>
                                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="XxT-LJ-tha">
                                                <rect key="frame" x="40" y="198" width="255" height="242"/>
                                                <subviews>
                                                    <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0nb-3e-w4G" customClass="MultilineTitleButton" customModule="QuickTest" customModuleProvider="target">
                                                        <rect key="frame" x="0.0" y="0.0" width="255" height="45.5"/>
                                                        <color key="backgroundColor" red="0.55634254220000001" green="0.97934550050000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <inset key="contentEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
                                                        <state key="normal" title="Multiline Button 1">
                                                            <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                        </state>
                                                        <state key="highlighted">
                                                            <color key="titleColor" systemColor="systemRedColor"/>
                                                        </state>
                                                        <connections>
                                                            <action selector="didTap:" destination="r96-gX-wwR" eventType="touchUpInside" id="bYu-lb-kCi"/>
                                                        </connections>
                                                    </button>
                                                    <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ip2-SI-4IG" customClass="MultilineTitleButton" customModule="QuickTest" customModuleProvider="target">
                                                        <rect key="frame" x="0.0" y="65.5" width="255" height="45.5"/>
                                                        <color key="backgroundColor" red="0.55634254220000001" green="0.97934550050000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <inset key="contentEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
                                                        <state key="normal" title="Multiline Button 2">
                                                            <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                        </state>
                                                        <state key="highlighted">
                                                            <color key="titleColor" systemColor="systemRedColor"/>
                                                        </state>
                                                        <connections>
                                                            <action selector="didTap:" destination="r96-gX-wwR" eventType="touchUpInside" id="sra-Gt-kB6"/>
                                                        </connections>
                                                    </button>
                                                    <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x68-Yu-wG9" customClass="MultilineTitleButton" customModule="QuickTest" customModuleProvider="target">
                                                        <rect key="frame" x="0.0" y="131" width="255" height="45.5"/>
                                                        <color key="backgroundColor" red="0.55634254220000001" green="0.97934550050000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <inset key="contentEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
                                                        <state key="normal" title="Multiline Button 3">
                                                            <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                        </state>
                                                        <state key="highlighted">
                                                            <color key="titleColor" systemColor="systemRedColor"/>
                                                        </state>
                                                        <connections>
                                                            <action selector="didTap:" destination="r96-gX-wwR" eventType="touchUpInside" id="nKy-zv-DRj"/>
                                                        </connections>
                                                    </button>
                                                    <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="65G-cT-IFY" customClass="MultilineTitleButton" customModule="QuickTest" customModuleProvider="target">
                                                        <rect key="frame" x="0.0" y="196.5" width="255" height="45.5"/>
                                                        <color key="backgroundColor" red="0.55634254220000001" green="0.97934550050000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <inset key="contentEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
                                                        <state key="normal" title="Multiline Button 4">
                                                            <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                        </state>
                                                        <state key="highlighted">
                                                            <color key="titleColor" systemColor="systemRedColor"/>
                                                        </state>
                                                        <connections>
                                                            <action selector="didTap:" destination="r96-gX-wwR" eventType="touchUpInside" id="qLv-lq-4Os"/>
                                                        </connections>
                                                    </button>
                                                </subviews>
                                            </stackView>
                                        </subviews>
                                        <color key="backgroundColor" red="0.95236831899999996" green="0.86624437570000001" blue="0.86609393359999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <constraints>
                                            <constraint firstItem="XxT-LJ-tha" firstAttribute="leading" secondItem="n1H-Aa-rGZ" secondAttribute="leading" constant="40" id="BKh-Wn-7pF"/>
                                            <constraint firstItem="XxT-LJ-tha" firstAttribute="top" relation="greaterThanOrEqual" secondItem="1fs-iq-BVa" secondAttribute="bottom" constant="70" id="FRK-Bc-8jR"/>
                                            <constraint firstItem="1fs-iq-BVa" firstAttribute="top" secondItem="n1H-Aa-rGZ" secondAttribute="top" constant="20" id="MRx-Ne-RhX"/>
                                            <constraint firstItem="1fs-iq-BVa" firstAttribute="leading" secondItem="n1H-Aa-rGZ" secondAttribute="leading" constant="20" id="PjO-KU-pYX"/>
                                            <constraint firstAttribute="trailing" secondItem="1fs-iq-BVa" secondAttribute="trailing" constant="20" id="QOs-Wt-QTT"/>
                                            <constraint firstAttribute="bottom" secondItem="XxT-LJ-tha" secondAttribute="bottom" constant="20" id="VaZ-Xp-ps0"/>
                                            <constraint firstAttribute="trailing" secondItem="XxT-LJ-tha" secondAttribute="trailing" constant="40" id="hiQ-NS-nMy"/>
                                        </constraints>
                                    </view>
                                </subviews>
                                <color key="backgroundColor" systemColor="systemTealColor"/>
                                <constraints>
                                    <constraint firstItem="n1H-Aa-rGZ" firstAttribute="width" secondItem="DTH-xk-U22" secondAttribute="width" id="0qP-Qh-tIf"/>
                                    <constraint firstAttribute="height" constant="460" id="3K2-hl-Olf"/>
                                    <constraint firstItem="n1H-Aa-rGZ" firstAttribute="trailing" secondItem="kfQ-fS-efa" secondAttribute="trailing" id="4qy-gg-Xv3"/>
                                    <constraint firstItem="n1H-Aa-rGZ" firstAttribute="top" secondItem="kfQ-fS-efa" secondAttribute="top" id="HaN-b4-u0C"/>
                                    <constraint firstItem="n1H-Aa-rGZ" firstAttribute="bottom" secondItem="kfQ-fS-efa" secondAttribute="bottom" id="Ne8-Ps-1QS"/>
                                    <constraint firstItem="n1H-Aa-rGZ" firstAttribute="height" relation="greaterThanOrEqual" secondItem="DTH-xk-U22" secondAttribute="height" id="imC-1S-pfk"/>
                                    <constraint firstItem="n1H-Aa-rGZ" firstAttribute="leading" secondItem="kfQ-fS-efa" secondAttribute="leading" id="pF3-CK-PA8"/>
                                    <constraint firstItem="n1H-Aa-rGZ" firstAttribute="height" secondItem="DTH-xk-U22" secondAttribute="height" priority="250" id="qke-Uc-Q9Z"/>
                                </constraints>
                                <viewLayoutGuide key="contentLayoutGuide" id="kfQ-fS-efa"/>
                                <viewLayoutGuide key="frameLayoutGuide" id="DTH-xk-U22"/>
                            </scrollView>
                        </subviews>
                        <viewLayoutGuide key="safeArea" id="GZf-aI-xFl"/>
                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                        <constraints>
                            <constraint firstItem="fms-ps-iLx" firstAttribute="centerY" secondItem="S6F-6v-wFA" secondAttribute="centerY" id="FGd-ND-TjS"/>
                            <constraint firstItem="fms-ps-iLx" firstAttribute="leading" secondItem="GZf-aI-xFl" secondAttribute="leading" constant="20" id="cRD-FB-Z31"/>
                            <constraint firstItem="GZf-aI-xFl" firstAttribute="trailing" secondItem="fms-ps-iLx" secondAttribute="trailing" constant="20" id="yNr-VV-Wth"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="buttonStack" destination="XxT-LJ-tha" id="jgC-VB-ni5"/>
                        <outlet property="topLabel" destination="1fs-iq-BVa" id="a6p-gb-USC"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="alt-Fj-H3x" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="217" y="89"/>
        </scene>
    </scenes>
    <designables>
        <designable name="0nb-3e-w4G">
            <size key="intrinsicContentSize" width="162" height="45.5"/>
        </designable>
        <designable name="65G-cT-IFY">
            <size key="intrinsicContentSize" width="165" height="45.5"/>
        </designable>
        <designable name="Ip2-SI-4IG">
            <size key="intrinsicContentSize" width="164.5" height="45.5"/>
        </designable>
        <designable name="x68-Yu-wG9">
            <size key="intrinsicContentSize" width="165" height="45.5"/>
        </designable>
    </designables>
    <resources>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <systemColor name="systemRedColor">
            <color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
        </systemColor>
        <systemColor name="systemTealColor">
            <color red="0.35294117647058826" green="0.78431372549019607" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
        </systemColor>
    </resources>
</document>

and some example code - tapping any of the buttons will cycle through a couple sets of data:

@IBDesignable
class MultilineTitleButton: UIButton {
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    func commonInit() -> Void {
        self.titleLabel?.numberOfLines = 0
        self.titleLabel?.textAlignment = .center
        self.setContentHuggingPriority(UILayoutPriority.defaultLow   1, for: .vertical)
        self.setContentHuggingPriority(UILayoutPriority.defaultLow   1, for: .horizontal)
        layer.cornerRadius = 8
        layer.borderWidth = 1
        layer.borderColor = UIColor.black.cgColor
    }
    
    override var intrinsicContentSize: CGSize {
        let size = self.titleLabel!.intrinsicContentSize
        return CGSize(width: size.width   contentEdgeInsets.left   contentEdgeInsets.right, height: size.height   contentEdgeInsets.top   contentEdgeInsets.bottom)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
    }
    
}

class ScrollExampleViewController: UIViewController {
    
    @IBOutlet var topLabel: UILabel!
    @IBOutlet var buttonStack: UIStackView!
    
    var sampleTitles: [[String]] = [
        ["Single", "Line", "Button", "Titles"],
        ["Single", "Line", "Button", "Titles"],
        ["Five\n2\n3\n4\n5", "Line\n2\n3\n4\n5", "Button\n2\n3\n4\n5", "Titles\n2\n3\n4\n5"],
        ["Various titles", "To demonstrate the amazing capabilities of auto-layout", "This set of titles should need to scroll", "This button (well, part of it) will be below the bottom of the scroll view."],
    ]
    var infoStrings: [String] = [
        "Short Top Label (no scrolling)",
        "This is a longer string for the Top Label, so we can see it wrap onto multiple lines. Because we want at least 70-pts of vertical space to the 1st button, it should force the bottom button down far enough that we need a little bit of vertical scrolling.",
        "This example shows our buttons each with Five lines of title text (lots of vertical scrolling).",
        "This example is just to show that it continues to work when the four individual multiline title buttons have different heights.",
    ]
    
    var titleSetIdx: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        infoStrings.append(topLabel.text ?? "No text in Top Label in Storyboard")
        var storyboardTitles: [String] = []
        buttonStack.arrangedSubviews.forEach { b in
            if let btn = b as? UIButton {
                storyboardTitles.append(btn.currentTitle ?? "Missing button title in Storyboard")
            }
        }
        sampleTitles.append(storyboardTitles)
    }
    
    @IBAction func didTap(_ sender: Any) {
        topLabel.text = infoStrings[titleSetIdx % infoStrings.count]
        let a = sampleTitles[titleSetIdx % sampleTitles.count]
        for (t, b) in zip(a, buttonStack.arrangedSubviews) {
            if let btn = b as? UIButton {
                btn.setTitle(t, for: [])
            }
        }
        titleSetIdx  = 1
    }
    
}
  • Related