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