SwiftUI Craftsmanship: ViewModifiers
Transforming Views with the Finishing Touches of a Craftsperson
One of my favorite aspects of SwiftUI is how we can create experiences by simply declaring the makeup of our experience. What’s really neat is how we can refine the details of our experiences by simply adding them, syntactically and precisely, where we want them. Namely, we do this through ViewModifiers.
In the world of SwiftUI, ViewModifiers are the craftsperson’s finishing touches—the final flourishes that enhance each piece, giving it character and polish. Just as a woodworker carefully applies finishing techniques to a piece of furniture, SwiftUI developers use ViewModifiers to add refinement to views. They don’t alter the core structure but instead add layers of styling, behavior, and layout adjustments.
In this next article of our SwiftUI Craftsmanship series, we explore ViewModifiers as vital tools in SwiftUI craftsmanship, examining the different types of modifiers, how they work, best practices, and the common pitfalls to avoid.
ViewModifiers As Tools
SwiftUI’s ViewModifiers offer a modular way to refine a view’s appearance, behavior, and layout. Instead of altering the view directly, each modifier wraps changes and returns a new, modified version of the view. Much like sanding or varnishing wood, modifiers in SwiftUI layer transformations in a clear, declarative way.
Each modifier creates a distinct view (keep that in mind for later on), which helps SwiftUI manage state and respond smoothly to data changes. This immutability mirrors the value of using durable, consistent materials in a woodworking project—it ensures that changes are predictable and maintainable.
Custom modifiers allow us to encapsulate frequently used design elements or behaviors into reusable components. Think of this as creating a set of custom tools tailored to a woodworker’s specific needs. In SwiftUI, these custom modifiers reduce code duplication and provide consistent styling across an app, helping keep the codebase clean and organized.
What is the Potential?
Just like with View Contracts, we can approach ViewModifiers with a question:
“What is the total potential value of this View?”
This is a loaded question. Let’s take a simple Text. On it’s own, it’s value is that it displays data in the form of text on the screen. But if I alter the color, it could signal extra information about that data (e.g. if tracking values that go up or down, green could mean gain and red could mean loss, without any text explicitly saying so). A larger font could mean this is really important data. Tapping the Text could have it switch to a different data point or revealing a sheet with further details.
The total potential value of that Text went from displaying just a small subset of data to unlocking layers of additional, detailed information. All of it unlockable through ViewModifiers.
Different Finishes for Different Needs
In SwiftUI, each modifier has a purpose. Just like choosing the right stain or polish for a specific type of wood, we should consider how we can unlock value for the view and data we’re working with.
Instead of memorizing every modifier in existence, though, it would be helpful to look at them in more general terms. To help, below are some categories to that clarify the role of different modifiers:
Styling Modifiers (e.g., font, foregroundColor): These modify a view’s visual appearance, much like the stain that enhances wood’s natural grain.
Event Modifiers (e.g., onTapGesture, onHover): Used to make views interactive, adding response to user actions, like handles on drawers.
Functional Modifiers (e.g., translationTask, draggable): These add specialized behaviors, like compartments that improve the usability of furniture.
Behavior Modifiers (e.g., task, onAppear): These control a view’s lifecycle, akin to ensuring each drawer or door stays securely in place.
Layout Modifiers (e.g., padding, frame, alignmentGuide): Control spacing and positioning, as a woodworker ensures all parts are aligned and balanced.
Visibility Modifiers (e.g., opacity, isHidden): Control what’s visible, like adjustable panels or sliding doors that reveal or conceal.
These categories clarify the types of “finishes” available in SwiftUI, helping us select the right modifier for each task. Used together, they add polish and utility to the user experience, much like how different techniques in woodworking bring out a piece’s beauty and functionality.
Order!
One crucial aspect of working with ViewModifiers is understanding that their order matters. The sequence in which modifiers are applied can significantly affect the outcome, much like how the order of woodworking techniques impacts the final appearance of a piece. For instance, applying varnish before sanding would result in a rough, uneven finish.
In SwiftUI, applying modifiers in the wrong order can lead to unexpected or undesirable results.
Consider this example:
Text("Hello, SwiftUI!")
.background(Color.yellow)
.padding(10)
Here, the background modifier adds a yellow background tightly around the text, and then padding creates a space of 10 points around the text and background together. If you reverse the order:
Text("Hello, SwiftUI!")
.padding(10)
.background(Color.yellow)
The yellow background now extends to cover the entire padded area. The padding is applied first, enlarging the view’s bounding box, and then the background fills that entire space. This illustrates how the order of modifiers affects both the visual layout and behavior of the view.
Understanding this concept ensures that your design intentions are accurately reflected, and it reinforces the importance of careful, thoughtful application of each modifier—just as a craftsman would plan each step in their woodworking process.
Creating a Custom ViewModifier
Custom ViewModifiers are SwiftUI’s equivalent of specialized tools, made for reusability and consistency. Here’s an example of a custom modifier that adds a rounded border and shadow, perfect for those times you want a view to stand out:
struct RoundedBorderModifier: ViewModifier {
var color: Color
var lineWidth: CGFloat
func body(content: Content) → some View {
content
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(color, lineWidth: lineWidth)
)
.shadow(radius: 5)
}
}
For ease of use, extend View so you can call it like any other modifier:
extension View {
func roundedBorder(color: Color, lineWidth: CGFloat) → some View {
self.modifier(RoundedBorderModifier(color: color, lineWidth: lineWidth))
}
}
Now, applying this modifier is as simple as:
Text("Hello, SwiftUI!")
.roundedBorder(color: .blue, lineWidth: 2)
This approach enables you to apply a custom “finish” consistently throughout your app, making each view reflect a unified, polished style.
Note: There is so much more that can be said about crafting custom modifiers. Therefore, a future article in the Craftsmanship series will focus on that, specifically.
Best Practices for ViewModifiers
Using ViewModifiers thoughtfully can make your codebase feel as meticulously crafted as fine furniture. Here are some best practices:
Prioritize Readability: Stack modifiers in a logical order—layout, then styling, then interactivity. This makes it clear what each modifier is doing. Remember, the order can impact the final outcome, so plan carefully.
Use Custom Modifiers for Reusability: Group commonly applied sets of modifiers into a single custom modifier. This reduces redundancy and ensures consistency.
Limit the Scope of Custom Modifiers: Keep custom modifiers focused. Don’t pack too many responsibilities into a single modifier. Small, focused modifiers are easier to understand and reuse.
Create Extensions for Intuitive Syntax: Add custom modifiers as extensions on View, making them feel like native SwiftUI modifiers. This keeps the syntax clean and intuitive.
Test Modifiers in Isolation: Test custom modifiers in SwiftUI previews to ensure they look and behave as expected.
Be Mindful of Performance: Each modifier adds some rendering cost. When using costly effects like shadow or blur, apply them sparingly.
Pitfalls and Anti-Patterns
Even the best woodworkers face challenges; in SwiftUI, there are common pitfalls when working with modifiers. Here’s what to avoid:
Overuse of Modifiers: While mentioned positively in the best practices section, it’s worth mentioning again, here, as a warning. Stacking too many modifiers can impact performance. Keep your designs simple to prevent slowdowns.
Complex Logic in Modifiers: Avoid embedding conditional or complex logic. Modifiers work best with straightforward tasks; handle logic higher up in the view hierarchy.
Hidden Dependencies: Custom modifiers that rely on environment objects or external state can introduce hidden dependencies, making the view’s behavior harder to predict. Instead, pass data as parameters to keep modifiers self-contained.
Misusing ViewModifier vs. ViewBuilder: Don’t try to create entire view hierarchies within a modifier. For complex components, use custom views or ViewBuilder closures. (We will tackle ViewBuilder in a future article, don’t you worry)
Layout Missteps: Overusing layout modifiers like offset can lead to unexpected layout behavior. Use alignment guides and SwiftUI’s layout system whenever possible.
Accessibility Concerns: Visual modifiers can unintentionally impact accessibility. Ensure that hidden elements are also accessibility-hidden where necessary. And remember, there are a slew of accessibility modifiers to help make your apps a positive experience for all users. Just as a thoughtfully designed piece of furniture should be easy for anyone to use, our SwiftUI views should be accessible and intuitive for all users.
Note: Crafting accessible experiences is another topic that can be discussed at length, and is on the roadmap for the Craftsmanship series.
Modifying our Approach to Modifiers
Continuously refining our approach to ViewModifiers is part of our journey as SwiftUI craftspeople, elevating our work from functional to masterful. Mastering ViewModifiers in SwiftUI is like mastering the finishing touches in woodworking. By selecting and layering modifiers thoughtfully, you can transform simple views into crafted, polished interfaces.
SwiftUI’s ViewModifiers are more than just tools—they’re what let us bring out the character, usability, and durability of each view, making the final product something special.
And that brings us back to our core question. We must take a step back, envision that final product, and imagine all it can be by simply asking “What is the total potential value of this View?”.