Resize uiscrollview based on content

2.1k views Asked by At

I'm having some problem resizing and making scrollview scrollable vertically when content is added to my labels. My hierarchy looks like this:

my hierarchy

While my view looks like this:

my view

So I would like to fill out labels with content in my code on start when I get text from API call. But for testing purposes I have just hardcoded some lorem ipsum text to make it work. So my content is added to labels and UILabels resize and also my views that containt labels resize. I'm having problem resizing my scrollview content so that I can scroll if text in label is longer. Also here is my current code:

import UIKit
import PureLayout

class QuestionAndAnswerViewController: UIViewController {

@IBOutlet var menuButton: UIBarButtonItem!
@IBOutlet var questionView: UIView!
@IBOutlet var questionLabel: UILabel!
@IBOutlet var questionContentLabel: UILabel!
@IBOutlet var answerView: UIView!

@IBOutlet var odgovorLabel: UILabel!
@IBOutlet var answerLabel: UILabel!
@IBOutlet var signLabel: UILabel!

@IBOutlet var lineView: UIView!

@IBOutlet var scrollView: UIScrollView!
@IBOutlet var contentView: UIView!

var yPosition: CGFloat = 0.0
var contentSize: CGFloat = 0.0
var attrText = NSMutableAttributedString()

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView.setNeedsLayout()
    scrollView.layoutIfNeeded()
    scrollView.translatesAutoresizingMaskIntoConstraints = false


    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineSpacing = 5
    paragraphStyle.alignment = .justified

    let paragraphStyle2 = NSMutableParagraphStyle()
    paragraphStyle2.lineSpacing = 5
    paragraphStyle2.alignment = .left


    if self.revealViewController() != nil {
        self.revealViewController().frontViewShadowRadius = 5.0
        self.revealViewController().frontViewShadowOpacity = 0.25
        menuButton.target = self.revealViewController()
        menuButton.action = #selector(SWRevealViewController.rightRevealToggle(_:))
        self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
    }

    var text = "To je prvo vprašanje? Kako dolgi je lahko ta tekst da se poravna"
    attrText = NSMutableAttributedString(string: text)
    attrText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle2, range: NSMakeRange(0, attrText.length))
    questionLabel.attributedText = attrText


    text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut quis mi nisi. Etiam nec augue id dui blandit ornare. Nulla auctor, purus vel tincidunt ultricies, enim turpis molestie augue, nec mattis libero ante mattis mauris. Suspendisse posuere, velit posuere viverra feugiat, nulla justo bibendum nisi, nec ultricies lorem enim in nisl. Nunc sit amet quam mollis, faucibus felis eu, posuere dui. Sed vel mattis neque. Fusce elementum at nisl ut volutpat. Nam placerat consequat mi in lacinia. Morbi ut est tristique, efficitur est a, faucibus erat. Suspendisse et ligula ac lacus porttitor pretium ut vehicula felis."
    attrText = NSMutableAttributedString(string: text)

    attrText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, attrText.length))
    questionContentLabel.attributedText = attrText


    text = "Cras auctor ullamcorper ullamcorper. Vestibulum dignissim quis risus eget congue. Sed et diam libero. Phasellus non lectus sem. Cras auctor ullamcorper ullamcorper. Vestibulum dignissim quis risus eget congue. Sed et diam libero. Phasellus non lectus sem. Cras auctor ullamcorper ullamcorper. Vestibulum dignissim quis risus eget congue. Sed et diam libero. Phasellus non lectus sem. Cras auctor ullamcorper ullamcorper. Vestibulum dignissim quis risus eget congue. Sed et diam libero. Phasellus non lectus sem."
    attrText = NSMutableAttributedString(string: text)
    attrText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, attrText.length))
    answerLabel.attributedText = attrText

    setupConstraints()

    contentView.frame = CGRect(x: contentView.frame.origin.x, y: contentView.frame.origin.y, width: contentView.frame.width, height: 2000)
    scrollView.contentSize = CGSize(width: contentView.frame.width, height: 2000)

    print(scrollView.contentSize)
}


