My problem is as follows:
The height of contentView is taller than its subviews, so although the subviews of contentView don't take up the entire screen vertically, you can still scroll past the content.
How can I set the height of contentView to match the height of its subviews, so that way if the screen is large enough vertically to fit the content, you can't scroll, but if the screen is smaller or a keyboard signal is sent, then you can scroll vertically?
I understand the problem is with the height constraint in the contentView.anchor, but I don't know how to fix it and I haven't been able to find any answers.
let scrollView = UIScrollView()
let contentView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
configureScrollView()
configureUI()
}
func configureScrollView() {
view.addSubview(scrollView)
scrollView.anchor(top: view.safeAreaLayoutGuide.topAnchor,
left: view.leftAnchor,
bottom: view.safeAreaLayoutGuide.bottomAnchor,
right: view.rightAnchor)
scrollView.addSubview(contentView)
contentView.anchor(top: scrollView.topAnchor,
left: scrollView.leftAnchor,
bottom: scrollView.bottomAnchor,
right: scrollView.rightAnchor,
width: view.frame.size.width,
height: view.frame.size.height)
}
func configureUI() {
contentView.addSubview(previewTitleLabel)
previewTitleLabel.anchor(top: contentView.topAnchor,
left: contentView.leftAnchor,
right: contentView.rightAnchor,
paddingTop: 20,
paddingLeft: 20,
paddingRight: 20,
height: 20)
contentView.addSubview(previewView)
previewView.anchor(top: previewTitleLabel.bottomAnchor,
left: contentView.leftAnchor,
right: contentView.rightAnchor,
paddingTop: 20,
paddingLeft: 20,
paddingRight: 20) // Has dynamic height
contentView.addSubview(detailsTitleLabel)
detailsTitleLabel.anchor(top: previewView.bottomAnchor,
left: contentView.leftAnchor,
right: contentView.rightAnchor,
paddingTop: 20,
paddingLeft: 20,
paddingRight: 20,
height: 20)
contentView.addSubview(descriptionView)
descriptionView.anchor(top: detailsTitleLabel.bottomAnchor,
left: contentView.leftAnchor,
right: contentView.rightAnchor,
paddingTop: 20,
paddingLeft: 20,
paddingRight: 20,
height: 278)
}
Couple tips:
.anchor()
"helper" until you fully understand how constraints work. This also lets you "group" your constraints logically, as you'll see in this example code.So, example code:
class ExampleScrollViewController: UIViewController {
let scrollView = UIScrollView()
let contentView = UIView()
let previewTitleLabel = UILabel()
let previewView = UIView()
let detailsTitleLabel = UILabel()
let descriptionView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
configureScrollView()
configureUI()
// use some background colors so we can easily see frames
scrollView.backgroundColor = .red
contentView.backgroundColor = .blue
previewTitleLabel.backgroundColor = .cyan
previewView.backgroundColor = .green
detailsTitleLabel.backgroundColor = .cyan
descriptionView.backgroundColor = .green
previewTitleLabel.text = "Preview Title Label"
detailsTitleLabel.text = "Details Title Label"
}
func configureScrollView() {
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
view.addSubview(scrollView)
// respect safe area
let g = view.safeAreaLayoutGuide
// reference to scrollView's contentLayoutGuide
let svContentLG = scrollView.contentLayoutGuide
// reference to scrollView's frameLayoutGuide
let svFrameLG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scroll view to full safe area
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
// constrain content view to all 4 sides of scroll view's content layout guide
contentView.topAnchor.constraint(equalTo: svContentLG.topAnchor),
contentView.leadingAnchor.constraint(equalTo: svContentLG.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: svContentLG.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: svContentLG.bottomAnchor),
// we want vertical scrolling,
// so constrain width of content view to scroll view's frame
contentView.widthAnchor.constraint(equalTo: svFrameLG.widthAnchor),
])
}
func configureUI() {
[previewTitleLabel, previewView, detailsTitleLabel, descriptionView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview($0)
}
NSLayoutConstraint.activate([
// horizontal constraints
// all 4 subviews will be constrained
// Leading and Trailing to the contentView
// with 20-pts "padding" on left and right
previewTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20.0),
previewTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20.0),
previewView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20.0),
previewView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20.0),
detailsTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20.0),
detailsTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20.0),
descriptionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20.0),
descriptionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20.0),
// vertical spacing constraints
// previewTitleLabel Top 20-pts from contentView Top
previewTitleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20.0),
// previewView Top 20-pts from previewTitleLabel Bottom
previewView.topAnchor.constraint(equalTo: previewTitleLabel.bottomAnchor, constant: 20.0),
// detailsTitleLabel Top 20-pts from previewView Bottom
detailsTitleLabel.topAnchor.constraint(equalTo: previewView.bottomAnchor, constant: 20.0),
// descriptionView Top 20-pts from detailsTitleLabel Bottom
descriptionView.topAnchor.constraint(equalTo: detailsTitleLabel.bottomAnchor, constant: 20.0),
// complete the vertical spacing constraints by
// constraining descriptionView Bottom 20-pts from contentView Bottom
descriptionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0),
// vertical height constraints
// previewTitleLabel Height: 20
previewTitleLabel.heightAnchor.constraint(equalToConstant: 20.0),
// previewView Height: 20
previewView.heightAnchor.constraint(equalToConstant: 20.0),
// detailsTitleLabel Height: 20
detailsTitleLabel.heightAnchor.constraint(equalToConstant: 20.0),
// descriptionView Height: 270
descriptionView.heightAnchor.constraint(equalToConstant: 270.0),
])
}
}
Result:
If I change the Height of previewView
also to 270
:
previewView.heightAnchor.constraint(equalToConstant: 270.0),
we get this result:
and we can scroll up to see the bottom of the content: