当 UITextView 位于 UIScrollView 内时,如何使整个页面可滚动?

How to make the entire page scrollable, when UITextView is inside UIScrollView?

目前我的布局设计如下

[Safe Area]

    [Scroll View (In green color)]
        
        [Custom View (In red color)]
            
            [Horizontal Stack View]
                [Button 1]
                [Button 2]
            
            [Text View]

    [Bottom Toolbar]

它的设计是为了随着 [Text View] 内容的增长,用户可以垂直滚动 [Text View] 以及按钮组 ([Horizontal Stack View])

假设如下所示

我们认为我们在 [Custom View][Scroll View]

之间使用了正确的约束
Custom View.top = Content Layout Guide.top
Custom View.trailing = Content Layout Guide.trailing
Custom View.leading = Content Layout Guide.leading
Custom View.bottom = Content Layout Guide.bottom
Custom View.width = Frame Layout Guide.width

我们从 Xcode 得到的一个警告是

Scrollable content size is ambiguous for "Scroll View".

因此,为了避免这种情况,我们需要添加

Custom View.centerX = Frame Layout Guide.centerX
Custom View.centerY = Frame Layout Guide.centerY

然而,当我们执行应用程序时,当文本内容增长时,只有 [Text View] 是垂直滚动的。 顶部按钮组保持不变。

我们尝试禁用 [Text View] 本身的滚动行为。同样,当文本内容增长时,整个页面不可滚动。

您是否知道如何解决此问题,以便当文本内容变大时,顶部按钮组可以与文本视图一起滚动?

演示位于https://github.com/yccheok/ios-tutorial/tree/learn-scroll-view/NavigationController

滚动滚动一直是一件棘手的事情。我的建议 -

如果不可编辑,请不要使用UITextView,使用UILabel

计算文本的高度并使文本视图与其相等并禁用滚动。这样你最终将只有一个可滚动的视图。

看起来你很接近...

UITextView 绝对应该禁用滚动。这将导致它在用户键入时垂直 grow/shrink。

我认为您缺少的关键点是文本视图的底部约束。

这是它的布局方式 - 我对元素使用了 8 磅“填充”:

故事板的来源如下:

