How to create custom views programmatically
Tags: ios, beginners, native, uikit
Published: 2023-05-14 06:31:13

Hello wonderful community 👋. As a new iOS developer I'm starting to learn how to create views programmatically, it has been tricky to learn how to do this coming from using Storyboards 😅.

In this tutorial, I'll guide you step-by-step on programmatically creating views in iOS. It's an opportunity for me to share what I've learned, with the hope of helping others who are also exploring this area.

Let's start coding! 😀

## Starting a new Xcode project

> I'm using Xcode version 14.3 and targeting iOS 16

1. Open Xcode and click on New Project, we will get this window:


![new project window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vp8go9y797myrjl7coeq.png)

Here we are going to select the simplest template available, iOS > App

2. Now let's give some basic information:
- Product Name: It's the name of our app.
- Team: If you have a Apple Developer Account you should select it here, otherwise leave it as None is alright (leave it as None will allow you to test your app only in the simulator).
- Interface: Since we are doing an example with `UIKit` we need to select Storyboard (don't worry we are not actually using them).
- Language: Select Swift.

The rest of the options can be unchecked, they won't be used in this simple example.

![app basic init](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19z58mvrq97svs4ox7pu.png)

3. Click on Next and select a location on your computer to save the project, finally click on Create.

We now have a simple app ready for us. 🎉

![basic project template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9zpwdyvf473g0kpnxnta.png)

## Deleting storyboard references from our project

Now it's time to delete the storyboard file and configurations associated with it.

In this simple project we have two storyboard files, `Main` and `LaunchScreen`.

We only need to delete the `Main` one because this is the one that is loaded once our app starts. We want to provide our own by using code.


![storyboard files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82fl7grirpnadjgv5ol7.png)

1. Delete `Main` from the project, when prompt click Move to trash.


![deleting main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yb8e0whtaxksrcexfaor.png)

2. Delete the references of this storyboard, open `Info.plist` and delete the value called `Storyboard Name` by clicking the minus icon.

![info deleting storybaord](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/47osbt6h6ytgsrc7vh7s.png)

3. Delete the storyboard reference in the target app, this is important because we no longer have the storyboard file so if we try to compile we would get an error because of the missing reference.

![target storyboard delete reference](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0psfkflntunkkzp29l6.png)

Now run your app and you'll get a nice black screen, this mean you deleted the storyboard correctly!.


![simulator black screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/id8wb2ugtlyvokmu6jjr.png)

## Configuring programmatically the root window

Now you have an empty screen, nothing was loaded and that's a problem. The storyboard made all of this configuration automatically for you. Now it's your responsibility to configure the screen that will be show when your app starts.

1. Open `SceneDelegate.swift` and configure the root window of your app. Locate the method called `func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)`.

Once you found it, let's change the method's body with the following:

```swift
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let viewController = ViewController()
window.rootViewController = viewController
self.window = window
window.makeKeyAndVisible()
```

This code do several things:

- It tries to create a `windoScene` by using the passed `scene` object, this object will contain the scene of our app.
- It creates a new `window`, this will tell your app to create a windowScene, this object will contain your app's user interface.
- It creates a `viewController`, this object is responsible for controlling your view objects. This class is already on your template (`ViewController.swift), so there is nothing special about its name.
- Assigning the new ViewController as the root view controller, i.e., the first view controller we are going to create to start our app UI.
- We are assigning the new created window to our class window, `self.window = window`.
- Making the window visible on the screen, `window.makeKeyAndVisible()`.

After that configuration your app interface is back to life. If you run it at this point you'll still get a back screen, that's ok because we haven't configured anything on our View objects.

## Creating our simple interface programmatically

Now it's time to add some code to make our interface show something on the screen!

### ViewController life cycle

The `UIViewController` has a specific life cycle, i.e., certain predefined methods are called at specific moments of the object's life. Since we are creating our interface programmatically we need to understand two methods of this life cycle.

- `func loadView()`: This method is used to load our custom views and make some configuration on them before they appear on screen.
- `func viewDidLoad()`: This method is called once the ViewController is loaded into memory, here you can create more customization and configuration to your views.

As you can guess, the object first run `loadView` and then `viewDidLoad`.

### Creating an empty `UIView`

Each `ViewController` creates a default empty View, but we want to have our own custom one, so let's start by doing that.

1. Create a new file for our new custom view, let's call it `CustomView.swift` and add the following code inside.

```swift
// CustomView.swift
import UIKit

final class CustomView: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .yellow
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
```

Our view inherits from `UIView` so we need to define at least these two constructors (init methods). The one we need to focus on is the first one. Here we are adding a background color.

2. Create the custom view object and show it on the screen, to achieve this lets open the `ViewController.swift` and inside the class create a new object of our view and assign it as the new view in the `loadView`. You should end up with a code similar to this:

```swift
// ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    lazy var customView = CustomView()
    
    override func loadView() {
        // we are creating a class property because we may have delegates
        // assign your delegates here, before view
        view = customView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
```

One important point to mention, the view property is assigned by default to use all of the phone screen. So we don't need to assign any constrains because of this. The `CustomView` will take all of the screen.

Now run your app and you'll see a yellow screen.


![yellow view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7zxcloqmrsh5zfnrmgbi.png)

At this point, you should have the following files on your project:

![current files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9335lsbq7rwnf22lrh37.png)

Great, now you loaded an empty custom view to your screen!.


## Adding a simple label on the empty View

To finish this tutorial let's add a simple label using auto layout.

1. Create a `UILabel` property on the `CustomView` class and give it some default values like a title and color. You don't need to provide any dimension, we will do this with auto layout. Your `CustomView` class should look something like this:

```swift
// CustomView.swift
import UIKit

final class CustomView: UIView {
    
    // 1. Creating the new element
    lazy var label: UILabel = {
        // internal label, not the same as the external label
        let label = UILabel()
        label.text = "Hello World"
        label.textAlignment = .center
        label.textColor = .white
        label.backgroundColor = .black
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .yellow
        // 2. Adding the new element into the view
        addSubview(label)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
```

If you run your code the new label won't appear on the screen, that's because the label has a size of 0 and its position in the screen's position (0,0). This happened because we didn't provide a `CGRect` initializer. It's ok, we are going to fix this with auto layout.

2. Add auto layout constrains to allow the object to appear on the view (this will give it an area), also remember to set to false the property `translatesAutoresizingMaskIntoConstraints` if we don't do this our view may present problems rendering with auto layout.

Remember that each ui element is represented as a box, so each edge has a name we can refer in our code to assign the constrains.

![ui box](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myp5gc6ctdrfmn7b96ml.png)

Your code inside `CustomView.swift` should look like something like this:

```swift
// CustomView.swift
import UIKit

final class CustomView: UIView {
    
    // 1. Creating the new element
    lazy var label: UILabel = {
        // internal label, not the same as the external label
        let label = UILabel()
        label.text = "Hello World"
        label.textAlignment = .center
        label.textColor = .white
        label.backgroundColor = .black
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .yellow
        // 2. Adding the new element into the view
        addSubview(label)
        // 3. Add the auto layout constrains
        setUpLabelConstrains()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setUpLabelConstrains() {
        // Needed to avoid auto layout conflicts
        label.translatesAutoresizingMaskIntoConstraints = false
        // Set the top part of the label to the safe area of the custom view and add a vertical separation of 64 points
        label.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 64).isActive = true
        // Set the left part of the label to the safe area of the custom view and add a horizontal separation of 18 points
        label.leftAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leftAnchor, constant: 18).isActive = true
        // Set the right part of the label to the safe area of the custom view and add a horizontal separation of -18 points
        // note: if this value it's positive the element will be separate from outside the screen 18 points.
        label.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -18).isActive = true
        // Set the height of the label to be equal to 48 points
        label.heightAnchor.constraint(equalToConstant: 48).isActive = true
    }
}
```

Now let's run the app, a custom label should appear on the screen now. This custom label is a child of the custom view, which was created by the ViewController and set as the default view for our ViewController.


![final result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/diojj4u4rsbokeyf6w68.png)

## Conclusion

Congratulations, you created an app's ui using only code!. I hope this simple tutorial helps you to understand this topic. There is a lot more to cover but this is a good first step, thank you for reading!.

Until next time.