I want to add view controller as subview inside the UIView . I have given the required constrains but I am not figure it out why it overlapping . Here is minimal code for it. I have given the top , bottom , leading , trailing constrains but not sure why it overlaps . I want to put svc below the parentView
final class MovieDetailsDisplayViewController: UIViewController {
let movieDetails: MovieDetails
let viewModel: MoviesDetailsViewModel
init(movieDetails: MovieDetails, viewModel: MoviesDetailsViewModel) {
self.movieDetails = movieDetails
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = parentView()
}
override func viewDidLoad() {
super.viewDidLoad()
//let parentView = parentView()
(view as? parentView)?.configure(movieDetails: movieDetails)
childView()
}
private func childView() {
let addHereView = UIView()
addHereView.backgroundColor = .clear
addHereView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(addHereView)
let similarVC = SmiliarMovieViewController(viewModel: viewModel)
addChild(similarVC)
// safely unwrap MySimilarViewController's view
guard let similarView = similarVC.view else { return }
similarView.translatesAutoresizingMaskIntoConstraints = false
addHereView.addSubview(similarView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
addHereView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
addHereView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
addHereView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// height of the child content
addHereView.heightAnchor.constraint(equalToConstant: 170.0),
similarView.topAnchor.constraint(equalTo: addHereView.topAnchor),
similarView.leadingAnchor.constraint(equalTo: addHereView.leadingAnchor),
similarView.trailingAnchor.constraint(equalTo: addHereView.trailingAnchor),
similarView.bottomAnchor.constraint(equalTo: addHereView.bottomAnchor),
])
similarVC.didMove(toParent: self)
}
private class parentView: UIView {
let scrollView = UIScrollView()
let backdropImageView = UIImageView()
let titleLabel = UILabel()
let overviewLabel = UILabel()
let addHereView = UIView()
private lazy var contentStackView = UIStackView(arrangedSubviews: [backdropImageView, titleLabel, overviewLabel])
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
backgroundColor = .white
addHereView.backgroundColor = .systemBlue
addHereView.translatesAutoresizingMaskIntoConstraints = false
backdropImageView.contentMode = .scaleAspectFill
backdropImageView.clipsToBounds = true
titleLabel.font = UIFont.Heading.medium
titleLabel.textColor = UIColor.Text.charcoal
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.setContentHuggingPriority(.required, for: .vertical)
overviewLabel.font = UIFont.Body.small
overviewLabel.textColor = UIColor.Text.grey
overviewLabel.numberOfLines = 0
overviewLabel.lineBreakMode = .byWordWrapping
contentStackView.axis = .vertical
contentStackView.spacing = 24
contentStackView.setCustomSpacing(8, after: titleLabel)
setupViewsHierarchy()
setupConstraints()
}
private func setupViewsHierarchy() {
addSubview(scrollView)
scrollView.addSubview(contentStackView)
}
private func setupConstraints() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
backdropImageView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: topAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
backdropImageView.heightAnchor.constraint(equalTo: backdropImageView.widthAnchor, multiplier: 11 / 16, constant: 0),
contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 24),
contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -24)
]
)
scrollView.layoutMargins = UIEdgeInsets(top: 24, left: 16, bottom: 24, right: 16)
preservesSuperviewLayoutMargins = false
}
func configure(movieDetails: MovieDetails) {
backdropImageView.dm_setImage(backdropPath: movieDetails.backdropPath)
titleLabel.text = movieDetails.title
overviewLabel.text = movieDetails.overview
}
}
}
Here is the code for SVC code ..
class SmiliarMovieViewController: UIViewController, UICollectionViewDelegate {
private let viewModel: MoviesDetailsViewModel
init(viewModel: MoviesDetailsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
navigationItem.largeTitleDisplayMode = .never
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var titleLable: UILabel = {
let titleLable = UILabel()
titleLable.translatesAutoresizingMaskIntoConstraints = false
titleLable.font = UIFont.systemFont(ofSize: 24)
titleLable.text = "Similar movies"
return titleLable
}()
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let width = 100
layout.itemSize = CGSize(width: width, height: 200)
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.translatesAutoresizingMaskIntoConstraints = false
collection.dataSource = self
collection.delegate = self
collection.register(SimilierMovieCell.self, forCellWithReuseIdentifier: SimilierMovieCell.identifier)
collection.backgroundColor = .clear
return collection
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
self.viewModel.updatedState = { [weak self] in
DispatchQueue.main.async {
self?.collectionView.reloadData()
}
}
viewModel.fetchSimilarMovie()
}
private func setUpUI() {
view.addSubview(titleLable)
view.addSubview(collectionView)
let safeArea = view.safeAreaLayoutGuide
titleLable.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
titleLable.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 10).isActive = true
titleLable.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: titleLable.bottomAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
collectionView.heightAnchor.constraint(equalToConstant: view.frame.width/2).isActive = true
}
}
Here is the cell code ..
class SimilierMovieCell: UICollectionViewCell {
static let identifier = "CompanyCell"
let columnSpacing: CGFloat = 16
let posterSize = CGSize(width: 92, height: 134)
let coverImage = UIImageView()
let tagView = TagView()
let titleLabel = UILabel()
let childStackView = UIStackView()
let containerStackView = UIStackView()
private func commonInit() {
layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
titleLabel.font = UIFont.systemFont(ofSize: 15)
titleLabel.textColor = UIColor.Text.charcoal
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
coverImage.contentMode = .scaleAspectFit
coverImage.layer.cornerRadius = 8
coverImage.layer.masksToBounds = true
childStackView.spacing = 10
childStackView.alignment = .leading
childStackView.axis = .vertical
containerStackView.spacing = columnSpacing
containerStackView.alignment = .top
containerStackView.translatesAutoresizingMaskIntoConstraints = false
setupViewsHierarchy()
setupConstraints()
}
func setupViewsHierarchy() {
contentView.addSubview(containerStackView)
childStackView.dm_addArrangedSubviews(coverImage)
childStackView.dm_addArrangedSubviews(titleLabel)
childStackView.dm_addArrangedSubviews(tagView)
containerStackView.dm_addArrangedSubviews(childStackView)
}
//Constraints
func setupConstraints() {
NSLayoutConstraint.activate([
containerStackView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
containerStackView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
containerStackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
containerStackView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
coverImage.widthAnchor.constraint(equalToConstant: posterSize.width),
coverImage.heightAnchor.constraint(equalToConstant: posterSize.height),
titleLabel.topAnchor.constraint(equalTo: coverImage.bottomAnchor),
tagView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor)
])
}
func configure(movie: Movie) {
commonInit()
titleLabel.text = movie.title
tagView.configure(.rating(value: movie.voteAverage))
if let path = movie.posterPath {
coverImage.dm_setImage(posterPath: path)
} else {
coverImage.image = nil
}
}
override func prepareForReuse() {
super.prepareForReuse()
}
}
Really, really quick example of using a child view controller...
We'll start with your "Similar" view controller. A collection view cell and a view controller that creates a collection view:
class MySimilarCell: UICollectionViewCell {
let imgView = UIImageView()
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
[imgView, label].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(v)
}
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
label.topAnchor.constraint(equalTo: imgView.bottomAnchor, constant: 4.0),
label.leadingAnchor.constraint(equalTo: g.leadingAnchor),
label.trailingAnchor.constraint(equalTo: g.trailingAnchor),
label.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
imgView.backgroundColor = .systemRed
imgView.tintColor = .white
label.textAlignment = .center
}
}
class MySimilarViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var cv: UICollectionView!
let data: [String] = [
"A", "B", "C", "D", "E", "F", "G", "H"
]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let fl = UICollectionViewFlowLayout()
fl.scrollDirection = .horizontal
fl.itemSize = .init(width: 120.0, height: 180.0)
cv = UICollectionView(frame: .zero, collectionViewLayout: fl)
cv.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cv)
// so we don't have to type so much
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
cv.topAnchor.constraint(equalTo: g.topAnchor),
cv.leadingAnchor.constraint(equalTo: g.leadingAnchor),
cv.trailingAnchor.constraint(equalTo: g.trailingAnchor),
cv.heightAnchor.constraint(equalToConstant: 180.0),
])
cv.dataSource = self
cv.delegate = self
cv.register(MySimilarCell.self, forCellWithReuseIdentifier: "c")
// so we can see the collection view framing
cv.backgroundColor = .cyan
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "c", for: indexPath) as! MySimilarCell
if let img = UIImage(systemName: "\(data[indexPath.item].lowercased()).circle") {
c.imgView.image = img
}
c.label.text = data[indexPath.item]
return c
}
}
If you set MySimilarViewController
as your Initial View Controller and run the app, it should look like this:
Next, we'll setup the "parent" controller, with an image view, a title label and a paragraph label, in a vertical stack view, in a scroll view, with space at the bottom for the "child":
class MyMainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// add an image view, title label, paragraph label
// to a vertical stack view
// added to a scroll view
let imgView = UIImageView()
imgView.backgroundColor = .white
imgView.tintColor = .systemOrange
if let img = UIImage(systemName: "swift") {
imgView.image = img
}
let titleLabel = UILabel()
titleLabel.backgroundColor = .green
titleLabel.font = .systemFont(ofSize: 28.0, weight: .bold)
titleLabel.text = "Spirited Away"
let paraLabel = UILabel()
paraLabel.backgroundColor = .green
paraLabel.font = .systemFont(ofSize: 20.0, weight: .regular)
paraLabel.numberOfLines = 0
paraLabel.text = "A young girl, Chihiro, becomes trapped in a strange new world of spirits. When her parents undergo a mysterious transformation, she must call upon the courage she never knew she had to free her family."
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 20.0
stackView.addArrangedSubview(imgView)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(paraLabel)
let scrollView = UIScrollView()
// so we can see the scroll view's framing
scrollView.backgroundColor = .systemYellow
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
let addHereView = UIView()
addHereView.backgroundColor = .systemBlue
addHereView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(addHereView)
// so we don't have to type so much
let g = view.safeAreaLayoutGuide
let cg = scrollView.contentLayoutGuide
let fg = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
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: addHereView.topAnchor, constant: -20.0),
stackView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 20.0),
stackView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 20.0),
stackView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -20.0),
stackView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: -20.0),
stackView.widthAnchor.constraint(equalTo: fg.widthAnchor, constant: -40.0),
// image view needs height
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: 1.0),
addHereView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
addHereView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
addHereView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// height of the child content
addHereView.heightAnchor.constraint(equalToConstant: 180.0),
])
}
}
Now, if we set MyMainViewController
as the Initial View Controller, we should get this:
The image and labels are in the (yellow) scroll view, and we have a blue view where we're going to add the child controller's view.
So, at the end of viewDidLoad()
in MyMainViewController
, we'll add this:
let similarVC = MySimilarViewController()
addChild(similarVC)
// safely unwrap MySimilarViewController's view
guard let similarView = similarVC.view else {
// this should never happen, unless we've really mis-written the detail view controller
fatalError("Similar VC had no view!!!")
}
similarView.translatesAutoresizingMaskIntoConstraints = false
addHereView.addSubview(similarView)
NSLayoutConstraint.activate([
similarView.topAnchor.constraint(equalTo: addHereView.topAnchor, constant: 0.0),
similarView.leadingAnchor.constraint(equalTo: addHereView.leadingAnchor, constant: 0.0),
similarView.trailingAnchor.constraint(equalTo: addHereView.trailingAnchor, constant: 0.0),
similarView.bottomAnchor.constraint(equalTo: addHereView.bottomAnchor, constant: 0.0),
])
similarVC.didMove(toParent: self)
So, we've added an instance of MySimilarViewController
as a child view controller, and we've added its view
as a subview of the blue view.
And we get this:
Edit
If you really, really want to stick with your current approach, couple suggestions...
1 - Set background colors to your UI elements, so you can see the framing.
2 - Learn how to use Debug View Hierarchy
so you can figure out what's going wrong with your layout.
After adding (and "faking") the missing information from your post in order to run your code and see the layout, this is what we get:
and if we try to scroll the content up, it only goes this far:
Let's use Debug View Hierarchy
to inspect the layout:
You've replaced the controller's built-in view with "parentView" (blue) and you've constrained your scroll view (red) to the full height of the view.
You've then added "similar" controller's view as a subview, but it's on top of the scroll view.
So, in parentView
-> setupConstraints()
, get rid of the scroll view's bottom anchor:
scrollView.topAnchor.constraint(equalTo: topAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
//scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
Then, in childView()
, after adding the current constraints, add this:
// make sure this controller's view was created as "parentView"
guard let pView = view as? parentView else {
fatalError("self.view was not instantiated correctly!")
}
// set the bottom constraint of the scroll view in parentView
// to the top of addHereView (minus 8-points "spacing")
pView.scrollView.bottomAnchor.constraint(equalTo: addHereView.topAnchor, constant: -8.0).isActive = true
similarVC.didMove(toParent: self)
Now, when run, we get this result:
and we can scroll up all the way to the bottom of the paragraph of text:
and our hierarchy now looks like this:
The scroll view no longer extends behind the collection view.