Create a Custom View Modifier in SwiftUI

Chase
5 min readOct 16, 2023

--

View Modifiers can be an easy way to give your views a custom look without much effort. A custom modifier can be a huge help to a project where you have styles that are used frequently.

Before we get started, please help others to find this content more easily by following me and clapping for this article.

Create a custom view modifier

Let’s say we have a set of styles that are used in many places in our app. For example, we could use a blue background with a subtle gradient that has rounded corners and a white foreground color. We can make this set of modifiers into a custom style that is used anywhere in our app.

Note that our CustomStyle inherits from ViewModifier, this allows us to make a modifier of our own. Inside our custom style, we are passing “content” into the body of our view, and that body returns the “some View” type. In this example we are only styling the content that we are passed, and then return the styled content back from our modifier.

The extension that we have added to our view allows us to call our custom modifier the same way that we would call any other modifier that is provided by SwiftUI out of the box. This gives a more natural look and feel when you use the modifier in your code.

// CustomStyle.swift
import SwiftUI

struct CustomStyle: ViewModifier {
func body(content: Content) -> some View {
content
.bold()
.font(.title)
.foregroundStyle(.white)
.padding()
.background(Color.blue.gradient)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}

extension View {
func customStyle() -> some View {
modifier(CustomStyle())
}
}

The following example shows how we can use our custom modifier in an existing view.

// CustomViewModifierExample.swift
import SwiftUI

struct CustomViewModifierExample: View {
var body: some View {
Text("Hello, World!")
.customStyle()
}
}

#Preview {
CustomViewModifierExample()
}

Which gives us the following output in our simulator.

A screenshot of our basic custom view modifier

Passing parameters to a custom modifier

We can also pass parameters to our custom modifier. This can allow our views to be more complex, or return a completely different view based on what we pass in. For example, maybe we want a button that has certain styles when a given state is active, and another set of styles when the button is NOT active.

We will modify our custom style from earlier to accept an isActive parameter. We will also update the styles to be different depending on whether or not the button is active. In this example, we change the background color, and remove the corner radius.

// CustomStyle.swift
struct CustomStyle: ViewModifier {
let isActive: Bool

func body(content: Content) -> some View {
content
.bold()
.font(.title)
.foregroundStyle(.white)
.padding()
.background(isActive ? Color.blue.gradient : Color.secondary.gradient)
.clipShape(isActive ? RoundedRectangle(cornerRadius: 16) : RoundedRectangle(cornerRadius: 0))
}
}

extension View {
func customStyle(isActive: Bool) -> some View {
modifier(CustomStyle(isActive: isActive))
}
}

We will also update our content view. We are changing the Text to a Button to make the state change easier while keeping a single component on the view (even though we don’t have to only use one component on our view). We have also added a buttonIsActive variable to keep track of the state of our button, and passed the new buttonIsActive state into our custom modifier.

We have also added a withAnimation block inside our button logic. This provides us with a nice default animation that we get for free without having to write much extra code. This animation runs when our button is clicked and also toggles the state variable we declared earlier.

We have also added a text block below the button to easily tell when our button is active and when it’s not.

// CustomViewModifierExample.swift
struct CustomViewModifierExample: View {
@State var buttonIsActive = false

var body: some View {
VStack {
Button("My Button") {
withAnimation {
buttonIsActive.toggle()
}
}
.customStyle(isActive: buttonIsActive)
.padding()

HStack {
Text("Button is:")
Text(buttonIsActive ? "Active" : "NOT active")
}
}
}
}

#Preview {
CustomViewModifierExample()
}

Now our button changes its appearance depending on the state that is passed in to our custom modifier.

A screenshot of our button with the custom view modifier applied showing both active and not active in the same image

Create Version Specific Modifiers

Sometimes you might want to add a modifier to your view, but it isn’t backwards compatible. We can create a custom modifier for that scenario also. For example, the following modifier adds an iOS 17 specific modifier when the users device is running iOS 17, and when the users device doesn’t have iOS 17, the modifier is not there. This allows us to prevent errors similar to the following: “ ‘modifier’ is only available in iOS XX.X or newer”.

// PagedScrollingTarget.swift
import SwiftUI

/// This modifier is attached to the target View of the EnablePagedScrolling modifier
/// and allows for backward compatibility without causing errors.
struct PagedScrollingTarget: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 17.0, *) {
content
.scrollTargetLayout()
} else {
content
}
}
}

extension View {
func pagedScrollingTarget() -> some View {
modifier(PagedScrollingTarget())
}
}

If you want to see this modifier in action, checkout my article on making a performant tab view here: https://medium.com/@jpmtech/making-a-performant-paged-tabview-45e360d55637

If you got value from this article, please consider following me, clapping for this article, or sharing it to help others more easily find it.

If you have any questions on the topic, or know of another way to accomplish the same task, feel free to respond to the post or share it with a friend and get their opinion on it.

If you want to learn more about native mobile development, you can check out the other articles I have written here: https://medium.com/@jpmtech

If you want to see apps that have been built with native mobile development, you can check out my apps here: https://jpmtech.io/apps

Thank you for taking the time to check out my work!

--

--