How do I align ImageView to the bottom of ScrollView programmatically?

Viewed 1157

I am trying to align a background image to the bottom of a scroll view that fits the screen, programmatically using Autolayout. Ideally, I want the image to be always aligned at the bottom of the scroll view. When the content of the scroll view goes beyond the screen height or when scroll view content size is less than screen height with scroll view fitting the whole screen.

MyView

class MyView: UIView {

    let myScrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.bounces = false
        return scrollView
    }()

    let contentView: UIView = {
        let view = UIView()
        view.backgroundColor = .red
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    let myLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Hello world"
        label.font = UIFont.systemFont(ofSize: 24)
        return label
    }()

    let myImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = #imageLiteral(resourceName: "Mask Group 3")
        return imageView
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        setupConstraints()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupView() {
        backgroundColor = .white

        addSubview(myScrollView)

        myScrollView.addSubview(contentView)

        contentView.addSubview(myLabel)
        contentView.addSubview(myImageView)
    }

    private func setupConstraints() {
        myScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        myScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        myScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
        myScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true

        contentView.topAnchor.constraint(equalTo: myScrollView.topAnchor).isActive = true
        contentView.bottomAnchor.constraint(equalTo: myScrollView.bottomAnchor).isActive = true
        contentView.leftAnchor.constraint(equalTo: myScrollView.leftAnchor).isActive = true
        contentView.rightAnchor.constraint(equalTo: myScrollView.rightAnchor).isActive = true
        contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
        // If I am setting this and when the content size go beyond the screen, it does not scroll
        // If I don't set this, there is no content size and image view will not position correctly
//        contentView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true

        myLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 200).isActive = true
        myLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true

        myImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        myImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
        myImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
    }

}

MyViewController

import UIKit

class MyViewController: UIViewController {

    override func loadView() {
        view = MyView()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(true, animated: false)
    }

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

}

Illustration

ScrollView

2 Answers

I have found the solution.

  1. A contentView is needed.
  2. Set the contentView's top, left, bottom, right constraint equal to scrollView edges.
  3. Set the contentView's width equal to view's width anchor
  4. Set the contentView's height greaterThanOrEqualTo view's height anchor
  5. Set the imageView's bottom equal to the bottom anchor of contentView.
  6. For the imageView's top, set the constraint to an element with greaterThanOrEqualTo, to give it a constant gap and avoid overlapping of elements in smaller screens.

It is seems ok:

private func setupConstraints() {
        myScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        myScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        myScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
        myScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true

        contentView.topAnchor.constraint(equalTo: myScrollView.topAnchor).isActive = true
        contentView.bottomAnchor.constraint(equalTo: myScrollView.bottomAnchor).isActive = true
        contentView.leftAnchor.constraint(equalTo: myScrollView.leftAnchor).isActive = true
        contentView.rightAnchor.constraint(equalTo: myScrollView.rightAnchor).isActive = true
        contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
        // If I am setting this and when the content size go beyond the screen, it does not scroll
        // If I don't set this, there is no content size and image view will not position correctly
        contentView.heightAnchor.constraint(equalToConstant: 1400).isActive = true

        myLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 200).isActive = true
        myLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true

        myImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        myImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
        myImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
        myImageView.heightAnchor.constraint(equalToConstant: 200).isActive = true

    }

If think you just forgot to specify image height:

myImageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
Related