简体   繁体   中英

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

Currently, I have a layout design as following

[Safe Area]

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

    [Bottom Toolbar]

It is designed so that, as the content of [Text View] grow, user can scroll vertically [Text View] together with the buttons group ( [Horizontal Stack View] )

It suppose to look as the following

在此处输入图像描述

We think we have used the correct constraints, between [Custom View] and [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

One warning we are getting from Xcode is

Scrollable content size is ambiguous for "Scroll View".

Hence, to avoid such, we need to add

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

However, when we execute the app, only [Text View] is vertically scrollable, when the text content grows. The top buttons group remains static.

We try to disable scrolling behaviour in [Text View] itself. Again, the entire page is not scrollable, when the text content grows.

Do you have any idea how to fix this, so that when text content grow, the top button group can scroll together with text view?

The demo is located at https://github.com/yccheok/ios-tutorial/tree/learn-scroll-view/NavigationController

Looks like you're close...

The UITextView definitely should have scrolling disabled. That will cause it to grow/shrink vertically as the user types.

The key point you're missing, I think, is a bottom constraint for your text view.

Here is how it should be laid out - I've used 8-pt "padding" for the elements:

在此处输入图像描述

Here's the source for the storyboard:

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

and a sample view controller with keyboard handling:

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

Scroll in scroll always been a tricky thing to handle. My suggestions -

Don't use UITextView if it's not editable, use UILabel .

OR

Calculate height of text and make text view equal to it and disable scrolling. this way you will ultimately have only one scrollable view. Helpful link.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM