0
votes

I want to have a scroll view that displays all of my things in a stack view vertically.

  1. First, I created a view named contentView within the scroll view which contains the stack view, which means I now have view -> scrollView -> contentView -> stackView.
  2. I set the content view's leading, trailing, top and bottom anchors to be equal to those constraints of the scroll views content layout guide.
  3. I made the width of the content view the same as the width of the scroll views frame layout guide.
  4. I made the stack view's leading, trailing, top and bottom anchors to be equal to the content view's corresponding anchors.

enter image description here

This doesn't scroll.

I tried following this SO answer:

  1. I got rid of the Content Layout Guides and applied the contentView's constraints to be 0,0,0,0 to all 4 sides and center it horizontally and vertically to the scroll view.
  2. In size inspector, change bottom and align center Y priority to 250.
  3. Set the bottom anchor of the stack view to view (not the scroll view).

enter image description here

This only scrolls a little bit, but doesn't fully scroll the bottom. Much of the view is just hidden outside of the screen.

I also tried getting rid of contentView all together and pinned my stack view to either the scroll view or to view directly, but none worked.

Finally, I tried this super hacky-looking solution:

override func viewWillLayoutSubviews(){
    super.viewWillLayoutSubviews()
    scrollView.contentSize = CGSize(width: view.bounds.width, height: view.bounds.height+300)
}

but, it squishes the stack view vertically and doesn't display the content fully.

P.S. I'm adding the constraints for the stack view programmatically:

stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
2

2 Answers

1
votes

Here's an example that will work - see if you can figure out what you may have done differently.

Instead of using a "content view" we'll just add the stack view directly to the scroll view via code.

Here's the Storyboard layout:

enter image description here

Here's the source for the Storyboard, so you can examine it directly:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dVO-AO-rAX">
    <device id="retina3_5" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Kevvv View Controller-->
        <scene sceneID="e7x-2X-Pdg">
            <objects>
                <viewController id="dVO-AO-rAX" customClass="KevvvViewController" customModule="MiniScratch" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="ZMq-2S-yNo">
                        <rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bEj-BB-5lU">
                                <rect key="frame" x="0.0" y="44" width="320" height="402"/>
                                <viewLayoutGuide key="contentLayoutGuide" id="VmC-Gj-CCr"/>
                                <viewLayoutGuide key="frameLayoutGuide" id="HBJ-Ua-m26"/>
                            </scrollView>
                        </subviews>
                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                        <constraints>
                            <constraint firstItem="bEj-BB-5lU" firstAttribute="leading" secondItem="goZ-oS-cQl" secondAttribute="leading" id="Jwq-Tg-wRK"/>
                            <constraint firstItem="goZ-oS-cQl" firstAttribute="bottom" secondItem="bEj-BB-5lU" secondAttribute="bottom" constant="34" id="bHJ-DL-1xi"/>
                            <constraint firstItem="bEj-BB-5lU" firstAttribute="trailing" secondItem="goZ-oS-cQl" secondAttribute="trailing" id="gIL-OY-ENf"/>
                            <constraint firstItem="bEj-BB-5lU" firstAttribute="top" secondItem="goZ-oS-cQl" secondAttribute="top" constant="44" id="zAh-qk-82E"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="goZ-oS-cQl"/>
                    </view>
                    <connections>
                        <outlet property="scrollView" destination="bEj-BB-5lU" id="jYI-Wh-d6w"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="ieG-NN-t0K" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="136.875" y="105"/>
        </scene>
    </scenes>
</document>

And here's example code that will add a stack view to the scroll view, add 40 labels to the stack view, and then properly constrain the stack view to the scroll view:

class KevvvViewController: UIViewController {

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

        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 12
        
        stack.translatesAutoresizingMaskIntoConstraints = false
        
        scrollView.addSubview(stack)
        
        for i in 1...40 {
            let v = UILabel()
            v.backgroundColor = .yellow
            v.text = "Label \(i)"
            stack.addArrangedSubview(v)
        }

        NSLayoutConstraint.activate([
            stack.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            stack.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            stack.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            stack.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
            
            stack.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
        ])
        
        // to make it easy to see the scroll view frame
        scrollView.backgroundColor = .cyan
    }
    
}

Result, after scrolling down to the 17th label (iPhone 8):

enter image description here

0
votes

It seems that you only need a UIScrollView and a UIStackView like that:

enter image description here

Both UIScrollView and UIStackView have set the top, bottom, leading and trailing constraints to their respective superviews. The only difference is the last constraint, between the UIStackView's width and the Safe Area width. (this depends on what width do you want the UIStackView to have)

You may also need to set UIStackView's Distribution to something like Equal Spacing to make sure that always expands depending on its content.

Also, add something in the UIStackView so that the auto layout stops complaining. (avoid red constraints on the interface builder)

Give it a shot.