SwiftUI: Modifiers to your rescue

Imagine you have this kind of View below, a text in a box that looks like a card.

Simulator Screen Shot iPhone 11 Pro 2021 11 13 at 22.42.13
ZStack {
    VStack {
        Text("Hello World")
            .font(.headline)
    }
    .frame(width: 200, height: 200, alignment: .center)
    .overlay(
        RoundedRectangle(cornerRadius: 10)
            .stroke(Color.white, lineWidth: 1)
    )
    .background(
        RoundedRectangle(cornerRadius: 10)
            .fill(Color.white)
            .shadow(color: Color.gray.opacity(0.5), radius: 3.0, x: 0.0, y: 0.0)
    )
}

If you find yourself in a situation where you have to copy this background and overlay modification multiple times in multiple places in your project, then it’s time to introduce ViewModifiers.

ViewModifier – A reusable modifier that you apply to a view or another view modifier, producing a different version of the original value.

Apple Documentation

Utilizing ViewModifier

Combining several modifiers to create a new modifier will help avoid having to write so much code in multiple places.

struct CardViewModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .overlay(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.white, lineWidth: 1)
            )
            .background(
                RoundedRectangle(cornerRadius: 10)
                    .fill(Color.white)
                    .shadow(color: Color.gray.opacity(0.5), radius: 3.0, x: 0.0, y: 0.0)
            )
    }
}

You can then apply modifier(_:) directly to a view:

ZStack {
VStack {
        Text("Hello World")
            .font(.headline)
    }
    .frame(width: 200, height: 200, alignment: .center)
    .modifier(CardViewModifier()) // <---- Here
}

A more common and better approach is to define an extension to View itself that incorporates the view modifier:

extension View {
    func cardViewStyle() -> some View {
        self.modifier(CardViewModifier())
    }
}

You can then apply the extension function to any view to achieve the same result:

ZStack {
    VStack {
        Text("Hello World")
            .font(.headline)
    }
    .frame(width: 200, height: 200, alignment: .center)
    .cardViewStyle() // <--- Here
}

VieModifier with Parameters

You can also create a ViewModifier having parameters. What if you want to make the StrokeColor for the card customizable?

struct CardViewModifier: ViewModifier {
    var strokeColor: Color // <--- Here

    func body(content: Content) -> some View {
        content
            .overlay(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(strokeColor, lineWidth: 1) // <--- Here
            )
            .background(
                RoundedRectangle(cornerRadius: 10)
                    .fill(Color.white)
                    .shadow(color: Color.gray.opacity(0.5), radius: 3.0, x: 0.0, y: 0.0)
            )
    }
}

extension View {
    func cardViewStyle(strokeColor: Color = Color.white) -> some View {
        self.modifier(CardViewModifier(strokeColor: strokeColor)) // <--- Here
    }
}

struct SimpleView: View {
    var body: some View {
        ZStack {
            VStack {
                Text("Hello World")
                    .font(.headline)
            }
            .frame(width: 200, height: 200, alignment: .center)
            .cardViewStyle(strokeColor: Color.red) // <--- Here
        }
    }
}
Simulator Screen Shot iPhone 11 Pro 2021 11 13 at 23.17.18

As you can see, using Modifiers can really save you some time as well as a number of lines of code. So you should consider adding that to your SwiftUI assets. 🙂

Cheers!