A simple no storyboard iOS app with a form

I have been working with iOS for a few years now and it wasn’t until last week when I had to build an iOS app for someone without using any storyboard. At first I thought “hmm, how am I going to manage auto-layout” but as I kept working with it, I realised, it’s actually not bad. In this post, I am going to talk about, how to build a simple iOS app just in code without any storyboards.


Background

I have been working with iOS since 2014 and I have been working with Storyboards most of the time. I often thought about building an app without storyboards but I never really had a reason to not do so. That was until the most recent project I delivered, I built something for another organisation. They had requirement of building the entire app UI programmatically so I had to build it all in code. I wasn’t looking forward to working with auto-layout but after the first couple of hours, I realised you get the hang of auto-layout pretty fast. It’s not that hard and fairly self-explanatory. I think building the UI programmatically is great, not only does it lend itself well to version control but also code reuse. I think there are plenty of opportunities for code reuse and I will show one of them in our sample project here. I only wish I had known this sooner, but ahh well, it’s never too late.

The app we are building

In this short post, I will highlight how to configure your environment to build an app without storyboard and build a small form with it. A lot of the tutorials I found online simply talked about deleting the Main.storyboard and changing the background colour in the home UIViewController. In this post, I intend to provide a step-by-step walkthrough of creating a simple form with UILabels, UITextField and a UIButton. Unlike the project that I built for the organisation, here I will be using UIStackView and UILayoutGuide to position the views.

Like the blog? Subscribe for updates


Start the app

Ok, follow along and

  • create a new single page application in iOS and call it, iOSUIInCode
  • delete the Main.storyboard file from your new project
  • remove all references to the main.storyboard from your project. Just to be sure, do a search in Xcode like what you see in the left screenshot

Now, we have a storyboard free Xcode project, that’s awesome. Now, all we need to do is let our AppDelegate (or SceneDelegate) know that it’s our ViewController that it should call on launch and not Main.storyboard. Add this piece of code to your SceneDelegate,

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

In the above code, all we are doing is setting our (UI) ViewController as a root of a UINavigationController and setting that as a rootViewController for our app.

Positioning elements

The bit I struggled with was to show my very first UILabel or UIButton on screen after launching my no storyboard app. Also, at the time, it was somewhat harder for me to find some code samples that would let me achieve that. Anyway, it doesn’t matter, I am writing this post so you don’t have to go through the same problem.

Like the blog? Subscribe for updates

Simple Form

Ok, what we are going to do here is create the UI for a simple form in iOS with the fields, username, email address, password and a submit button. A simple form, like the one in the screen below

Target UI for the form

Simple huh? yes it is and you will see how.

So let’s see, what does the above UI have? it has three UILabel (s), three UITextField (s) and a UIButton. Look closer and you can see that we have four rows, the first three rows have a pair: a UILabel and a UITextField and the last row has a single UIButton. Now, when designing the UI, it makes sense to create create one row at a time, right? Yes, it does, so let’s do that. So for each row, we will add a UILabel and a UITextView, let’s start with the first row for username.

The UILabel and UITextField are next to each other and we can place them and set auto-layout constraints to put horizontally next to each other. or maybe we can use a UIStackView? For those who don’t know UIStackView, it’s a handy little data structure for laying out your UIViews in rows or columns. You can read more about UIStackView on the official docs, for now let’s look at the code to implement this.

In your ViewController create a new method called createForm with the following code,

