I know this question has been asked thousand times but I am still not able to get it right.
Here are the constraints I used
- Added
ScrollView
to View. SetScrollView
top/left/right/bottom anchors to view. - Added
ContainerView
toScrollView
. SetContainerView
top/left/right/bottom anchors toScrollView.contentLayoutGuide
. Added Width and height constraint ofContainerView
toScrollView.frameLayoutGuide
and Added priority to height as low. - Added 1 label to
ContainerView
with top(wrtContainerView
)/left(wrtContainerView
)/right(wrtContainerView
) - Added
ContentView
toContainerView
by setting top(wrt Label)/left(wrt ContainerView)/right(wrt ContainerView)/bottom(wrt ContainerView) anchors with some constant. - Added
UIImageView
,UILabel
andUITableView
toContentView
. - Set
ImageView
top/left/right/height anchors wrt toContentView
. - Set
label
top(wrtImageView
)/left(wrtContentView
)/right(wrtContentView
) constant - Set
TableView
top(wrt Label)/left(wrt ContentView)/right(wrt ContentView)/height(constant) anchors.
Here is the code
self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.containerView)
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.containerView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
let layoutGuide = scrollView.contentLayoutGuide
self.containerView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
self.containerView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
self.containerView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
self.containerView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
self.containerView.widthAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.widthAnchor).isActive = true
self.containerView.heightAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.heightAnchor).isActive = true
self.containerView.heightAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.heightAnchor).priority = .defaultLow
self.containerView.addSubview(self.titleLabel)
self.containerView.addSubview(self.instructionStackView)
self.containerView.addSubview(self.contentView)
self.contentView.addSubview(self.questionImageView)
self.contentView.addSubview(self.questionTitleLabel)
self.contentView.addSubview(self.quizOptionsTableView)
self.titleLabel.topAnchor.constraint(equalTo: self.containerView.topAnchor, constant: 16).isActive = true
self.titleLabel.leftAnchor.constraint(equalTo: self.containerView.leftAnchor).isActive = true
self.titleLabel.rightAnchor.constraint(equalTo: self.containerView.rightAnchor).isActive = true
self.instructionStackView.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: 24).isActive = true
self.instructionStackView.leftAnchor.constraint(equalTo: self.containerView.leftAnchor, constant: 50).isActive = true
self.instructionStackView.rightAnchor.constraint(equalTo: self.containerView.rightAnchor, constant: -50).isActive = true
let verticalStackView = UIStackView(frame: .zero)
verticalStackView.axis = .vertical
verticalStackView.addArrangedSubview(self.questionNumberLabel)
verticalStackView.addArrangedSubview(self.marksLabel)
self.questionNumberLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
let emptyView = UIView(frame: .zero)
emptyView.widthAnchor.constraint(equalToConstant: 40).isActive = true
let horizantalStackView = UIStackView(frame: .zero)
horizantalStackView.axis = .horizontal
horizantalStackView.addArrangedSubview(emptyView)
horizantalStackView.addArrangedSubview(self.clockImageView)
horizantalStackView.addArrangedSubview(self.timerLabel)
horizantalStackView.spacing = 8
self.clockImageView.widthAnchor.constraint(equalToConstant: 46).isActive = true
self.clockImageView.heightAnchor.constraint(equalToConstant: 46).isActive = true
self.instructionStackView.addArrangedSubview(verticalStackView)
self.instructionStackView.addArrangedSubview(horizantalStackView)
self.contentView.topAnchor.constraint(equalTo: self.instructionStackView.bottomAnchor,
constant: 16).isActive = true
self.contentView.leftAnchor.constraint(equalTo: self.containerView.leftAnchor,
constant: 50).isActive = true
self.contentView.rightAnchor.constraint(equalTo: self.containerView.rightAnchor,
constant: -50).isActive = true
self.contentView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor,
constant: -16).isActive = true
self.questionImageView.topAnchor.constraint(equalTo: self.contentView.topAnchor,
constant: 8).isActive = true
self.questionImageView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor,
constant: 8).isActive = true
self.questionImageView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor,
constant: -8).isActive = true
self.questionImageView.heightAnchor.constraint(equalToConstant: 170).isActive = true
self.questionTitleLabel.topAnchor.constraint(equalTo: self.self.questionImageView.bottomAnchor,
constant: 8).isActive = true
self.questionTitleLabel.leftAnchor.constraint(equalTo: self.contentView.leftAnchor,
constant: 8).isActive = true
self.questionTitleLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor,
constant: -8).isActive = true
self.questionTitleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
self.quizOptionsTableView.topAnchor.constraint(equalTo: self.questionTitleLabel.bottomAnchor,
constant: 8).isActive = true
self.quizOptionsTableView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor,
constant: 8).isActive = true
self.quizOptionsTableView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor,
constant: -8).isActive = true
self.quizOptionsTableView.heightAnchor.constraint(equalToConstant: 320).isActive = true
View is scrollable with no warnings. But does not scroll to bottom. Where I am wrong?
CodePudding user response:
You don't need either of these lines - remove them:
self.containerView.heightAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.heightAnchor).isActive = true
self.containerView.heightAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.heightAnchor).priority = .defaultLow
You have no constraint controlling the height of contentView
... you need to add:
// quizOptionsTableView bottom to contentView bottom with 8-points "padding
self.quizOptionsTableView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
Couple of suggestions...
- Respect the Safe Area
- Stick with
.leadingAnchor
and.trailingAnchor
(right now you're mixing in left/right). - Group actions together ... that is, do your subview adding in one place, your constraints all together ... your UI element properties all together.
- Give your UI elements contrasting background colors to make it easy to see the frames.
Very STRONGLY Recommend: use comments!!!!
Take a look at the way I've edited your code:
class SampleViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let scrollView = UIScrollView()
let titleLabel = UILabel()
let containerView = UIView()
let contentView = UIView()
let questionImageView = UIImageView()
let questionTitleLabel = UILabel()
let questionNumberLabel = UILabel()
let marksLabel = UILabel()
let quizOptionsTableView = UITableView()
let instructionStackView = UIStackView()
let clockImageView = UIImageView()
let timerLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
[scrollView, containerView, contentView, titleLabel, instructionStackView,
questionImageView, questionTitleLabel, questionNumberLabel,
marksLabel, quizOptionsTableView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
//self.scrollView.showsVerticalScrollIndicator = false
self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.containerView)
self.containerView.addSubview(self.titleLabel)
self.containerView.addSubview(self.instructionStackView)
self.containerView.addSubview(self.contentView)
self.contentView.addSubview(self.questionImageView)
self.contentView.addSubview(self.questionTitleLabel)
self.contentView.addSubview(self.quizOptionsTableView)
self.questionTitleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
let verticalStackView = UIStackView(frame: .zero)
verticalStackView.axis = .vertical
verticalStackView.addArrangedSubview(self.questionNumberLabel)
verticalStackView.addArrangedSubview(self.marksLabel)
self.questionNumberLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
let emptyView = UIView(frame: .zero)
emptyView.widthAnchor.constraint(equalToConstant: 40).isActive = true
let horizantalStackView = UIStackView(frame: .zero)
horizantalStackView.axis = .horizontal
horizantalStackView.addArrangedSubview(emptyView)
horizantalStackView.addArrangedSubview(self.clockImageView)
horizantalStackView.addArrangedSubview(self.timerLabel)
horizantalStackView.spacing = 8
self.instructionStackView.addArrangedSubview(verticalStackView)
self.instructionStackView.addArrangedSubview(horizantalStackView)
let safeGuide = view.safeAreaLayoutGuide
let layoutGuide = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// all 4 sides of scrollView to view
self.scrollView.topAnchor.constraint(equalTo: safeGuide.topAnchor),
self.scrollView.leftAnchor.constraint(equalTo: safeGuide.leftAnchor),
self.scrollView.rightAnchor.constraint(equalTo: safeGuide.rightAnchor),
self.scrollView.bottomAnchor.constraint(equalTo: safeGuide.bottomAnchor),
// all 4 sides of containerView to scrollView's Content Layout Guide
self.containerView.topAnchor.constraint(equalTo: layoutGuide.topAnchor),
self.containerView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
self.containerView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
self.containerView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor),
// containerView Width to scrollView's Frame Layout Guide
self.containerView.widthAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.widthAnchor),
// NO height constraint for containerView
// titleLabel to top of containerView 16-points "padding"
self.titleLabel.topAnchor.constraint(equalTo: self.containerView.topAnchor, constant: 16),
// titleLabel to leading/trailing of containerView
self.titleLabel.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor),
self.titleLabel.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor),
// instructionStackView top to titleLabel bottom with 24-points "padding"
self.instructionStackView.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: 24),
// instructionStackView to leading/trailing of containerView with 50-points "padding"
self.instructionStackView.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, constant: 50),
self.instructionStackView.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, constant: -50),
// clockImageView width and height
self.clockImageView.widthAnchor.constraint(equalToConstant: 46),
self.clockImageView.heightAnchor.constraint(equalToConstant: 46),
// contentView top to instructionStackView bottom 16-points "padding"
self.contentView.topAnchor.constraint(equalTo: self.instructionStackView.bottomAnchor, constant: 16),
// contentView to leading/trailing of containerView with 50-points "padding"
self.contentView.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, constant: 50),
self.contentView.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, constant: -50),
// contentView bottom to containerView bottom with 16-points "padding"
self.contentView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: -16),
// questionImageView top/leading/trailing to contentView with 8-points "padding"
self.questionImageView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 8),
self.questionImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8),
self.questionImageView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -8),
// questionImageView height constant
self.questionImageView.heightAnchor.constraint(equalToConstant: 170),
// questionTitleLabel top to questionImageView bottom 8-points "padding"
self.questionTitleLabel.topAnchor.constraint(equalTo: self.self.questionImageView.bottomAnchor, constant: 8),
// questionTitleLabel leading/trailing to contentView with 8-points "padding"
self.questionTitleLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8),
self.questionTitleLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -8),
// quizOptionsTableView top to questionTitleLabel 8-points "padding
self.quizOptionsTableView.topAnchor.constraint(equalTo: self.questionTitleLabel.bottomAnchor, constant: 8),
// quizOptionsTableView leading/trailing to contentView with 8-points "padding"
self.quizOptionsTableView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8),
self.quizOptionsTableView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -8),
// quizOptionsTableView height constant
self.quizOptionsTableView.heightAnchor.constraint(equalToConstant: 320),
// quizOptionsTableView bottom to contentView bottom with 8-points "padding
self.quizOptionsTableView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8),
])
// UI element properties
titleLabel.numberOfLines = 0
titleLabel.textAlignment = .center
titleLabel.text = "This is the text for the Title Label which should be able to wrap onto multiple lines."
questionTitleLabel.numberOfLines = 0
questionTitleLabel.text = "This is the text for the Question Title Label which should be able to wrap onto multiple lines just like the Title Label."
questionNumberLabel.text = "1"
marksLabel.text = "Marks?"
if let img = UIImage(systemName: "clock.fill") {
clockImageView.image = img
}
if let img = UIImage(systemName: "photo.tv") {
questionImageView.image = img
}
quizOptionsTableView.register(UITableViewCell.self, forCellReuseIdentifier: "c")
quizOptionsTableView.dataSource = self
quizOptionsTableView.delegate = self
// let's give our UI elements some constrasting colors so we can see their frames
view.backgroundColor = .lightGray
scrollView.backgroundColor = .red
containerView.backgroundColor = .systemGreen
contentView.backgroundColor = .systemBlue
titleLabel.backgroundColor = .yellow
questionTitleLabel.backgroundColor = .cyan
marksLabel.backgroundColor = .green
clockImageView.backgroundColor = .systemYellow
questionImageView.backgroundColor = .systemYellow
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
}