2
votes

I have a layout like this:

It consists of a UINavigationController that contains a UIScrollView

That view contains a UIImage and a UILabel

The text in the label can be quite long, if this happens I would like to scroll the entire view to be scrollable, including the image.

I have tried embedding a UIScrollView and anchoring it to the top and bottom, however cannot get the image to scoll, only the label.

I have no tried to set my parent view as a UIScrollView. This is not working

class ContentView: UIScrollView {

    private var margins: UILayoutGuide!
    private var activity: UIActivityIndicatorView!

    private var articleImageView: UIImageView!
    private var articleTextView: UILabel!

    override init(frame: CGRect) {
        super.init(frame: frame)

        margins = safeAreaLayoutGuide

        isScrollEnabled = true

        articleImageView = UIImageView(image: #imageLiteral(resourceName: "feed_header_bg"))
        articleImageView.contentMode = .scaleAspectFill

        addSubview(articleImageView)
        articleImageView.anchor(top: margins.topAnchor, leading: margins.leadingAnchor, bottom: nil, trailing: margins.trailingAnchor)


        articleTextView = UILabel(frame: .zero)

        articleTextView.numberOfLines = 0

        articleTextView.text = "foo\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nboo\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nbaz"

        addSubview(articleTextView)
        articleTextView.anchor(top: articleImageView.bottomAnchor, leading: margins.leadingAnchor, bottom: nil, trailing: margins.trailingAnchor)
    }

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

    private func addLoadingView() {
        activity = UIActivityIndicatorView(style: .whiteLarge)
        activity.color = .black
        activity.isHidden = true
        addSubview(activity)
        activity.anchor(centerX: centerXAnchor, centerY: centerYAnchor)
        activity.startAnimating()
    }
}

I am also using an extension to help with programmatic auto layout

struct AnchoredConstraints {
    var top, leading, bottom, trailing, width, height: NSLayoutConstraint?
}

extension UIView {
    @discardableResult
    func anchor(top: NSLayoutYAxisAnchor? = nil, leading: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, trailing: NSLayoutXAxisAnchor? = nil, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints {
        translatesAutoresizingMaskIntoConstraints = false
        var anchoredConstraints = AnchoredConstraints()

        if let top = top {
            anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top)
        }

        if let leading = leading {
            anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
        }

        if let bottom = bottom {
            anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
        }

        if let trailing = trailing {
            anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
        }

        if size.width != 0 {
            anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width)
        }

        if size.height != 0 {
            anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height)
        }

        [anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach { $0?.isActive = true }

        return anchoredConstraints
    }

    func anchor(
        top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil,
        paddingTop: CGFloat = 0, paddingLeft: CGFloat = 0, paddingBottom: CGFloat = 0, paddingRight: CGFloat = 0,
        width: CGFloat = 0, height: CGFloat = 0,
        centerX: NSLayoutXAxisAnchor? = nil, centerY: NSLayoutYAxisAnchor? = nil
        ) {
        translatesAutoresizingMaskIntoConstraints = false

        if let top = top {
            self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
        }

        if let left = left {
            self.leadingAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
        }

        if let bottom = bottom {
            self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
        }

        if let right = right {
            self.trailingAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
        }

        if width != 0 {
            widthAnchor.constraint(equalToConstant: width).isActive = true
        }

        if height != 0 {
            heightAnchor.constraint(equalToConstant: height).isActive = true
        }

        if let centerX = centerX {
            self.centerXAnchor.constraint(equalTo: centerX).isActive = true
        }

        if let centerY = centerY {
            self.centerYAnchor.constraint(equalTo: centerY).isActive = true
        }
    }


    func fillSuperview(padding: UIEdgeInsets = .zero) {
        translatesAutoresizingMaskIntoConstraints = false
        if let superviewTopAnchor = superview?.topAnchor {
            topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true
        }

        if let superviewBottomAnchor = superview?.bottomAnchor {
            bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: -padding.bottom).isActive = true
        }

        if let superviewLeadingAnchor = superview?.leadingAnchor {
            leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true
        }

        if let superviewTrailingAnchor = superview?.trailingAnchor {
            trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: -padding.right).isActive = true
        }
    }

    func centerInSuperview(size: CGSize = .zero) {
        translatesAutoresizingMaskIntoConstraints = false
        if let superviewCenterXAnchor = superview?.centerXAnchor {
            centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true
        }

        if let superviewCenterYAnchor = superview?.centerYAnchor {
            centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true
        }

        if size.width != 0 {
            widthAnchor.constraint(equalToConstant: size.width).isActive = true
        }

        if size.height != 0 {
            heightAnchor.constraint(equalToConstant: size.height).isActive = true
        }
    }
}



