MikeG
MikeG

Reputation: 4044

Subviews of UIScrollView stay in same position while UIScrollView scrolls veritcally

Can anyone tell me why the subviews that I've added to my UIScrollView won't scroll? When I run the project I can scroll the UIScrollView up and down, I can see the scroll-indicator on the right side of the screen as I scroll. But the subviews that I've added to the UIScrollView stay in the same position as if they aren't subviews of the UIScrollView at all. This is very strange because I copied most of this code from another view i'm using and its working exactly as expect, just this view won't work . Thanks for looking, here's my code

import UIKit

class EnterCredentialsVC2: UIViewController {

    private lazy var scrollView: UIScrollView = { [unowned self] in
        let view = UIScrollView()
        view.backgroundColor = UIColor.ricdBlue
        view.contentSize.height = self.view.frame.size.height + 300
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    private let emailTextField: UITextField = { 
        let field = UITextField()
        field.placeholder = "Email Address"
        field.translatesAutoresizingMaskIntoConstraints = false
        return field
    }()

    private let passwordTextField: UITextField = { 
        let field = UITextField()
        field.placeholder = "Password (6 characters minimum)"
        field.translatesAutoresizingMaskIntoConstraints = false
        return field
    }()

    private let enterButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let errorLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.minimumScaleFactor = 0.1
        label.adjustsFontSizeToFitWidth = true
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
            layoutView()
    }
}


//view layout methods
extension EnterCredentialsVC2 {
    private func layoutView() {
        layoutScrollView()
        layoutEmailTextField()
        layoutPasswordTextField()
        layoutEnterButton()
        layoutErrorLabel()
    }

    private func layoutScrollView() {
        view.addSubview(scrollView)
        scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        scrollView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
        scrollView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    }

    private func layoutEmailTextField() {
        scrollView.addSubview(emailTextField)
        emailTextField.heightAnchor.constraint(equalToConstant: 45).isActive = true
        emailTextField.widthAnchor.constraint(equalToConstant: view.bounds.width * 0.9).isActive = true
        emailTextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
        emailTextField.topAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true
    }

    private func layoutPasswordTextField() {
        scrollView.addSubview(passwordTextField)
        passwordTextField.heightAnchor.constraint(equalToConstant: 45).isActive = true
        passwordTextField.widthAnchor.constraint(equalToConstant: view.bounds.width * 0.9).isActive = true
        passwordTextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
        passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 50).isActive = true
    }

    private func layoutEnterButton() {
        scrollView.addSubview(enterButton)
        enterButton.widthAnchor.constraint(equalToConstant: view.bounds.width * 0.5).isActive = true
        enterButton.heightAnchor.constraint(equalToConstant: 55).isActive = true
        enterButton.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true

        let top = enterButton.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 50)
        top.priority = .defaultHigh
        top.isActive = true
        let bottom = enterButton.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor, constant: 200)
        bottom.priority = .defaultLow
        bottom.isActive = true
    }

    private func layoutErrorLabel() {
        scrollView.addSubview(errorLabel)
        errorLabel.widthAnchor.constraint(equalToConstant: view.bounds.width-20).isActive = true
        errorLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true

        let top = errorLabel.topAnchor.constraint(equalTo: enterButton.bottomAnchor, constant: 30)
        top.priority = .required
        top.isActive = true

        let bottom = errorLabel.bottomAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.bottomAnchor, constant: -20)
        bottom.priority = .defaultLow
        bottom.isActive = true
    }
}

Upvotes: 0

Views: 362

Answers (2)

MikeG
MikeG

Reputation: 4044

Found the source of the issue, here is the line that I changed:

private func layoutEmailTextField() {
    ...
    emailTextField.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 100).isActive = true
}

constraining the textFields topAnchor equal to scrollView.safeAreaLayoutGuide.topAnchor is different than constraining it to the scrollView.topAnchor. Here is the relevant documentation and also Here. My thoughts are that because the safeArea is any area that is not obscured by the device, the subviews were forced to stay within that area and therefore wouldn't scroll beyond it. Although I'm not sure why they wouldn't bounce around whatsoever, even within the safeArea... anybody who knows more on this please leave a comment, thanks.

Upvotes: 1

matt
matt

Reputation: 535989

Constraints of direct subviews inside a scroll view work in a special way. They must be pinned either to the scroll view itself or to its contentLayoutGuide. This has a special meaning: "I'm not really pinned to your outside, I'm just showing you the layout of the subviews and I'm telling you your content size."

Pinning to the safe area layout guide — a thing that never moves — completely violates that. It's like pinning to the scroll view's frameLayoutGuide, or to something outside the scroll view altogether. It specifically instructs the scroll view not to take these views into account when working out the content height.

You had explicitly given the scroll view a content size — in a wrong way at a wrong time, but that's a different matter — and "luckily" that caused the scroll view to be scrollable. But if you set up the internal constraints correctly, any explicit setting of the contentSize is ignored.

Upvotes: 2

Related Questions