func setupConstraints() {


    //Question Contstraints
    questionView.autoPinEdge(.top, to: .top, of: contentView, withOffset: 30)
    questionView.autoPinEdge(.left, to: .left, of: contentView, withOffset: 0)
    questionView.autoPinEdge(.right, to: .right, of: contentView, withOffset: 0)

    questionView.autoPinEdge(.top, to: .top, of: questionLabel, withOffset: -10)
    questionLabel.autoPinEdge(.right, to: .right, of: questionView, withOffset: -20)
    questionLabel.autoPinEdge(.left, to: .left, of: questionView, withOffset: 20)

    lineView.autoPinEdge(.top, to: .bottom, of: questionLabel, withOffset: 20)
    lineView.autoPinEdge(.left, to: .left, of: questionView, withOffset: 20)
    lineView.autoPinEdge(.right, to: .right, of: questionView, withOffset: -20)
    lineView.autoSetDimensions(to: CGSize(width: lineView.frame.width, height: 2))

    questionContentLabel.autoPinEdge(.top, to: .bottom, of: lineView, withOffset: 20)
    questionContentLabel.autoPinEdge(.right, to: .right, of: questionView, withOffset: -20)
    questionContentLabel.autoPinEdge(.left, to: .left, of: questionView, withOffset: 20)
    questionView.autoPinEdge(.bottom, to: .bottom, of: questionContentLabel, withOffset: 20)


    //Anwser Constraints
    answerView.autoPinEdge(.top, to: .bottom, of: questionView, withOffset: 10)
    answerView.autoPinEdge(.left, to: .left, of: contentView, withOffset: 0)
    answerView.autoPinEdge(.right, to: .right, of: contentView, withOffset: 0)


    odgovorLabel.autoPinEdge(.top, to: .top, of: answerView, withOffset: 10)
    odgovorLabel.autoPinEdge(.left, to: .left, of: answerView, withOffset: 20)
    odgovorLabel.autoPinEdge(.right, to: .right, of: answerView, withOffset: -20)

    answerLabel.autoPinEdge(.top, to: .bottom, of: odgovorLabel, withOffset: 20)
    answerLabel.autoPinEdge(.left, to: .left, of: answerView, withOffset: 20)
    answerLabel.autoPinEdge(.right, to: .right, of: answerView, withOffset: -20)

    signLabel.autoPinEdge(.top, to: .bottom, of: answerLabel, withOffset: 20)
    signLabel.autoPinEdge(.left, to: .left, of: answerView, withOffset: 20)
    signLabel.autoPinEdge(.right, to: .right, of: answerView, withOffset: -20)

    contentView.autoPinEdge(.bottom, to: .bottom, of: answerView)

    //answerView.autoPinEdge(.bottom, to: .bottom, of: signLabel, withOffset: 20)

}


override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

@IBAction func backButtonPressed(_ sender: Any) {
    navigationController?.popViewController(animated: true)
}
}

I'm also using PureLayout because it was the only way I was able to make my labels and view resize based on text length.

1

There are 1 answers

0
Milan Nosáľ On

I'm not sure what constraints do you set for scrollView and contentView in the storyboard, and I must say that I'm a bit curious about why use set frames directly for them in viewDidLayout:

contentView.frame = CGRect(x: contentView.frame.origin.x, y: contentView.frame.origin.y, width: contentView.frame.width, height: 2000)
scrollView.contentSize = CGSize(width: contentView.frame.width, height: 2000)

You would like this to be calculated automatically, right?

What I do when I need a scrollable view:

  1. I add a scrollView to the hierarchy and use autolayout to properly layout it, e.g., if it is supposed to cover the whole view of the viewController:

    scrollView.translatesAutoresizingMaskIntoConstraints = false
    
    scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
    scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
    scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
    scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
    
  2. Then I need to add a contentView to the scrollView and provide a proper layout constraints for it, so if I want vertically scrollable scrollView in the example I started above, I need following autolayout constraints:

    contentView.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
    contentView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
    contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
    contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
    

    Notice here that I constrained the leftAnchor and rightAnchor of the contentView to the self.view rather than to scrollView to make it of fixed width. However, top and bottom anchors are constrained to the scrollView, so they are expanded and scrollable when contentView needs more space.

  3. Now I add to the contentView all the content that I want, and I lay it out using autolayout as if the contentView was a view with infinite height - scrollView will take care of presenting it whole by scrolling. So in my example if the only content would be one huge UILabel with many lines:

    label.translatesAutoresizingMaskIntoConstraints = false
    
    label.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
    label.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
    label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
    label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    

Try to go over your code and check your constraints (I wrote my constraints using Autolayout, but I guess you should be able to translate them pretty easily both to PureLayout and storyboards).