I know, I know. There are numerous similar (read: identical) posts about why creating views programmatically is superior or obsolete (depending on the author) versus making them via Storyboards/Xibs.
Luckily for you, I won’t go over why programmatic views are superior to Storyboards or Xibs. Why?
Because all three methods accomplish the same thing: creating the user interface of an iOS application.
There will be endless debates on the topic. Each method has its own strengths/weaknesses, and everyone has a preference. But more importantly, each method will be around for as long as iOS is alive.
As my finance professor used to say: “There is more than one way to get to Nassau Street.”
Why I like Programmatic Views
Personally, I learned more about UIView and its instance/class methods when I switched to creating views programmatically more than I ever did using Storyboards or Xibs. Storyboards/Xibs didn’t really teach me about the inner workings of UIKit, like when it calls certain UIView methods. Everything was magic. As a software developer, not knowing how things work is a HUGE no-no. Personally, it annoys me to no end if I don’t know how things work in software development. I lose sleep over it.
After scouring the Apple Documentation, Google, StackOverflow, and the iOSProgramming subreddit, I really improved my understanding of:
- Object Oriented Programming
In addition, you have finer control over what you can do with the user interface if you create views programmatically. (Who doesn’t like absolute control over what they make?)
Lastly (and also what I think is most important), every single property of the UIView subclasses you create are all visible in one .swift file. You don’t have to click each UIView in Interface Builder to see what the properties are set to. Everything you modified is explicit.
Why did I bother making a post about a topic that already has numerous articles about it?
I’ve read many articles/tutorials on programmatic views in iOS but none really adhered to the Model-View-Controller pattern.
The tutorials were brief, and the UIView code was located in the view controller (you should never be making views in the view controller). I basically had to guess what to do to separate the UIView code from the view controller but luckily after searching far and wide on Google, I found a bit of advice that helped me get started:
A single UIView subclass should be created for each view controller, essentially acting as a Xib that’s written in Swift instead of XML.
The Goal Of This Post
To give the reader (you) an idea of how to write views programmatically (using SnapKit) without breaking the Model-View-Controller design pattern and preventing the creation of the Massive View Controller.
This isn’t a beginner friendly tutorial
I’m assuming you know the basic project structure of a Single View Application in Xcode for Swift and know what Cocoapods is and how to install pods using it. You should have some familiarity with retain cycles and an understanding of value types vs. reference types.
With that said, let’s get started!
The Tutorial! — Basic Setup
Let’s start our journey into programmatic views with everyone’s favorite view: the Login Authentication screen!
But before we get started, let’s start with the basic setup.
- Create a new project: Single View Application
- Delete Main.storyboard (put in trash and permanently delete)
- Go to info.plist and delete “Main storyboard file base name”
- Rename ViewController.swift to LoginVC.swift
- Rename class
6. Go to your AppDelegate.swift file, and add the following in the
Run the simulator. It shouldn’t crash, and you should see a green screen
7. Initialize your Podfile using
pod init in the terminal at the directory of the project you’re using to follow along, and install SnapKit
After installing SnapKit, open up the .xcworkspace file of the project, and build the project using cmd + b. That should index and compile the project, and take out any false errors when interacting with Snapkit pod
8. Create a new .swift file called LoginView, and type the following boilerplate code. (When you start to override initWithFrame (Objective-C jargon) initializer this is the boilerplate code the Swift compiler types for you)
Now maybe you maybe thinking at this point, I’ll start doing something like this:
You’re half correct. I’m actually going to use closures to set these subviews up.
setUpKeyboard method is a custom method I have that speeds up modifying a UITextField’s keyboard properties. It is an
extension to UITextField:
I also have another helper function called
set(cornerRadius:_). All it does is set the
cornerRadius properly on a
Now you might be wondering about the closures.
The reason why I use them is because they perfectly encapsulate each subview’s properties away from other subviews’ properties. This awesome separation of logic is what we need to make our code readable.
They are also set once and only once when the subview is instantiated in
LoginView. How do I know this? From this.
Another thing you might be wondering about is the
lazy declaration in the
stackView property. The
lazy keyword allows you to use
self in the closure. Without it we can’t add other subviews in the
stackView closure. Also, the capture list with
unowned self is important because of a possible retain cycle since closures are reference types. The
unowned declaration ensures that doesn’t happen.
Also you may be wondering about the various properties I’m modifying in each subview, and how that might violate the DRY principle. My answer is each of these properties also exist in Interface Builder, and I probably am violating DRY considering these two textfields are identical aside from the placeholder text.
You can create a helper function that takes in a string for the placeholder text and sets all the other properties to identical values but my personal preference is to show and explicitly modify each subview’s properties within the closure so a teammate looking at the code knows what each property is being set to.
You can probably abstract the “AvenirNext-Regular” magic string I created to a global string
enum called Font which has all of your application’s fonts but for the sake of convenience I didn’t do that.
Finally, before we can start constraining the subviews with AutoLayout we have to do one more thing: add the subviews as subviews to
We haven’t done that yet! :)
I typically use these helper functions to make this easy for myself:
initWithFrame initializer add the following:
What about the other subview? They’re already subviews of stackView so by that logic they’re in
LoginView’s view hierarchy already :)
If you don’t add
LoginView’s subview you’ll get an instant crash.
Also I’m well aware that SnapKit takes care of the
translatesAutoresizingMaskIntoConstraints property for you. But I think it’s good practice to change it yourself.
Now for the constraints!
initWithFrame initializer put this code in:
SnapKit makes it easy to create constraints. What constraints did we put?
stackViewis centered in the screen
stackView’s leading edge (left edge) is constrained to the
superview(LoginView’s left edge) and has an offset of 40.0 points (which means it’s 40.0 points away from LoginView’s left edge)
stackView’s trailing edge (right edge) is constrained to the
superview(LoginView’s right edge) has an inset of 40.0 points (which means it’s 40.0 points away LoginView’s right edge)
emailTextField’s height is equal to 7.5% of the
superview’s height and because the
stackView’s distribution property is
loginButton’s heights are also 7.5% of the superview’s height
You may be wondering why we’re laying out the constraints code in the initializer. The answer is because most of the time we need to only do this once and to make sure it’s only done once, do it in the initializer.
If you have more than one of the same constraint, your application will build up its memory footprint causing you problems in the future. This is especially true if it involves a table or collection view full of cells with duplicate constraints.
If you want to be extra safe use
remakeConstraints instead. (It’s the same as
makeConstraints except it removes all existing constraints of the UIView before creating any.
We can’t see what the view looks like yet because we haven’t connected it to
LoginVC. Let’s do that now.
LoginVC, add the following code:
loadView method is the key to connecting
This method is also one of the few methods in UIKit where you don’t call the super’s implementation.
Now we can run simulator. Your simulator should look something like this:
11. Making the equivalent of IBOutlets.
So if you used storyboards/xibs you would have had to make code like this at one point:
@IBOutlet weak var emailTextField: UITextField!
and connected it from the storyboard/xib
Programmatic Views can do something similar and more akin to Object Oriented Programming :)
Add this to inside LoginVC (The equivalent of putting IBOutlets in the view controller):
Oh no! A forced downcast!
view property as an instance of
LoginView in the
loadView method therefore, this forced downcast is safe and will succeed. We also make them weak references to avoid any retain cycles.
What about IBActions? Can we make those programmatically? The answer: Yup.
IBActions are just Target Action mechanisms
12. Create a function to be associated with
loginButtonPressed will be the action that is invoked when the
loginButton is pressed by the user. How do we connect this function to the
Doing this in
Congrats! You connected a function to a UIControl programmatically!
Now run it in the simulator and it should work :)
So the final code in LoginVC should be this:
I hope you found this tutorial enjoyable and informative. I definitely had fun making it.
Here’s the link to the repo for future reference!
If you have any questions please comment away. This was my first attempt at making an iOS tutorial. Please let me know on what I can improve on.