func createForm(margins:UILayoutGuide) {
    let usernameSV = UIStackView()
    usernameSV.translatesAutoresizingMaskIntoConstraints = false
    usernameSV.axis = .horizontal
    usernameSV.spacing = CGFloat(10)
    usernameSV.distribution = .fillEqually
    view.addSubview(usernameSV)

    usernameSV.heightAnchor.constraint(equalToConstant: CGFloat(50)).isActive = true
    let usernameLbl = UILabel()
    usernameLbl.text = "Username"
    usernameLbl.textAlignment = .right
    usernameLbl.backgroundColor = .cyan
    usernameSV.addArrangedSubview(usernameLbl)

    let usernameTF = UITextField()
    usernameTF.placeholder = "type your username"
    usernameTF.textAlignment = .left
    usernameSV.addArrangedSubview(usernameTF)
    view.addSubview(usernameSV)

In the above code, we are creating a UIStackView,

  • I will talk about UILayoutGuide a little later
  • create a UIStackView and name it usernameSV
  • configure the stackView to arrange it’s subviews horizontally
  • ensure the’s a 10 point space between arrangedSubViews
  • ensure the arrangedSubviews occupy equal space
  • create a label usernameLbl and a text field usernameTF
  • add them to usernameSV using arrangedSubView

At this stage our first row is all setup and we are left with one last thing. To position the UIStackView in our UIViewController. Here’s the code for this,

usernameSV.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
usernameSV.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
usernameSV.topAnchor.constraint(equalTo: margins.topAnchor, constant: 40).isActive = true

The above code specifies, our UIStackView for the username row should be placed to the left (leading), right (trailing) of our view’s margins and 40px from the view’s top margin. Ok so for those from the HTML5 world, remember how we specify margin-left, margin-right and margin-top for a div? yes, it’s like that.

Layout margins (UILayoutGuide)

I want to keep things simple in this post and as such, I am using layout margins to place our usernameSV. By using layout margins, we are are limiting our usernameSV to be placed within the safe area of our parent view. Safe area here would be the visible portions of the view, so it excludes the status bar, navigation bar, tab bar etc and also account for the default padding of the UIView which is 8px. Using layout margins may not always be appropriate but here, as I said, I am keeping it simple. Anyway moving on,

From an HTML5 perspective

If you are from the HTML5 world, you can think of it as our usernameSV is a div, with a label and an input of type text. We position them in our HTML page by setting margin-left, margin-right and margin-top properties in CSS.

Like the blog? Subscribe for updates

Email row

Again, we will follow the same process, create a UILabel and UITextField and place it in a row i.e. UIStackView. Before we do that, let’s think about this a little, remember how we configured our the UIStackView for our username? i.e. create a UIStackView and name it usernameSV

  • configure the stackView to arrange it’s subviews horizontally
  • ensure the’s a 10 point space between arrangedSubViews
  • and the arrangedSubviews occupy equal space

So those things apply to our other rows as well, so let’s just create a method to construct our UIStackView e.g.

private func getStackView() -> UIStackView {
    let sv = UIStackView()
    sv.translatesAutoresizingMaskIntoConstraints = false
    sv.heightAnchor.constraint(equalToConstant: CGFloat(50)).isActive = true
    sv.axis = .horizontal
    sv.spacing = CGFloat(10)
    sv.distribution = .fillEqually
    view.addSubview(sv)
    return sv
}

Great, then the code to create our email row would be as follows,

let emailSV = getStackView()

let emailLbl = UILabel()
emailLbl.text = "Email"
emailLbl.textAlignment = .right
emailLbl.backgroundColor = .cyan
emailSV.addArrangedSubview(emailLbl)

let emailTF = UITextField()
emailTF.placeholder = "type your email"
emailTF.textContentType = .emailAddress
emailTF.textAlignment = .left
emailSV.addArrangedSubview(emailTF)

emailSV.topAnchor.constraint(equalTo: usernameSV.bottomAnchor, constant: 20).isActive = true
emailSV.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
emailSV.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true

See? Get an idea of what I meant, when I said coding the UI programmatically leads to better code reuse.

Now, you can use the above concepts and apply them when creating rows for password and submit button. Here’s what the code for the full ViewController looks like,

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        navigationItem.title = "UI In Code"
        let margins = view.layoutMarginsGuide
        createForm(margins: margins)
    }
    func createForm(margins:UILayoutGuide) {
        let usernameSV = getStackView()

        let usernameLbl = UILabel()
        usernameLbl.text = "Username"
        usernameLbl.textAlignment = .right
        usernameLbl.backgroundColor = .cyan
        usernameSV.addArrangedSubview(usernameLbl)

        let usernameTF = UITextField()
        usernameTF.placeholder = "type your username"
        usernameTF.textAlignment = .left
        usernameSV.addArrangedSubview(usernameTF)
        view.addSubview(usernameSV)

        usernameSV.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
        usernameSV.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
        usernameSV.topAnchor.constraint(equalTo: margins.topAnchor, constant: 40).isActive = true

        let emailSV = getStackView()

        let emailLbl = UILabel()
        emailLbl.text = "Email"
        emailLbl.textAlignment = .right
        emailLbl.backgroundColor = .cyan
        emailSV.addArrangedSubview(emailLbl)

        let emailTF = UITextField()
        emailTF.placeholder = "type your email"
        emailTF.textContentType = .emailAddress
        emailTF.textAlignment = .left
        emailSV.addArrangedSubview(emailTF)

        emailSV.topAnchor.constraint(equalTo: usernameSV.bottomAnchor, constant: 20).isActive = true
        emailSV.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
        emailSV.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true

        let passwordSV = getStackView()
        let passwordLbl = UILabel()
        passwordLbl.text = "Password"
        passwordLbl.textAlignment = .right
        passwordLbl.backgroundColor = .cyan
        passwordSV.addArrangedSubview(passwordLbl)

        let passwordTF = UITextField()
        passwordTF.placeholder = "type your password"
        passwordTF.textAlignment = .left
        //since it's password, let's do the steps below
        passwordTF.textContentType = .password
        passwordTF.isSecureTextEntry = true
        passwordSV.addArrangedSubview(passwordTF)

        passwordSV.topAnchor.constraint(equalTo: emailSV.bottomAnchor, constant: 20).isActive = true
        passwordSV.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
        passwordSV.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true

        let submitSV = getStackView()
        let btn = UIButton()
        btn.backgroundColor = .systemGreen
        btn.setTitle("Submit", for: .normal)
        btn.addTarget(self, action: #selector(submitForm), for: .touchUpInside)
        submitSV.addArrangedSubview(btn)

        submitSV.topAnchor.constraint(equalTo: passwordSV.bottomAnchor, constant: 20).isActive = true
        submitSV.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
        submitSV.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
    }
    private func getStackView() -> UIStackView {
        let sv = UIStackView()
        sv.translatesAutoresizingMaskIntoConstraints = false
        sv.heightAnchor.constraint(equalToConstant: CGFloat(50)).isActive = true
        sv.axis = .horizontal
        sv.spacing = CGFloat(10)
        sv.distribution = .fillEqually
        view.addSubview(sv)
        return sv
    }
    @objc func submitForm() {
        print("submit form")
    }
}

