How to use container view programmatically - ios

I created a ViewController, and I want to add in my ViewController a container view, here I set the container view inside my ViewController:
var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
func setUpViews() {
view.addSubview(containerView)
containerView.heightAnchor.constraint(equalToConstant: 300).isActive = true
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
Here I set my instance of SecondViewController in the containerView:
override func viewDidLoad() {
super.viewDidLoad()
setUpViews()
let secondViewController = SecondViewController()
secondViewController.willMove(toParent: self)
containerView.addSubview(secondViewController.view)
self.addChild(secondViewController)
secondViewController.didMove(toParent: self)
}
In my SecondViewController, I declared label and a view, I set the label in the center of the view:
let label: UILabel = {
let label = UILabel()
label.text = "Hello!"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myView)
myView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
myView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
myView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
That's what I see in my app, but I aspected to see a label in the center of the gray view.
It doesn't work like I aspected and I don't understand why.

You need to set the frame and/or constraints on the loaded view:
override func viewDidLoad() {
super.viewDidLoad()
setUpViews()
let secondViewController = SecondViewController()
secondViewController.willMove(toParent: self)
containerView.addSubview(secondViewController.view)
// set the frame
secondViewController.view.frame = containerView.bounds
// enable auto-sizing (for example, if the device is rotated)
secondViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addChild(secondViewController)
secondViewController.didMove(toParent: self)
}

Related

Programatically adding UITextField above UITableView

I'm trying to make a search application using MusicBrainz API, where the API will return JSON data that matches the search term typed in by the user.
This is what I have so far of my UI:
import UIKit
import WebKit
class ArtistListViewController: UIViewController{
let tableView = UITableView()
var safeArea: UILayoutGuide!
var artists: [Artists]?
let textField = UITextField()
var searchTerm = "Search"
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
safeArea = view.layoutMarginsGuide
searchBar()
setUpTable()
setUpNavigation()
}
func searchBar(){
view.addSubview(textField)
textField.placeholder = "Search"
textField.frame = CGRect(x: 10,y: 200,width: 300.0,height: 30.0)
textField.borderStyle = UITextField.BorderStyle.line
textField.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
textField.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func setUpTable(){
view.addSubview(tableView)
ArtistSearchModelData().loadArtists(searchTerm: "Adele"){ [weak self] (artists) in
self?.artists = artists
DispatchQueue.main.async{
self?.tableView.reloadData()
}
}
//populate with data
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
//turn off autoresizing
tableView.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func setUpNavigation(){
self.navigationItem.title = "Artists"
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.orange,
NSAttributedString.Key.font: UIFont(name: "Arial-BoldMT", size: 30)
]
}
}
This is what my UI looks like:
As you can see my search bar is completely missing and I have no idea how to render it.
I tried using a UIStackView but got the same results.
I've tried searching the internet and found similar solutions but couldn't get any of them to work.
Adding both textField and tableView to a custom subview renders nothing too, maybe because they're functions? Am I just going about this the wrong way?
Any help is appreciated!
You constrain your text field to the top of the view:
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
then, you constrain your table view to the top of the view:
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
So your table view is covering your text field.
You could do this:
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
To constrain the Top of the table view to the Bottom of the text field.
As a side note, you should constrain to the view's Safe Area ... not to the view itself:
textField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
Edit
Here is your class with the above modifications (note that I commented-out the stuff I don't have access to, such as your Artist specific code):
class ArtistListViewController: UIViewController{
let tableView = UITableView()
var safeArea: UILayoutGuide!
//var artists: [Artists]?
let textField = UITextField()
var searchTerm = "Search"
//var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
safeArea = view.safeAreaLayoutGuide
searchBar()
setUpTable()
setUpNavigation()
}
func searchBar(){
view.addSubview(textField)
textField.placeholder = "Search"
// not needed
//textField.frame = CGRect(x: 10,y: 200,width: 300.0,height: 30.0)
textField.borderStyle = UITextField.BorderStyle.line
textField.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
// constrain Top to safeArea Top
textField.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: safeArea.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: safeArea.rightAnchor).isActive = true
// don't constrain the bottom
//textField.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func setUpTable(){
view.addSubview(tableView)
// ArtistSearchModelData().loadArtists(searchTerm: "Adele"){ [weak self] (artists) in
// self?.artists = artists
//
// DispatchQueue.main.async{
// self?.tableView.reloadData()
// }
// }
//
// //populate with data
// tableView.delegate = self
// tableView.dataSource = self
// tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
//turn off autoresizing
tableView.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
// constrain Top to textField Bottom
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: safeArea.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: safeArea.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
}
func setUpNavigation(){
self.navigationItem.title = "Artists"
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.orange,
NSAttributedString.Key.font: UIFont(name: "Arial-BoldMT", size: 30)
]
}
}
If you run that code as-is, you should get your Search textField above the (empty) tableView.
If you then un-comment your Artist-specific code, it should work properly.
Update the constraints and it will work:
//Layout Configs
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
textField.heightAnchor.constraint(equalToConstant: 30).isActive = true
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
Couldn't exactly find out why you're using UIViewController instead of UITableViewController so here's a solution for the second one...
For those kind of tasks, instead of embedding a UITextField, there's a simpler, ready for use solution called UISearchController-
Use a search controller to provide a standard search experience of the
contents of another view controller. When the user interacts with a
UISearchBar, the search controller coordinates with a search results
controller to display the search results.
import UIKit
class TableViewController: UITableViewController, UISearchResultsUpdating, UISearchControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
createSearchController()
}
// MARK: - Table view data source
let items = ["item1", "item2", "item3", "item4"]
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
cell.detailTextLabel?.text = items[indexPath.row] + " detail"
return cell
}
// MARK: - Search controller
private let searchController = UISearchController(searchResultsController: nil)
func createSearchController(){
searchController.searchResultsUpdater = self
searchController.delegate = self
tableView.tableHeaderView = searchController.searchBar
}
func updateSearchResults(for searchController: UISearchController) {
// Do something with it
let searchedText = searchController.searchBar.text
}
}
That's how it looks like in the end.
when use call two methods searchBar() setUpTable() - your tableView will be over searchBar. I mean the order that you chose to call this methods
any way when you call it like you did some cell at the top will hide under searchBar.
you can make this constraints
textField.translatesAutoresizingMaskIntoConstraints = false
textField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
textField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
textField.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: textField.bottomAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
or you can add your searchBar into navigationBar
let searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
navigationItem.searchController?.searchBar.delegate = self
navigationItem.searchController?.obscuresBackgroundDuringPresentation = false
navigationItem.searchController?.hidesNavigationBarDuringPresentation = false
navigationItem.searchController?.searchBar.placeholder = "Enter text here..."

