I have a project where it has a viewController and inside of it there is a Scroll view that has a UILabel and a UITextView as subviews. Every time I open the app I retrieve data from a Google Firestore database and therefore updates the UILabel as the Title and the UITextView as the text body. I have set the constraints and everything is working perfectly. However, I don't know how to calculate the contentView height of UIScrollView if the size of the textView exceeds the boundaries of the UIViewController's view so that you can scroll down to read the rest of the text. I have a tabBar at the bottom as well which means that the scroll view should stop above that.
Here is my code.
import UIKit
class StoryViewController: UIViewController {
var storyTitle = String()
var storyBody = ""
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.numberOfLines = 0
label.font = UIFont(name: "Arial-BoldMT", size: 28)
label.textColor = UIColor.black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let textView: UITextView = {
let textView = UITextView()
textView.textAlignment = .left
textView.isEditable = false
textView.isSelectable = false
textView.font = UIFont(name: "ArialMT", size: 19)
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
let scrollView: UIScrollView = {
let scrollingView = UIScrollView()
scrollingView.translatesAutoresizingMaskIntoConstraints = false
return scrollingView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.shadowImage = UIImage()
self.view.backgroundColor = UIColor.white
titleLabel.text = storyTitle
textView.text = storyBody.replacingOccurrences(of: "NL", with: "\n\n")
textView.sizeToFit()
scrollView.contentSize = CGSize(width: self.view.frame.width, height: textView.contentSize.height)
view.addSubview(scrollView)
scrollView.addSubview(titleLabel)
scrollView.addSubview(textView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
titleLabel.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),
titleLabel.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16),
titleLabel.bottomAnchor.constraint(equalTo: textView.topAnchor, constant: -16),
titleLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
textView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
textView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
textView.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),
])
I have tried many things but nothing seems to work.
Thank you all for your time and responses in advance.
You are close... but you don't need to worry about setting the .contentSize
of the scroll view. Let auto-layout handle that for you.
The key point you were missing was a bottom constraint for your scroll view's subviews.
Here is your class (with example title and body text), only slightly modified to work. The comments I added should make it clear:
class StoryViewController: UIViewController {
var storyTitle = String()
var storyBody = ""
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.numberOfLines = 0
label.font = UIFont(name: "Arial-BoldMT", size: 28)
label.textColor = UIColor.black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let textView: UITextView = {
let textView = UITextView()
textView.textAlignment = .left
textView.isEditable = false
textView.isSelectable = false
textView.font = UIFont(name: "ArialMT", size: 19)
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
let scrollView: UIScrollView = {
let scrollingView = UIScrollView()
scrollingView.translatesAutoresizingMaskIntoConstraints = false
return scrollingView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.shadowImage = UIImage()
self.view.backgroundColor = UIColor.white
// sample Title and Body
storyTitle = "Example\nTitle Label"
storyBody = ""
storyBody += "UITextView:"
storyBody += "NL"
storyBody += "When a user taps a text view, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text view can handle the input in an application-specific way. You can specify attributes, such as font, color, and alignment, that apply to all text in a text view."
storyBody += "NL"
storyBody += "UIScrollView:"
storyBody += "NL"
storyBody += "UIScrollView provides a mechanism to display content that is larger than the size of the application’s window and enables users to scroll within that content by making swiping gestures."
storyBody += "NL"
storyBody += "UILabel:"
storyBody += "NL"
storyBody += "A label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label."
storyBody += "NL"
storyBody += "UIButton:"
storyBody += "NL"
storyBody += "You can set the title, image, and other appearance properties of a button. In addition, you can specify a different appearance for each button state."
titleLabel.text = storyTitle
textView.text = storyBody.replacingOccurrences(of: "NL", with: "\n\n")
// you do not need either of these two lines
//textView.sizeToFit()
//scrollView.contentSize = CGSize(width: self.view.frame.width, height: textView.contentSize.height)
view.addSubview(scrollView)
scrollView.addSubview(titleLabel)
scrollView.addSubview(textView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// don't use .centerXAnchor constraints
//titleLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
//textView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
// don't need to set titleLabel bottom constraint
//titleLabel.bottomAnchor.constraint(equalTo: textView.topAnchor, constant: -16),
titleLabel.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16),
titleLabel.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),
textView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
textView.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),
// set the textView's bottomAnchor to let auto-layout determine content size
textView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])
}
}