Stop Using Storyboard, Start Building Programmatic UI

Last modified on September 19th, 2022
iOS Swift

In this iOS 100% programmatically tutorial, I am going to start talking about my views on why you should stop using Storyboard and consider building your UI design for your iOS App 100% Programmatically.

After that, I will show you how to build a simple login screen user interface 100% programmatically in 4 steps.

Let’s talk about why I think you need to stop using Storyboard.

I used Storyboard for one of my recent iOS projects because I did not have a lot of experience with building complex iOS apps.

I thought that was the right thing to do for an app.

However, I ran into a few issues working with Storyboard:

1. IBAction/IBOutlet Issues: When I refactored the code by changing the IBOutlet/IBAction variable name after a few days of work, the app crashed for no apparent reason and I had to reconnect it every time that happened.

2. Nested Views: When I added a few nested subviews under a view, it was hard to adjust when I wanted two subviews to be placed one behind another. On top of that, adding constraints to them was one of the most frustrating things to do.

3. Compile Time: I had an opportunity to build an app with around 35 Storyboard scenes. After switching to complete code I noticed that the compile-time was lightning fast.

Before I had an issue that, as I add more scenes to my storyboard, I had to wait a little longer even though I have a pretty fast machine with a 2GB graphics card and 16 GB memory.

I can’t insist on how much more reliable that code was when I wrote everything in code without using Storyboard. 🙂

I like to use Storyboard for quick mockups and prototypes, but not for the production.

I did find that it took more time than using Storyboard in some cases, such as adding things to the view, etc.

However, overall I think I saved a lot of time without using Storyboard.

I want to tell any new iOS Developer to stop using Storyboard and design UI programmatically in your iOS app.

Eventually, you will be building UI Design programmatically anyway, so why not do it now?

I hope I have convinced you to go straight to building UI programmatically.

Let’s jump right in and build a simple login screen 100% programmatically, like in the screenshot below.

Step #1: Get Rid of Storyboard
Step #2: Set A Root View Controller
Step #3: Add Subviews
Step #4: Enable Auto Layout Constraints

STEP #1: Get Rid of Storyboard & it’s references

  • Go ahead and create a project in Xcode.
  • Then, go to the Project NavigatorMain.Storyboard file and
  • Hit delete key and choose Remove Reference to get rid of it…Yep, you heard it right 🙂

Now, let’s get rid of the two instances that point to the Main. storyboard file.

1. Go to Project Navigator → top-level project folder → General Tab → Deployment Info section → clear the text Main from the input field, which is next to the Main Interface label, like in the image below.

2. Go to the Project Navigator → info.plist file → Application Scene Manifest property → Scene Configurationitem 0 and get rid of the property Storyboard Name by clicking the icon that has a minus in the circle next to it.

► Run the app and you will see the black screen and let’s change that by adding a root view controller.

Recommended
▶︎ Get User Location Real-Time in your iOS App Quickly

STEP #2 Set A Root View Controller

Open up SceneDelegate.swift file from the Project Navigator and add the following code to the willConnectTo() method:

Replace the existing code to

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {    
    guard let winScene = (scene as? UIWindowScene) else { return }
    window = UIWindow(windowScene: winScene)
    window?.rootViewController = ViewController()
    window?.makeKeyAndVisible()
}

Make sure that the ViewController() which is assigned to the window’s rootViewController matches with the file name ViewController.Swift.

One more thing I want to do before running the app is to adding a background color to the ViewController() class so that I can make sure that it’s working properly.

Go to ViewController.swift class file→ viewDidLoad() method.

Add the following code to that method,

view.backgroundColor = .systemRed

If everything goes well, you should be able to see the background color similar to the screenshot above when you run the app.

Recommended
▶︎ Get User Location Real-Time in your iOS App Quickly

Step #3: Add Subviews Programmatically

I am going to need four views in my ViewController.swift in order to match the original sample design.

1 — UIView
2 — TextFields
1 — UIButton

Note: I could use UIStackView for arranging views, however, for simplicity’s sake, I am using UIView.

The first thing you need to do is create UIView which is the wrapper for the login form.

Create a property called loginContentView in the ViewController.swift class, (normally above the ViewDidLoad() method).

private let loginContentView:UIView = {
  let view = UIView()
  view.backgroundColor = .gray
  return view
}()

Assign an anonymous function that will be called immediately with () parenthesis at the end, which will return the view object that I have initialized inside the function.

Add the backgroundColor property to the view object and set its value to grey.

Note: All of the views will go to a separate file to follow the MVC pattern. For simplicity’s sake, I am creating views inside my ViewController.swift (which is the C part of MVC).

Create the other three views as properties of ViewController class similar to loginContentView.

Username:

private let unameTxtField:UITextField = {
    let txtField = UITextField()
    txtField.backgroundColor = .white
    txtField.placeholder = "Username"
    txtField.borderStyle = .roundedRect
    return txtField
}()

Password:

private let pwordTxtField:UITextField = {
    let txtField = UITextField()
    txtField.placeholder = "Username"
    txtField.borderStyle = .roundedRect
    return txtField
}()

Login Button:

let btnLogin:UIButton = {
    let btn = UIButton(type:.system)
    btn.backgroundColor = .blue
    btn.setTitle("Login", for: .normal)
    btn.tintColor = .white
    btn.layer.cornerRadius = 5
    btn.clipsToBounds = true
    btn.translatesAutoresizingMaskIntoConstraints = false
    return btn
}()

Once all the views are created it is time to add them to the main view.

• Go to ViewController.swift → ViewDidLoad() method.
• Add unameTxtField, pwordTxtField, and btnLogin as subviews of the loginContentView using the addSubView method, like the code below.