There are other areas in the above code that you can isolate into its own method for further code reuse, maybe have a think about it and do so.

Open-source

You can find the entire project on Github check out the project and have a play with it. If you think that I could have done something better than feel free to either comment or make a change to the repo and create a pull request.


Conclusion

I simply didn’t know that you can build iOS UI in code and now that I know this, I will be building all my iOS apps this way. Anyway, I hope this post shows you that building the UI for an iOS app in code is really not that hard and you can get cracking at it pretty quickly.

 

As usual, if you find any of my posts useful support us by  buying or even trying one of our products and leave us a review on the app store.

‎My Day To-Do - Smart Task List
‎My Day To-Do - Smart Task List
‎My Day To-Do Lite - Task list
‎My Day To-Do Lite - Task list
‎Snap! I was there
‎Snap! I was there
Developer: Bhuman Soni
Price: $1.99
‎Numbers Game: Calculate Faster
‎Numbers Game: Calculate Faster
Numbers Game: Calculation Master
Numbers Game: Calculation Master
‎Simple 'N' Easy: Todos & food
‎Simple 'N' Easy: Todos & food
‎Captain's Personal Log
‎Captain's Personal Log
Developer: Bhuman Soni
Price: $4.99
My Simple Notes
My Simple Notes
Developer: Bhuman Soni
Price: Free
‎My Simple Notes - Dictate
‎My Simple Notes - Dictate
Developer: Bhuman Soni
Price: $2.99

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *