0

I have a UIView that contains a UIScrollView, and that UIScrollView contains another UIView, which contains labels etc. Sometimes, certain labels contain 3 lines of text, sometimes they contain 15,... depending on the received data. The view also changes when certain buttons get clicked. When I scroll through my screen, it doesn't go al the way to the bottom. I notice that the height of the labels in label.frame.height is way smaller than the actual height of the label. How can I get the actual size of the labels at runtime? Anyone that can help?

I've tried overriding function viewDidLayoutSubviews:

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        contentview.layoutSubviews()
        
        var contentHeigt: CGFloat = 0
        for subview in contentview.subviews{
            contentHeigt += subview.frame.height
        }
                
        scrollView.contentSize = CGSize(width: scrollView.frame.width, height: contentHeigt)
    }

but that's where the height doesn't seem to match the actual height.

I've tried this:

func getLableHeightRuntime(label:UILabel) -> CGFloat {
            let stringValue = label.text!
            let width:CGFloat = 0
            let height:CGFloat = 0
            let font = UIFont(name: UIFont.systemFont(ofSize: 17).fontName, size: 17)
            let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
            let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
            return ceil(boundingBox.height)
        }

seems to give back the right size, but when I add that to the contentHeight in viewDidLayoutSubviews, like this:

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        contentview.layoutSubviews()
        
        var contentHeigt: CGFloat = 0
        for subview in contentview.subviews{
            contentHeigt += subview.frame.size.height
        }
        
        contentHeigt += getLableHeightRuntime(label: lblLocations)
        contentHeigt += getLableHeightRuntime(label: lblTeachers)
        
        scrollView.contentSize = CGSize(width: scrollView.frame.width, height: contentHeigt)
    }

I can't click certain buttons, and it scrolls way too far.

Nienke
  • 1
  • 4

1 Answers1

0

If you are using auto-layout / constraints, you don't need to set the scroll view's .contentSize -- auto-layout handles that for you.

Quick example:

class ContentSizeExampleViewController: UIViewController {
    
    let scrollView = UIScrollView()

    // we'll set the text of this label to the
    //  scrollView's .contentSize
    //  determined by auto-layout
    let centerLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)

        // create a "content view"
        let contentView = UIView()
        
        // let's create Top-Left, Center and Bottom-Right labels
        let topLeftLabel = UILabel()
        topLeftLabel.backgroundColor = .cyan
        topLeftLabel.font = .systemFont(ofSize: 24, weight: .regular)
        topLeftLabel.text = "Top-Left"
        
        centerLabel.backgroundColor = .cyan
        centerLabel.font = .systemFont(ofSize: 24, weight: .regular)
        centerLabel.text = "Center"
        
        let bottomRightLabel = UILabel()
        bottomRightLabel.backgroundColor = .cyan
        bottomRightLabel.font = .systemFont(ofSize: 24, weight: .regular)
        bottomRightLabel.text = "Bottom-Right"
        
        // we'll be using auto-layout / constraints
        [topLeftLabel, centerLabel, bottomRightLabel, contentView, scrollView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }

        // add the labels to the content view
        contentView.addSubview(topLeftLabel)
        contentView.addSubview(centerLabel)
        contentView.addSubview(bottomRightLabel)
        
        // add the content view to the scroll view
        scrollView.addSubview(contentView)
        
        // add the scroll view to the view
        view.addSubview(scrollView)
        
        let g = view.safeAreaLayoutGuide
        let cg = scrollView.contentLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // inset the scroll view 20-points on all 4 sides
            scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
            
            // constrain Top-Left label top/leading to content view
            //  with 8-points "padding"
            topLeftLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
            topLeftLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),

            // constrain Bottom-Right label trailing/bottom to content view
            //  with 8-points "padding"
            bottomRightLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
            bottomRightLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),

            // constrain Bottom-Right label
            //  400-points to the right
            //  800-points below
            // of the Top-Left label
            bottomRightLabel.leadingAnchor.constraint(equalTo: topLeftLabel.trailingAnchor, constant: 400.0),
            bottomRightLabel.topAnchor.constraint(equalTo: topLeftLabel.bottomAnchor, constant: 800.0),

            // constrain Center label to center of content view
            centerLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            centerLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),

            // constrain all 4 sides of content view Content Layout Guide
            //  with 8-points "padding"
            contentView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 8.0),
            contentView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 8.0),
            contentView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -8.0),
            contentView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: -8.0),

        ])
        
        // so we can see the framing
        scrollView.backgroundColor = .systemRed
        contentView.backgroundColor = .systemBlue

    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        centerLabel.text = "ContentSize: \(scrollView.contentSize)"
    }
    
}

It will look like this - scroll view background is Red, "content view" background is Blue, labels are constrained relative to contentView and contentView is constrained to the scroll view's .contentLayoutGuide.

The "center label" text is set in viewDidAppear() - which is when we know everything has been laid-out:

enter image description here

scrolled toward the middle:

enter image description here

and all the way to bottom-right:

enter image description here

If/when the subviews change (multi-line labels change text, positions/sizes of views are changed via code, etc), as long as you have your constraints setup correctly, you never need to set .contentSize.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • I set the constraints on the storyboard, does this also work, or do I need to set them programatically? – Nienke Mar 15 '23 at 14:48
  • @Nienke - sure, as long as you have your constraints setup correctly. Take a look here: https://stackoverflow.com/a/59828434/6257435 – DonMag Mar 15 '23 at 15:31
  • Thank you for the link, I followed the "tutorial", but now I can't scroll at all anymore, I'm assuming somethings wrong with my constraints, but I can't seem to figure out what it is – Nienke Mar 15 '23 at 15:48
  • @Nienke - if you create a [mre] -- strip your current stuff down to just what's needed to check out the scroll view -- and post it somewhere like GitHub we can talk a look at it to figure out what's not-quite-right. – DonMag Mar 15 '23 at 17:06