<?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="22Q-Va-uW9">
    <device id="retina6_1" 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>
        <!--Text View Scroll View Controller-->
        <scene sceneID="cwD-Ry-Rln">
            <objects>
                <viewController id="22Q-Va-uW9" customClass="TextViewScrollViewController" customModule="PanZoom" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="FK3-2k-kFr">
                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9i4-uC-ifa">
                                <rect key="frame" x="0.0" y="88" width="414" height="725"/>
                                <subviews>
                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cmb-lb-s1V" userLabel="GreenView">
                                        <rect key="frame" x="0.0" y="0.0" width="414" height="220.5"/>
                                        <subviews>
                                            <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="dtt-fC-qBS" userLabel="ButtonsStack">
                                                <rect key="frame" x="8" y="8" width="398" height="30"/>
                                                <subviews>
                                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GFD-bo-TQG">
                                                        <rect key="frame" x="0.0" y="0.0" width="195" height="30"/>
                                                        <color key="backgroundColor" red="0.83741801979999997" green="0.83743780850000005" blue="0.83742713930000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <state key="normal" title="Button 1"/>
                                                    </button>
                                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G0H-uo-Hhv">
                                                        <rect key="frame" x="203" y="0.0" width="195" height="30"/>
                                                        <color key="backgroundColor" red="0.83741801979999997" green="0.83743780850000005" blue="0.83742713930000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                        <state key="normal" title="Button 2"/>
                                                    </button>
                                                </subviews>
                                            </stackView>
                                            <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="31s-od-JSj">
                                                <rect key="frame" x="8" y="46" width="398" height="166.5"/>
                                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                                <string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
                                                <color key="textColor" systemColor="labelColor"/>
                                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                                <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                                            </textView>
                                        </subviews>
                                        <color key="backgroundColor" red="0.97629755740000002" green="0.25518852469999997" blue="0.1867151558" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                        <constraints>
                                            <constraint firstAttribute="bottom" secondItem="31s-od-JSj" secondAttribute="bottom" constant="8" id="48L-4C-2qT"/>
                                            <constraint firstAttribute="trailing" secondItem="31s-od-JSj" secondAttribute="trailing" constant="8" id="8Fk-Hs-oE5"/>
                                            <constraint firstItem="31s-od-JSj" firstAttribute="top" secondItem="dtt-fC-qBS" secondAttribute="bottom" constant="8" id="FBX-A2-9r1"/>
                                            <constraint firstItem="dtt-fC-qBS" firstAttribute="top" secondItem="cmb-lb-s1V" secondAttribute="top" constant="8" id="ReQ-rG-pxE"/>
                                            <constraint firstAttribute="trailing" secondItem="dtt-fC-qBS" secondAttribute="trailing" constant="8" id="TNF-MC-1Fo"/>
                                            <constraint firstItem="dtt-fC-qBS" firstAttribute="leading" secondItem="cmb-lb-s1V" secondAttribute="leading" constant="8" id="cHU-IF-eqx"/>
                                            <constraint firstItem="31s-od-JSj" firstAttribute="leading" secondItem="cmb-lb-s1V" secondAttribute="leading" constant="8" id="vzb-fw-4rS"/>
                                        </constraints>
                                    </view>
                                </subviews>
                                <color key="backgroundColor" red="0.045027168950000002" green="0.85423937179999998" blue="0.076285673880000002" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                                <constraints>
                                    <constraint firstItem="cmb-lb-s1V" firstAttribute="trailing" secondItem="vj7-x9-CG7" secondAttribute="trailing" id="Ify-cz-Rri"/>
                                    <constraint firstItem="cmb-lb-s1V" firstAttribute="leading" secondItem="vj7-x9-CG7" secondAttribute="leading" id="Sup-Q0-buq"/>
                                    <constraint firstItem="cmb-lb-s1V" firstAttribute="top" secondItem="vj7-x9-CG7" secondAttribute="top" id="anR-gY-fDu"/>
                                    <constraint firstItem="cmb-lb-s1V" firstAttribute="width" secondItem="n5I-JV-9MK" secondAttribute="width" id="qXA-46-Ghy"/>
                                    <constraint firstItem="cmb-lb-s1V" firstAttribute="bottom" secondItem="vj7-x9-CG7" secondAttribute="bottom" id="xfn-l1-QHb"/>
                                </constraints>
                                <viewLayoutGuide key="contentLayoutGuide" id="vj7-x9-CG7"/>
                                <viewLayoutGuide key="frameLayoutGuide" id="n5I-JV-9MK"/>
                            </scrollView>
                        </subviews>
                        <viewLayoutGuide key="safeArea" id="0PY-SX-dfw"/>
                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                        <constraints>
                            <constraint firstItem="9i4-uC-ifa" firstAttribute="leading" secondItem="0PY-SX-dfw" secondAttribute="leading" id="VfX-uv-BcR"/>
                            <constraint firstItem="9i4-uC-ifa" firstAttribute="top" secondItem="0PY-SX-dfw" secondAttribute="top" id="nrt-pL-ZXy"/>
                            <constraint firstItem="9i4-uC-ifa" firstAttribute="bottom" secondItem="0PY-SX-dfw" secondAttribute="bottom" id="tLV-GX-hce"/>
                            <constraint firstItem="9i4-uC-ifa" firstAttribute="trailing" secondItem="0PY-SX-dfw" secondAttribute="trailing" id="v8d-TH-4oS"/>
                        </constraints>
                    </view>
                    <simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
                    <simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
                    <connections>
                        <outlet property="theScrollView" destination="9i4-uC-ifa" id="Wq7-j8-nud"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="QQ0-6m-9TV" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="331.8840579710145" y="162.72321428571428"/>
        </scene>
    </scenes>
    <resources>
        <systemColor name="labelColor">
            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
    </resources>
</document>

和一个带键盘处理的示例视图控制器:

class TextViewScrollViewController: UIViewController {

    @IBOutlet var theScrollView: UIScrollView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // button to end editing
        let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.doneTapped(_:)))
        self.navigationItem.rightBarButtonItem = btn
        
        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
        notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)

    }
    
    @objc func doneTapped(_ sender: Any?) -> Void {
        view.endEditing(true)
    }
    
    @objc func adjustForKeyboard(notification: Notification) {
        guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
        
        let keyboardScreenEndFrame = keyboardValue.cgRectValue
        let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
        
        if notification.name == UIResponder.keyboardWillHideNotification {
            theScrollView.contentInset = .zero
        } else {
            // bottom padding to keep textView above keyboard - adjust as desired
            let padding: CGFloat = 16
            theScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - (view.safeAreaInsets.bottom - padding), right: 0)
        }
        
        theScrollView.scrollIndicatorInsets = theScrollView.contentInset
        
    }
    
}