I am so confused. I cannot get any subviews to appear in a scrollView
Specifically, I want to add the view of another view controller, but I can't even add any labels or anything. I cannot see any subviews in the view debugger hierarchy either
What am I doing wrong??
import UIKit
class MainViewController: UIViewController {
var scrollView: UIScrollView {
let scrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
scrollView.isPagingEnabled = true
scrollView.backgroundColor = .orange
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width * 3.0, height: UIScreen.main.bounds.height)
return scrollView
}
var leftViewController: UIViewController {
let vC = UIViewController()
vC.view.backgroundColor = .purple
return vC
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
leftViewController.view.frame = .init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
addChild(leftViewController)
scrollView.addSubview(leftViewController.view)
leftViewController.didMove(toParent: self)
}
}
can achieve what I want to like this with auto layout, still hard coding widths though.
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
scrollViewContainer.addArrangedSubview(redView)
scrollViewContainer.addArrangedSubview(blueView)
scrollViewContainer.addArrangedSubview(greenView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .horizontal
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let redView: UIView = {
let view = UIView()
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
view.backgroundColor = .red
return view
}()
let blueView: UIView = {
let view = UIView()
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
view.backgroundColor = .blue
return view
}()
let greenView: UIView = {
let view = UIView()
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
view.backgroundColor = .green
return view
}()
}
If nothing else, if your first example that was provided in your original question, you are using computed properties. These will return a new instance every time you reference them. You should make these stored properties:
var scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
scrollView.isPagingEnabled = true
scrollView.backgroundColor = .orange
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width * 3.0, height: UIScreen.main.bounds.height)
return scrollView
}()
var leftViewController: UIViewController = {
let vC = UIViewController()
vC.view.backgroundColor = .purple
return vC
}()
Note the =
and adding the ()
at the end to designate that they’re initialized with closures.
The problem with computed properties is that because each reference returns a new instance, the child view controller’s view whose frame
you adjusted is the not the same one you added as a subview. And the scroll view to which you added the child is not the same scroll view that you added to your container view controller.
In your revised question, you shared code using constraints. If you use constraints, make sure that you set translatesAutoresizingMaskIntoConstraints
to false
for all the relevant views. Otherwise it will automatically translate the auto resizing mask of the view into constraints that will conflict with the new constraints that you are adding manually.
And you should also avoid constraint(equalToConstant:)
, using constraint(equalTo:)
, referencing the main view, wherever possible. For example:
class ViewController: UIViewController {
let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isPagingEnabled = true
return view
}()
let redView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
let greenView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .green
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(redView)
scrollView.addSubview(greenView)
NSLayoutConstraint.activate([
// set the scroll view to be the size of the main view
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// set the subviews to be the size of the main view, too
redView.widthAnchor.constraint(equalTo: view.widthAnchor),
redView.heightAnchor.constraint(equalTo: view.heightAnchor),
greenView.widthAnchor.constraint(equalTo: view.widthAnchor),
greenView.heightAnchor.constraint(equalTo: view.heightAnchor),
// now dictate where these subviews are in the scroll view vertically ...
redView.topAnchor.constraint(equalTo: scrollView.topAnchor),
greenView.topAnchor.constraint(equalTo: scrollView.topAnchor),
redView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
greenView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
// ... and horizontally
redView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
greenView.leadingAnchor.constraint(equalTo: redView.trailingAnchor),
greenView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
])
}
}
Frankly, since you’re talking about full-screen child view controllers, I’d jettison this code entirely, and just use UIPageViewController
(which not only gets us out of the business of sizing child view controller’s top level views, but lets us do a more efficient “just in time” creation of child view controllers, too).
But I wanted to show what the constraints would look like if you were to do this manually.