多行按钮的自动布局问题

Autolayout problem with multiline buttons

我有一个 UIScrollview 和一个内容视图,其中包含 1 个 UILabel 和 4 个 UIButton,它们的高度基于内容。请看下图。

现在在界面生成器中使用自动布局,我想将 UILabel 定位在 contentView 的顶部,并将按钮组定位在 contentView 的底部。记住这一点,我想在 UILabel 和第一个 UIButton 之间允许 >=70px 的垂直间距,如果标签或按钮有大量文本超出 UIScrollView 的可见区域,则让滚动开始。

我尝试使用标签和按钮作为 contentview 的子视图以及将 UIStackView 作为 UIScrollview 的子视图来实现此目的,其中包含 UILabel 作为第一个元素,以及包含按钮的 Inner Stackview 作为另一个元素。但是,我不确定如何应用 >=70 垂直间距。提前致谢。

方法...

  • 将顶部标签限制在“contentView”的顶部
  • 将带有按钮的 stackView 限制在“contentView”的底部
  • 从顶部标签底部约束 stackView Top >= 70
  • 将“contentView”的高度限制为 >= 滚动视图框架布局指南的高度
  • 将“contentView”的高度限制为等于滚动视图框架布局指南的高度,但优先级较低
  • 您通常设置的所有其他约束

它可能是这样的:

并且,在 run-time,有几组不同的文本/标题:

这是该控制器的故事板来源:

<?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>

和一些示例代码 - 点击任何按钮将循环显示几组数据:

@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
    }
    
}