Nov 4, 2020
Programatic UI in UIKit without Storyboards
Use simple functions and layout anchors to create a simple UI without storyboards.
This doesn't reflect my opinions circa 2024, however I'm leaving this public cause removing media is always bad. This was written while I was in high school, so take it with a large grain of salt and don't judge my writing abilities 😅.
Why are storyboards all that bad?
Answer: They're not
Really, storyboards are super good at helping create UI. They make it easy to see exactly what you're building and work with constraints on each view. Sometimes though, it's easier to not use them. For more repetitive views, using storyboards will be a pain, having to link every single input and output to your UIViewController
file, copy-pasting the whole thing. It leads to bugs and spaghetti code.
Sometimes though, in an app with many views storyboards get messy and overloaded. It then becomes easier to use programmatic views and try not to use storyboards. Otherwise there's no disadvantage to using them.
The best way that I've found to create View Controllers programmatically is this.
We'll be making a simple UI with a label, image and button as an example.
First, create your UIViewController
. We'll call it SampleViewController
import UIKit
class SampleViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
There. Now lets add a few methods underneath the viewDidLoad()
function.
private func setUpViews() {
}
private func setUpConstraints() {
}
These methods will hold our UI setup code. Let's add some variables to the top of this class to reference the 3 UI item's we're going to add
class SampleViewController: UIViewController {
var label: UILabel?
var button: UIButton?
var image: UIImage?
...
Great! Now lets populate those variables.
In the setUpViews()
function put this code in.
private func setUpViews() {
label = UILabel()
label?.text = "Hello World!"
label?.translatesAutoresizingMaskToConstraints = false //Important to do with all views. If you don't set this to false, iOS will break all the constraints you will set.
button = UIButton()
button?.titleLabel.text = "Press Me"
button?.tintColor = .systemBlue
button?.translatesAutoresizingMaskToConstraints = false //Important
button?.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) //Will return an error right now, ignore it as we haven't added the target function yet.
image = UIImage()
image?.image = UIImage(systemName: "gamecontroller")
image?.translatesAutoresizingMaskToConstraints = false //Important
//Add the views we just created to the view hierarchy
view.addSubview(label!) //we can force-unwrap these because we know we just made them and they won't be nil.
view.addSubview(button!)
view.addSubview(image!)
}
Awesome! It's easy right? You have complete access to each view you're adding right here in this function. You modify it and add targets and images cleanly and knowing exactly what each one is going to be.
Now lets add some constraints and lay out our UI.
We'll be using layout anchors to layout our views. They're much simpler to deal with than constraints.
In the setUpConstraints()
function we defined before enter this.
private func setUpConstraints() {
NSLayoutConstriant.activate([
// Place the label in the top
label.topAnchor.constraint(equalTo: view.topAnchor),
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
label.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
// put the button centered below the label
button.topAnchor.constraint(equalTo: label.bottomAnchor),
button.widthAnchor.constraint(equalToConstant: 250),
button.heightAnchor.constraint(equalToConstant: 100),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
//put the image below the button
image.topAnchor.constraint(equalTo: button.bottomAnchor),
image.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
image.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16)
])
}
The function NSLayoutConstraint.activate([])
activates multiple constraints at once. So we use it here to activate all of ours at once.
Make sure to add the setUpViews()
and setUpConstraints
to your viewDidLoad()
function. Otherwise our code won't ever be executed :).
Let's also add the button target code at the end of the file:
@objc func buttonPressed() {
print("You Pressed the Button!")
}
Sweet! You're all done! You've made a UIViewController with a UI without any storyboards.
Here's the full code:
import UIKit
class SampleViewController: UIViewController {
var label: UILabel?
var button: UIButton?
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
setUpViews()
setUpConstraints()
}
private func setUpViews() {
label = UILabel()
label?.text = "Hello World!"
label?.translatesAutoresizingMaskToConstraints = false //Important to do with all views. If you don't set this to false, iOS will break all the constraints you will set.
button = UIButton()
button?.titleLabel.text = "Press Me"
button?.tintColor = .systemBlue
button?.translatesAutoresizingMaskToConstraints = false //Important
button?.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) //Will return an error right now, ignore it as we haven't added the target function yet.
image = UIImage()
image?.image = UIImage(systemName: "gamecontroller")
image?.translatesAutoresizingMaskToConstraints = false //Important
//Add the views we just created to the view hierarchy
view.addSubview(label!) //we can force-unwrap these because we know we just made them and they won't be nil.
view.addSubview(button!)
view.addSubview(image!)
}
private func setUpConstraints() {
NSLayoutConstriant.activate([
// Place the label in the top
label.topAnchor.constraint(equalTo: view.topAnchor),
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
label.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
// put the button centered below the label
button.topAnchor.constraint(equalTo: label.bottomAnchor),
button.widthAnchor.constraint(equalToConstant: 250),
button.heightAnchor.constraint(equalToConstant: 100),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
//put the image below the button
image.topAnchor.constraint(equalTo: button.bottomAnchor),
image.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
image.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16)
])
}
@objc func buttonPressed() {
print("You Pressed the Button!")
}
}