ViewController Containment with Subclassing

in an attempt to up my "getting away from massive view controllers" I am trying some stuff with view controller containment.
Here's what I want:
have a base class with a scroll view and an embedded stack view
inherit from that class with an implementation of my desired scene
being able to add child view controllers as needed to my implementation
I expect something like this:
Here's the code I am trying to achieve this with:
import UIKit
/**
Base class
*/
class PrototypeStackViewController: UIViewController {
private let scrollView: UIScrollView = {
let s = UIScrollView()
s.translatesAutoresizingMaskIntoConstraints = false
s.contentMode = .scaleToFill
return s
}()
private let stackView: UIStackView = {
let s = UIStackView()
s.translatesAutoresizingMaskIntoConstraints = false
s.axis = .vertical
s.alignment = .fill
s.distribution = .fill
s.contentMode = .scaleToFill
return s
}()
override func loadView() {
view = UIView()
view.backgroundColor = .systemGray
view.addSubview(scrollView)
scrollView.addSubview(stackView)
let nstraint = stackView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor)
stackViewHeightConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
stackViewHeightConstraint
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
func add(child: UIViewController) {
addChild(child)
stackView.addArrangedSubview(child.view)
child.didMove(toParent: self)
}
func remove(child: UIViewController) {
guard child.parent != nil else { return }
child.willMove(toParent: nil)
stackView.removeArrangedSubview(child.view)
child.view.removeFromSuperview()
child.removeFromParent()
}
}
/**
Implementation of my scene
*/
class PrototypeContainmentViewController: PrototypeStackViewController {
lazy var topViewController: PrototypeSubViewController = {
let t = PrototypeSubViewController()
t.view.backgroundColor = .systemRed
t.label.text = "Top View Controller"
return t
}()
lazy var centerViewController: PrototypeSubViewController = {
let t = PrototypeSubViewController()
t.view.backgroundColor = .systemGreen
t.label.text = "Center View Controller"
return t
}()
lazy var bottomViewController: PrototypeSubViewController = {
let t = PrototypeSubViewController()
t.view.backgroundColor = .systemBlue
t.label.text = "Bottom View Controller"
return t
}()
override func loadView() {
super.loadView()
add(child: topViewController)
add(child: centerViewController)
add(child: bottomViewController)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
/**
Sample View Controller
*/
class PrototypeSubViewController: UIViewController {
lazy var label: UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override func loadView() {
view = UIView()
view.backgroundColor = .systemRed
view.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12),
label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Here's what I get:
If you look close, you can even see the "Bottom View Controller" label – but on top the green center view controller.
I am missing something here in my love-hate-relation with auto layout, that much is for sure...
You are missing the width constraint that ensures that the stack view fills the width of the scroll view:
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
Also I would set your label constraints in this way:
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12),
label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12),
label.top.constraint(equalTo: view.topAnchor),
label.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
Had to make some tweaks to make it work.
First of all, to span over the whole width, I was missing stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), which Francesco Deliro correctly told me in the other answer.
Also, turns out I needed to change my stackViews distribution to .fillProportionally.
The code I was able to get what I showed on the question's picture is now this:
/**
Base Class
*/
class PrototypeStackViewController: UIViewController {
private let scrollView: UIScrollView = {
let s = UIScrollView()
s.translatesAutoresizingMaskIntoConstraints = false
s.contentMode = .scaleToFill
return s
}()
private let stackView: UIStackView = {
let s = UIStackView()
s.translatesAutoresizingMaskIntoConstraints = false
s.axis = .vertical
s.alignment = .fill
s.distribution = .fillProportionally //CHANGED to this
s.contentMode = .scaleToFill
return s
}()
override func loadView() {
view = UIView()
view.backgroundColor = .systemGray
view.addSubview(scrollView)
scrollView.addSubview(stackView)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
//CHANGED set of constraints here, seems as though width and heigt anchors are needed
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor),
stackView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor)
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
func add(child: UIViewController) {
addChild(child)
stackView.addArrangedSubview(child.view)
child.didMove(toParent: self)
}
func remove(child: UIViewController) {
guard child.parent != nil else { return }
child.willMove(toParent: nil)
stackView.removeArrangedSubview(child.view)
child.view.removeFromSuperview()
child.removeFromParent()
}
}
class PrototypeContainmentViewController: PrototypeStackViewController {
lazy var topViewController: PrototypeSubViewController = {
let t = PrototypeSubViewController()
t.view.backgroundColor = .systemRed
t.label.text = "Top View Controller"
return t
}()
lazy var centerViewController: PrototypeSubViewController = {
let t = PrototypeSubViewController()
t.view.backgroundColor = .systemGreen
t.label.text = "Center View Controller"
return t
}()
lazy var bottomViewController: PrototypeSubViewController = {
let t = PrototypeSubViewController()
t.view.backgroundColor = .systemBlue
t.label.text = "Bottom View Controller"
return t
}()
override func loadView() {
super.loadView()
add(child: topViewController)
add(child: centerViewController)
add(child: bottomViewController)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
class PrototypeSubViewController: UIViewController {
lazy var label: UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.textAlignment = .center
return l
}()
override func loadView() {
view = UIView()
view.backgroundColor = .systemRed
view.addSubview(label)
NSLayoutConstraint.activate([
//CHANGED to this set of constraints
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Hope that this answer will help future me and potentially others as well :-)

Thread 1: signal SIGABRT error in AppDelegate when add subView to main view

App crash when add some views (i. e. UIView, UITextView, UIImageView, ...) to main view
import UIKit
class ViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
textview.translatesAutoresizingMaskIntoConstraints = false
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
view.addSubview(textview)
}
}
How to create views by programmatically?
Edit: I forgot to add the view before setting constraints. So you need to set constraints after adding subviews.
You need to add subview first view.addSubview(textview) before adding constraints and set height and width also for textview as you can see updated code:
import UIKit
class ViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textview)
textview.translatesAutoresizingMaskIntoConstraints = false
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
textview.widthAnchor.constraint(equalToConstant: 150).isActive = true
textview.heightAnchor.constraint(equalToConstant: 80).isActive = true
}
}
First you need to add view.addSubview(textview) then add constraints.
import UIKit
class testViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textview)
textview.translatesAutoresizingMaskIntoConstraints = false
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// you need to specify height and width constraints as well otherwise UITextView will not appear
textview.widthAnchor.constraint(equalToConstant: 100).isActive = true
textview.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
}
You must set constraints after view.addSubview
like this :
import UIKit
class ViewController: UIViewController {
let textview: UITextView = {
let textview2 = UITextView()
textview2.text = "Hello World"
textview2.font = UIFont.boldSystemFont(ofSize: 18)
textview2.textAlignment = .center
return textview2
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textview)
textview.translatesAutoresizingMaskIntoConstraints = false
textview.heightAnchor.constraint(equalToConstant: 100).isActive = true
textview.widthAnchor.constraint(equalToConstant: 200).isActive = true
textview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}

