Combining Multiple SwiftUI View Modifiers Into One

There comes a day in the life of a Swift developer when you find yourself writing the same set of ViewModifiers repeatedly to apply the same style to Stacks in different parts of your project. This can be not only tedious but also prone to visual inconsistencies. In the case of even the smallest design change, there might be dozens of places to update, and missing any is not acceptable, despite being human.

For instance, consider the following example:

HStack{...}
	.padding()
	.background(
		LinearGradient(
			gradient: Gradient(colors: [Color.blue, Color.purple]),
			startPoint: .leading,
			endPoint: .trailing
		)
	)
	.cornerRadius(15)
	.overlay(
		RoundedRectangle(cornerRadius: 15)
			.stroke(Color.white, lineWidth: 2)
	)
	.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 5)

Tweaking a shadow or changing the lineWidth can be a hassle if the style is used in multiple places. Moreover, having several of these elements in the same file creates too much visual clutter.

Creating your Custom View Modifier

The solution is to create a custom ViewModifier. To do this, first, create a Struct that conforms to ViewModifier. Inside, apply all the modifiers from before to the content within the ViewModifier's body function. It's that simple!

struct CombinedViewModifier: ViewModifier {

    private let cornerRadius: CGFloat = 12
    private let padding: CGFloat = 12

    func body(content: Content) -> some View {
        content
            .padding()
			.background(
				LinearGradient(
					gradient: Gradient(
						colors: [Color.blue, Color.purple]
					),
					startPoint: .leading,
					endPoint: .trailing
				)
			)
			.cornerRadius(15)
			.overlay(
				RoundedRectangle(cornerRadius: 15)
					.stroke(Color.white, lineWidth: 2)
			)
			.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 5)
	}
}

To make the process even more straightforward, you can write an extension of View containing a combinedViewModifier function that returns the View itself with the modifier already applied. While this step is optional, Apple promotes this method in their official documentation, and I appreciate that the end result looks and behaves like any other view. Of course, this is a matter of personal taste.

extension View {
    func combinedViewModifier() -> some View {
        self.modifier(CombinedViewModifier())
    }
}

Now, all you have to do is add a single ViewModifier to the HStack rather than the parade of modifiers we had before. This approach makes the code much cleaner and less prone to styling inconsistencies due to improved reusability.

HStack{...}.combinedViewModifier()

Personally, I find this method much more appealing than the previous approach. It gives me peace of mind that I have full control and oversight of how my app looks, no matter how often I change my mind when it comes to the exact parameters of the radius. I hope you feel the same way!

Hope you feel the same way!

OXOX, Christian 👨🏻‍💻


modifier(_:) | Apple Developer Documentation
Applies a modifier to a view and returns a new view.