1

I have a scroll view in which I have a content view. I set the scroll view's top anchor to be just above the bottom of an image. I set the content view's top anchor to actually be at the bottom of the image. That way you can pull down on the content and reveal up to the bottom of the image without being able to pull the content view down any further. However, this is causing the content to jump.

Here is my code:

class HomeParallaxScrollViewController: UIViewController {

    private let topImageView = UIImageView(image: UIImage(named: "cat"))
    private let contentView = UIView()
    private let scrollView = UIScrollView()
    private let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .gray

        topImageView.contentMode = .scaleAspectFill
        contentView.backgroundColor = .white
        label.text = "SOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT"
        label.textColor = .black
        label.numberOfLines = 0

        [contentView, label, topImageView, scrollView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }

        scrollView.addSubview(contentView)
        contentView.addSubview(label)
        view.addSubview(topImageView)
        view.addSubview(scrollView)

        NSLayoutConstraint.activate([
            topImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            topImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            topImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            topImageView.heightAnchor.constraint(equalToConstant: 200),

            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.widthAnchor.constraint(equalTo: view.widthAnchor),
            scrollView.topAnchor.constraint(equalTo: topImageView.bottomAnchor, constant: -30),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.topAnchor.constraint(lessThanOrEqualTo: topImageView.bottomAnchor), //This is what's causing the glitch

            label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            label.topAnchor.constraint(equalTo: contentView.topAnchor),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }
}

And here is that is happening: enter image description here

Slaknation
  • 2,124
  • 3
  • 23
  • 42

1 Answers1

1

Trying to add another top constraint -- particularly to an element outside the scroll view -- is a bad idea, and, as you see, won't work. I'm sure you noticed auto-layout conflict messages being generated.

One approach is to implement scrollViewDidScroll delegate func:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    // limit drag-down in the scroll view to the overlap size
    scrollView.contentOffset.y = max(scrollView.contentOffset.y, -30)
}

As the user drags-down to scroll, it will stop at 30-points.

Here is your example, with slight modifications -- I don't have your .plBackgroundLightGray or .PLSemiboldFont and I added an image load for the top image view -- but this should run as-is:

// conform to UIScrollViewDelegate
class HomeParallaxScrollViewController: UIViewController, UIScrollViewDelegate {

    private let topImageView = UIImageView(image: UIImage(named: "cat"))
    private let contentView = UIView()
    private let scrollView = UIScrollView()
    private let label = UILabel()

    // this will be the "overlap" of the scroll view and top image view
    private var scrollOverlap: CGFloat = 30.0

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // limit drag-down in the scroll view to scrollOverlap points
        scrollView.contentOffset.y = max(scrollView.contentOffset.y, -scrollOverlap)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .lightGray // .plBackgroundLightGray

        topImageView.contentMode = .scaleAspectFill
        if let img = UIImage(named: "background") {
            topImageView.image = img
        }
        contentView.backgroundColor = .white
        label.text = "SOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT"
        label.font = UIFont.boldSystemFont(ofSize: 16) // .PLSemiboldFont(size: 16)
        label.textColor = .black
        label.numberOfLines = 0

        [contentView, label, topImageView, scrollView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }

        scrollView.addSubview(contentView)
        contentView.addSubview(label)
        view.addSubview(topImageView)
        view.addSubview(scrollView)

        NSLayoutConstraint.activate([
            topImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            topImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            topImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            topImageView.heightAnchor.constraint(equalToConstant: 200),

            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.widthAnchor.constraint(equalTo: view.widthAnchor),
            scrollView.topAnchor.constraint(equalTo: topImageView.bottomAnchor, constant: scrollOverlap),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),

            // nope, not a good idea -- will cause constraint conflicts
            //contentView.topAnchor.constraint(lessThanOrEqualTo: topImageView.bottomAnchor), //This is what's causing the glitch

            label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            label.topAnchor.constraint(equalTo: contentView.topAnchor),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])

        // set delegate to self
        scrollView.delegate = self
    }

}
DonMag
  • 69,424
  • 5
  • 50
  • 86