有几种方法可以做到这一点...
我建议您将“切换容器视图”置于滚动视图之外,固定在滚动视图的底部。当滚动视图的内容(多行标签)改变高度时,我们将改变滚动视图的高度——直到开关容器到达视图的底部(安全-区域底部)。
它在 Storyboard 中的外观如下:
多行标签背景为青色,开关容器视图背景为黄色。
请注意,我们已将滚动视图的高度限制为多行标签的高度,但将优先级设置为750 (High)。通过将其从Required更改为High,我们告诉自动布局在需要时打破该约束。也就是说,当它对于可用空间来说太高时。
还要注意,开关容器视图的底部约束设置为>= 0。当我们将文本添加到多行标签时,它会导致滚动视图的高度增加将容器视图向下推 ...但直到它到达视图的底部(安全区域)。
我们在这里看不到的唯一一件事 - 但它很重要 - 是我们将多行标签和 Switch 的 Vertical Content Compression Resistance 设置为 1000 (Required)。
这是该故事板的来源,您可以查看它:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="XCt-Hc-wQm">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<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 Footer View Controller-->
<scene sceneID="Caf-Eb-sIu">
<objects>
<viewController id="XCt-Hc-wQm" customClass="ScrollFooterViewController" customModule="DelMe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="dUm-Xz-ZMW">
<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="USX-Ck-yG1">
<rect key="frame" x="0.0" y="0.0" width="375" height="21.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lwg-yg-39D">
<rect key="frame" x="0.0" y="0.0" width="375" height="21.5"/>
<color key="backgroundColor" red="0.45138680930000002" green="0.99309605359999997" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" secondItem="lwg-yg-39D" secondAttribute="height" priority="750" id="76J-30-uTn"/>
<constraint firstItem="lwg-yg-39D" firstAttribute="trailing" secondItem="ZIz-K9-t2b" secondAttribute="trailing" id="GCE-Kl-AnT"/>
<constraint firstItem="lwg-yg-39D" firstAttribute="leading" secondItem="ZIz-K9-t2b" secondAttribute="leading" id="KKY-k0-5P6"/>
<constraint firstItem="lwg-yg-39D" firstAttribute="top" secondItem="ZIz-K9-t2b" secondAttribute="top" id="MY4-LC-VHJ"/>
<constraint firstItem="lwg-yg-39D" firstAttribute="bottom" secondItem="ZIz-K9-t2b" secondAttribute="bottom" id="dnk-39-RlU"/>
<constraint firstItem="lwg-yg-39D" firstAttribute="width" secondItem="PfO-IK-xkv" secondAttribute="width" id="zrQ-RH-rly"/>
</constraints>
<viewLayoutGuide key="contentLayoutGuide" id="ZIz-K9-t2b"/>
<viewLayoutGuide key="frameLayoutGuide" id="PfO-IK-xkv"/>
</scrollView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Njo-Q9-KiY" userLabel="ContainerView">
<rect key="frame" x="0.0" y="21.5" width="375" height="47"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vUU-Ps-Crk" userLabel="SwitchLabel">
<rect key="frame" x="8" y="13" width="42" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="24L-2e-H7p">
<rect key="frame" x="318" y="8" width="51" height="31"/>
</switch>
</subviews>
<color key="backgroundColor" red="0.99953407049999998" green="0.98835557699999999" blue="0.47265523669999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUU-Ps-Crk" firstAttribute="centerY" secondItem="24L-2e-H7p" secondAttribute="centerY" id="Dwk-0c-XGq"/>
<constraint firstAttribute="trailing" secondItem="24L-2e-H7p" secondAttribute="trailing" constant="8" id="Vuj-Vf-Ubm"/>
<constraint firstItem="vUU-Ps-Crk" firstAttribute="leading" secondItem="Njo-Q9-KiY" secondAttribute="leading" constant="8" id="VzC-He-6eA"/>
<constraint firstItem="24L-2e-H7p" firstAttribute="top" secondItem="Njo-Q9-KiY" secondAttribute="top" constant="8" id="xVS-9o-J0n"/>
<constraint firstAttribute="bottom" secondItem="24L-2e-H7p" secondAttribute="bottom" constant="8" id="yR9-XP-FQq"/>
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="y57-Pg-ijO"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="y57-Pg-ijO" firstAttribute="trailing" secondItem="Njo-Q9-KiY" secondAttribute="trailing" id="7PH-bB-Xu1"/>
<constraint firstItem="USX-Ck-yG1" firstAttribute="top" secondItem="y57-Pg-ijO" secondAttribute="top" id="RRh-cW-cdn"/>
<constraint firstItem="y57-Pg-ijO" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Njo-Q9-KiY" secondAttribute="bottom" id="ZhG-3Y-WiX"/>
<constraint firstItem="Njo-Q9-KiY" firstAttribute="leading" secondItem="y57-Pg-ijO" secondAttribute="leading" id="aj2-fS-rab"/>
<constraint firstItem="Njo-Q9-KiY" firstAttribute="top" secondItem="USX-Ck-yG1" secondAttribute="bottom" id="eeo-Zi-yar"/>
<constraint firstItem="USX-Ck-yG1" firstAttribute="trailing" secondItem="y57-Pg-ijO" secondAttribute="trailing" id="iK0-ax-YU6"/>
<constraint firstItem="USX-Ck-yG1" firstAttribute="leading" secondItem="y57-Pg-ijO" secondAttribute="leading" id="lMU-A2-QsG"/>
</constraints>
</view>
<connections>
<outlet property="multiLineLabel" destination="lwg-yg-39D" id="huD-0B-amT"/>
<outlet property="scrollView" destination="USX-Ck-yG1" id="2c5-7h-7Ii"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="cjI-ra-ksU" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1586.4000000000001" y="266.71664167916043"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
以及一些示例代码来演示:
class ScrollFooterViewController: UIViewController {
@IBOutlet var scrollView: UIScrollView!
@IBOutlet var multiLineLabel: UILabel!
var numLines: Int = 0
var addLine: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// add a tap recognizer...
// each time we tap the view, we'll add text from the sample text array
// until we reach the end of the array
// then, each tap will remove text until we reach 1
let t = UITapGestureRecognizer(target: self, action: #selector(self.updateMultilineLabel))
view.addGestureRecognizer(t)
// init the multiline label text
updateMultilineLabel()
}
@objc func updateMultilineLabel() -> Void {
// are we adding or removing text?
if addLine == true {
numLines += 1
} else {
numLines -= 1
// keep at least 1 line of text
numLines = max(numLines, 1)
}
// generate string with numLines of text from sample text array
let s = sampleText[0..<numLines].joined(separator: "\n\n")
// set text of multiline label
multiLineLabel.text = s
// auto-scroll to the bottom
scrollToBottom(scrollView)
// if we're at the end of the sample text array, switch from adding to removing
if numLines == sampleText.count {
addLine = false
}
// if we're at 1, switch from removing to adding
if numLines == 1 {
addLine = true
}
}
func scrollToBottom(_ scrollView: UIScrollView) -> Void {
// set content offset after 0.01 seconds (to make sure it runs after the label has updated its frame)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01, execute: {
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
if(bottomOffset.y > 0) {
scrollView.setContentOffset(bottomOffset, animated: true)
}
})
}
let sampleText: [String] = [
"UILabel\n\nA label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label.",
"UIButton\n\nYou can set the title, image, and other appearance properties of a button. In addition, you can specify a different appearance for each button state.",
"UISegmentedControl\n\nThe segments can represent single or multiple selection, or a list of commands.\n\nEach segment can display text or an image, but not both.",
"UITextField\n\nDisplays a rounded rectangle that can contain editable text. When a user taps a text field, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text field can handle the input in an application-specific way. UITextField supports overlay views to display additional information, such as a bookmarks icon. UITextField also provides a clear text control a user taps to erase the contents of the text field.",
"UISlider\n\nUISlider displays a horizontal bar, called a track, that represents a range of values. The current value is shown by the position of an indicator, or thumb. A user selects a value by sliding the thumb along the track. You can customize the appearance of both the track and the thumb.",
"UISwitch\n\nDisplays an element that shows the user the boolean state of a given value. By tapping the control, the state can be toggled.",
]
}
代码有一个“示例文本”数组...每次点击屏幕时,多行标签会添加更多文本,直到我们到达数组末尾,此时每次点击都会删除一些文字。
结果如下:
还有一点文字,但还不足以需要滚动:
并且有足够的文本,我们需要滚动: