SwiftUI Custom ViewModifier — Define Once, Use Everywhere
Originally published on Medium in July 2024.
Five buttons, five times the same modifier chain. That is copy-paste development, and there is a better way.
SwiftUI makes it frighteningly easy to build beautiful views — and just as easy to repeat yourself in the process. A button with .font(.headline), .foregroundStyle(.white), .background(.pink), .cornerRadius(16) looks great. By the third time you type it, it gets tedious. By the fifth, it becomes a maintenance problem because every design change has to happen in five places at once.
The solution is simple: an extension on View that bundles all modifiers.
The Extension
extension View {
public func myAmazingModifier(bgColor: Color = .pink) -> some View {
self
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.frame(height: 50)
.background(bgColor)
.cornerRadius(16)
}
}
A default parameter for the background color, everything else is fixed. Changes to font, height, or corner radius take effect everywhere immediately.
Before vs. After
Before — every view carries the full modifier chain:
Text("First Button")
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.frame(height: 50)
.background(.pink)
.cornerRadius(16)

After — a one-liner per button:
Text("First Button")
.myAmazingModifier()
Text("Second Button")
.myAmazingModifier(bgColor: .yellow)
Text("Third Button")
.myAmazingModifier(bgColor: .blue)
Five buttons, one definition. The color is overridden per call, everything else stays consistent.

Extension vs. ViewModifier Protocol
SwiftUI also offers the ViewModifier protocol with func body(content: Content) -> some View. Which approach to choose depends on whether the modifier needs its own state.
| Approach | When to use |
|---|---|
| View Extension | Simple modifier chains without their own state |
| ViewModifier Protocol | When the modifier needs its own @State, @Environment, or lifecycle hooks |
For everyday use, the extension is enough. The protocol only makes sense when the modifier itself needs to think — for example, a shake animation modifier with its own animation state.
When three lines are enough, you do not need a protocol.
Translated with the help of Claude