I would like to have an image at the top, text underneath and allow the user to scroll if the text does not fit in the view.

1
My Class subclasses UIScrollView already - Tim J
Why do you have private var scrollView: UIScrollView! then? which doesn't look like it is being set? - Scriptable
Ah, I copied only the relevant code from the class, I missed that. Please ignore it. - Tim J

1 Answers

0
votes

Could you try subclassing UIView and adding a UIScroll and setting your anchors I have below?

I was able to replicate this and it appears to be working for me now

class ContentView: UIView {
    private var sv: UIScrollView!

    override init(frame: CGRect) {
        super.init(frame: frame)

        sv = UIScrollView(frame: .zero)
        sv.backgroundColor = .purple

        addSubview(sv)
        sv.fillSuperview()

        let ai = UIImageView(image: #imageLiteral(resourceName: "feed_header_bg"))
        ai.contentMode = .scaleToFill

        sv.addSubview(ai)
        ai.anchor(
            top: sv.topAnchor, left: sv.leadingAnchor, bottom: nil, right: sv.trailingAnchor,
            paddingTop: 20, paddingLeft: 20, paddingBottom: 0, paddingRight: 20,
            width: 0, height: 0,
            centerX: sv.centerXAnchor, centerY: nil
        )

        let l = UILabel(frame: .zero)
        l.numberOfLines = 0
        l.text = """
        Bacon ipsum dolor amet turkey jowl sausage, fatback pork chop kevin frankfurter cupim leberkas picanha ground round swine andouille buffalo meatloaf. Kielbasa meatloaf filet mignon tail. Spare ribs strip steak pork belly t-bone tongue pork loin. Hamburger tenderloin prosciutto venison bacon buffalo.

        Shankle andouille sausage shank capicola kevin, pork loin beef ribs pig leberkas short loin prosciutto jerky beef. Swine kevin pork chop, ribeye cupim short loin boudin buffalo beef. Leberkas filet mignon beef ribs turducken, ribeye sausage shoulder t-bone. Capicola leberkas tail, salami buffalo doner brisket prosciutto sirloin pork loin pork chop ribeye spare ribs drumstick.

        Picanha tongue cow jowl landjaeger cupim meatball sausage kielbasa strip steak pork loin chuck jerky venison biltong. Leberkas capicola bacon, pork loin ground round venison strip steak swine picanha tail flank andouille. Ball tip shankle ham hock leberkas swine. Ball tip sirloin boudin chicken, ground round tail jowl ribeye bresaola. Beef ribs shankle rump jowl chuck cow frankfurter capicola. Sausage picanha short ribs porchetta. Picanha drumstick corned beef beef ribs meatball leberkas pancetta.

        Shankle pancetta kevin hamburger fatback jerky shoulder frankfurter alcatra flank meatloaf rump. Burgdoggen rump shank sirloin hamburger chicken turducken cupim tail andouille ham hock ribeye fatback. Swine pork belly bacon pork chop pig jowl. Drumstick shankle pork loin ham hock, bresaola filet mignon kielbasa.

        Drumstick tongue pork loin pork belly turducken, pig porchetta ground round pancetta bacon pastrami strip steak venison. Tail cow doner pig, beef venison filet mignon landjaeger shank. Swine ham hock burgdoggen picanha shankle capicola tail prosciutto. Short loin ball tip pork chop turkey, pork loin chuck pork shankle salami. Buffalo short ribs sausage doner shankle, filet mignon pancetta frankfurter short loin. Pastrami burgdoggen strip steak, ground round swine shank sirloin pork loin hamburger jowl ribeye. Buffalo turducken kielbasa pork capicola ground round tri-tip drumstick bacon.
        """

        sv.addSubview(l)
        l.anchor(
            top: ai.bottomAnchor, left: sv.leadingAnchor, bottom: sv.bottomAnchor, right: sv.trailingAnchor,
            paddingTop: 20, paddingLeft: 0, paddingBottom: 0, paddingRight: 0,
            width: 0, height: 0,
            centerX: sv.centerXAnchor, centerY: nil
        )
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}