A brief introduction

Everything is a View in SwiftUI. This was made clear from the very first SwiftUI talk at WWDC 2019. This concept is core to how SwiftUI works - and if you really think about it - it makes perfect sense. SwiftUI Views don't maintain state, they're redrawn with every layout pass, the old ones are discarded and new ones take their place. Modifying a View in a literal sense wouldn't work as we have no reference to it. In the case of SwiftUI a view modifier actually refers to creating a new view from the state of the old one.

Let's take a quick look at an example.

import SwiftUI
import PlaygroundSupport

struct CustomRectangleView: View {
    var body: some View {
        RoundedRectangle(cornerRadius: 15)
    }
}

PlaygroundPage.current.setLiveView(CustomRectangleView())

This code creates a SwiftUI View inside a playground, right now it's just a rectangle with a corner radius, that's it. SwiftUI will make the rounded rectangle just big enough to accommodate the corner radius and give it a default fill of black. It results in the following.

Now let's add a couple of view modifiers...

import SwiftUI
import PlaygroundSupport

struct CustomRectangleView: View {
    var body: some View {
        RoundedRectangle(cornerRadius: 7)
            .fill(
                LinearGradient(gradient:
                    Gradient(colors: [.blue, .green]),
                               startPoint: .top,
                               endPoint: .bottom)
            )
            .frame(width: 140, height: 200)
    }
}

PlaygroundPage.current.setLiveView(CustomRectangleView())

The above code adds two modifiers, fill and frame.

  1. fill takes in any View and uses it to fill the contents of the RoundedRectangle
  2. frame sets the size - or frame - of the View.

It results in the following.

Looks much cooler right? So far you've looked at some built in View modifiers SwiftUI provides, but that's not why you're here. How do you go about creating your own?


Adding your own modifiers

Adding your own modifiers is much easier than it sounds. It involves two steps:

  1. Create a struct and subscribe to the  ViewModifier protocol
  2. Override body(content:) -> some View

Let's see how this looks in practice.

struct CustomTextField: ViewModifier {

    func body(content: Content) -> some View {
        content
            .frame(minWidth: 0, maxWidth: .infinity)
            .frame(height: 43)
            .padding(.horizontal)
            .font(.system(size: 18, weight: .medium, design: .rounded))
            .foregroundColor(Color(UIColor.label))
            .background(
                RoundedRectangle(cornerRadius: 10)
                    .foregroundColor(
	                Color(UIColor.quaternarySystemFill)
                    )
            )
    }
}

In this example we're combing multiple view mofidiers into one inside the body(content:) -> some View function. content in this case is the View you're adding the modifier to.

To apply the CustomTextField modifier, you pass it as an argument to the .modifier view modifier. Take a look at the following example.

TextField("Email", text: $email)
    .modifier(CustomTextField())

That's it! You can combine any number of view modifiers into custom ones, you can even combine multiple custom ones, totally your call 😄.

Using this view modifier, a login form would look something like this.

Thanks for reading ✌️