loginContentView.addSubview(unameTxtField)
loginContentView.addSubview(pwordTxtField)
loginContentView.addSubview(btnLogin)

Then…

• Add loginContentView as a subview of the main view, like so:

view.addSubview(loginContentView)

Run it and see nothing…😕

This is because I have not given dimensions to any of the views.

I could use frame property on each view object, however, the better way is…

to use Auto Layout Constraints.

Step #4: Add Auto Layout Constraints Programmatically

• Add translatesAutoresizingMaskIntoConstraints property and set it’s value to false for all of the views inside their anonymous functions.

private let loginContentView:UIView = {
  let view = UIView()
  view.translatesAutoresizingMaskIntoConstraints = false
  return view
}()

Make sure to add a translatesAutoresizingMaskIntoConstraints property to unameTxtField, pwordTxtField and btnLogin function declarations similar to the one above.

Note: Without this property, AutoLayout will NOT work… I have made mistakes by not adding them and wondered why nothing showed up on the viewscreen.

Then…

• Create a method called setUpAutoLayout() so that I do not have to write constraints code inside ViewDidLoad function.

Inside setUpAutoLayout(), I am going to add an auto layout constraint to each view one by one.

loginContentView Constraints

1. First, I am going to set the left margin of loginContentView equal to the left margin of its parent view and set the isActive property to true, like so.

loginContentView.leftAnchor.constraint(equalTo:view.leftAnchor).isActive = true

2. Do the same with the right anchor.

loginContentView.rightAnchor.constraint(equalTo:view.rightAnchor).isActive = true

These two constraints will stretch the width of loginContentView to fit the left and right margin of its parent main view.

3. heightAnchor: Set height of loginContentView using hightAnchor constraint. Only then you can see the loginContentView appearing on the screen.

loginContentView.heightAnchor.constraint(equalToConstant: view.frame.height/3).isActive = true

As you can see, I set the loginContentView’s height to match one-third the height of its parent’s main view.

If you run (►) the app at this point, you can see the loginContentView appearing at the top.

Finally, I need to move the loginContentView to the center of the screen vertically. You can achieve this by using another constraint called CenterYAnchor.

loginContentView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

Run it and you should be able to see the loginContentView vertically centered to its parent view.

Nice.

Let’s move on to the next one.

Recommended
▶︎ Get User Location Real-Time in your iOS App Quickly

unameTxtField Constraints

1. I am going to add the topAnchor constraint to unameTxtField, which will determine where the top margin of unameTxtField should be in its parent view (loginContentView).

I am setting the top margin of unameTxtField to match the top margin of loginContentView with the equalTo argument variable.

unameTxtField.topAnchor.constraint(equalTo:loginContentView.topAnchor).isActive = true

Also, I can use constant as a second argument variable to push the unameTxtField a little down from its parent view in this case loginContentView so that you have some space between them.

unameTxtField.topAnchor.constraint(equalTo:loginContentView.topAnchor, constant:40).isActive = true

Next,

2. Add leftAnchor and rightAnchor to unameTxtField.

Now, I am going to stretch the unameTxtField to match its parent left and right margins using leftAnchor and rightAnchor constraints similar to the previous one.

unameTxtField.leftAnchor.constraint(equalTo:loginContentView.leftAnchor).isActive = true
unameTxtField.rightAnchor.constraint(equalTo:loginContentView.rightAnchor).isActive = true

Let’s add some space between both sides so that unameTxtField is not touching the screen margins on either side. To do that, again use constant as a second argument, like the code below.

unameTxtField.leftAnchor.constraint(equalTo:loginContentView.leftAnchor, constant:20).isActive = true
unameTxtField.rightAnchor.constraint(equalTo:loginContentView.rightAnchor, constant:-20).isActive = true

I could use the main view’s constraints rather than using loginContentView’s constraints.

There is one problem with using the main view. What if you want to change the width of the loginContentView in the future, the subviews inside will NOT adjust properly.

That’s why I use loginContentView’s constraints to its children’s views.

3. Let’s change the height of unameTxtField using heightAnchor constraint.

unameTxtField.heightAnchor.constraint(equalToConstant:50).isActive = true

Perfect.

Two more views to go…

pwordTxtField

1. First, I am going to add the width of the text field using left and right anchors similar to the unameTxtField.

pwordTxtField.leftAnchor.constraint(equalTo:loginContentView.leftAnchor, constant:20).isActive = true
pwordTxtField.rightAnchor.constraint(equalTo:loginContentView.rightAnchor, constant:-20).isActive = true

2. Next, add the height.

pwordTxtField.heightAnchor.constraint(equalToConstant:50).isActive = true

3. I am going to set the top margin of pwordTxtField to the bottom margin of unameTxtField using bottomAnchor constraint.

pwordTxtField.topAnchor.constraint(equalTo:unameTxtField.bottomAnchor, constant:20).isActive = true

Login Button Constraints

This one is very similar to pwordTxtField 🙂

btnLogin.topAnchor.constraint(equalTo:pwordTxtField.bottomAnchor, constant:20).isActive = true 
btnLogin.leftAnchor.constraint(equalTo:loginContentView.leftAnchor, constant:20).isActive = true 
btnLogin.rightAnchor.constraint(equalTo:loginContentView.rightAnchor, constant:-20).isActive = true 
btnLogin.heightAnchor.constraint(equalToConstant:50).isActive = true

If you want to know more about adding constraints programmatically, you can check out Apple’s documentation here.

In conclusion, I hope you understand why I stopped using the storyboard and started building UI design programmatically. Also, thank you for joining me in building a login screen design PROgrammatically.

What challenges do you think you are going to face if you switch to building the UI 100% programmatically? I am looking forward to hearing your feedback…👍

Recommended
▶︎ Get User Location Real-Time in your iOS App Quickly