How do I align ImageView to the bottom of ScrollView programmatically?

I am trying to align a background image to the bottom of a scroll view that fits the screen, programmatically using Autolayout. Ideally, I want the image to be always aligned at the bottom of the scroll view. When the content of the scroll view goes beyond the screen height or when scroll view content size is less than screen height with scroll view fitting the whole screen.
MyView
class MyView: UIView {
let myScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = false
return scrollView
}()
let contentView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let myLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello world"
label.font = UIFont.systemFont(ofSize: 24)
return label
}()
let myImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = #imageLiteral(resourceName: "Mask Group 3")
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
setupConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
backgroundColor = .white
addSubview(myScrollView)
myScrollView.addSubview(contentView)
contentView.addSubview(myLabel)
contentView.addSubview(myImageView)
}
private func setupConstraints() {
myScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
myScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
myScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
myScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: myScrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: myScrollView.bottomAnchor).isActive = true
contentView.leftAnchor.constraint(equalTo: myScrollView.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: myScrollView.rightAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
// If I am setting this and when the content size go beyond the screen, it does not scroll
// If I don't set this, there is no content size and image view will not position correctly
// contentView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
myLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 200).isActive = true
myLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
myImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
myImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
}
}
MyViewController
import UIKit
class MyViewController: UIViewController {
override func loadView() {
view = MyView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Illustration
I have found the solution.
A contentView is needed.
Set the contentView's top, left, bottom, right constraint equal to scrollView edges.
Set the contentView's width equal to view's width anchor
Set the contentView's height greaterThanOrEqualTo view's height anchor
Set the imageView's bottom equal to the bottom anchor of contentView.
For the imageView's top, set the constraint to an element with greaterThanOrEqualTo, to give it a constant gap and avoid overlapping of elements in smaller screens.
It is seems ok:
private func setupConstraints() {
myScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
myScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
myScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
myScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: myScrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: myScrollView.bottomAnchor).isActive = true
contentView.leftAnchor.constraint(equalTo: myScrollView.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: myScrollView.rightAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
// If I am setting this and when the content size go beyond the screen, it does not scroll
// If I don't set this, there is no content size and image view will not position correctly
contentView.heightAnchor.constraint(equalToConstant: 1400).isActive = true
myLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 200).isActive = true
myLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
myImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
myImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
myImageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
If think you just forgot to specify image height:
myImageView.heightAnchor.constraint(equalToConstant: 200).isActive = true

Setting a view controller's view's frame with constraints?

This app has no storyboard. This is the root view controller where ViewController2 (page2) is instantiated. ViewController2's view is given constraints (leading, top, width, height). The constraints appear to work great except when I create a UINavigationController in page2.
class ViewController0: UIViewController {
let scrollView = UIScrollView()
let page1 = ViewController1()
let page2 = ViewController2()
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.allButUpsideDown
}
override func loadView() {
setView()
addScrollView()
addToScrollView()
}
func setView() {
view = UIView()
}
func addScrollView() {
scrollView.bounces = false
scrollView.isPagingEnabled = true
scrollView.backgroundColor = UIColor.brown
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1.0).isActive = true
}
func addToScrollView() {
addChildViewController(page1)
page1.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(page1.view)
page1.didMove(toParentViewController: self)
page1.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
page1.view.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
page1.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = true
page1.view.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1.0).isActive = true
page1.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
addChildViewController(page2)
page2.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(page2.view)
page2.didMove(toParentViewController: self)
page2.view.leadingAnchor.constraint(equalTo: page1.view.trailingAnchor).isActive = true
page2.view.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
page2.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = true
page2.view.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1.0).isActive = true
page2.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
page2.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}
}
Because for the navigation controller to work properly on page2, I must explicitly set the view's frame, as seen below with view.frame = UIScreen.main.bounds even though I set it in ViewController0 using constraints. If I don't again set the view's frame like so (below), the bounds of the view extend to infinity and the constraints within page2 become useless.
class ViewController2: UIViewController {
var navigation = UINavigationController()
let navigationRoot = ViewController3()
override func loadView() {
setView()
addNavigation()
}
func setView() {
view = UIView()
view.frame = UIScreen.main.bounds
}
func addNavigation() {
self.addChildViewController(navigationRoot)
navigationRoot.didMove(toParentViewController: self)
navigation = UINavigationController(rootViewController: navigationRoot)
view.addSubview(navigation.view)
}
}
I was under the impression that when you use constraints, you can forego the frame property of views entirely. Did I not set up the constraints correctly? Or because this is purely programmatic I must, therefore, at times, explicitly set the frame of a view?

Resources