When we create views using SwiftUI
, we are using a variety of built-in view modifiers — such as foregroundColor
, font
, or frame
. View modifiers are a powerful tool in SwiftUI
that allow us to configure a view or modify its behavior and appearance. By design, they’re reusable and composable. However, since a modifier is called as a function that modifies the view it’s called on, the order of the function calls matters. For example, calling background(Color.red)
before padding()
creates a different result than calling them in the reverse order.
Creating our own ViewModifier
s
A ViewModifier
is composed of two parts:
- A function extended on some
View
type that makes it accessible to callers. - A
ViewModifier
object, which changes the behavior of the view it’s applied to.
Let’s create a custom modifier that can be applied to a View
that visually makes the view take on a “warning” or “danger” appearance.
struct DangerModifier: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(.red)
.font(.headline)
}
}
extension View {
func isDangerousAction() -> some View {
self.modifier(MyCustomModifier())
}
}
For those familiar with PointFree, Brandon Williams and Stephen Celis explored a similar construction for composable
UIKit
styles.
In general, when we create our own view modifiers, we should aim to follow a set of simple guidelines:
- Keep it simple. It’s important to keep custom view modifiers simple and focused. A good rule of thumb is to create a separate modifier for each distinct behavior or appearance that we want to encapsulate. This makes our code more modular and easier to understand.
- Use descriptive names. It’s also important to use descriptive names for our custom view modifiers. This makes it easier for other developers (and ourselves) to understand what the modifier does and how it should be used.
- Use parameters. We can use parameters to make our custom view modifiers more flexible. If we find ourselves reusing many modifiers on views, it may be a sign that we should create a custom view modifier instead.
Use styles for specific View
s
Several view types — like Button
have a style
associated with them that can encapsulate several of their properties. For example, when we are configuring a button we can create a ButtonStyle
. ButtonStyle
and its counterparts are quite similar to creating our own modifiers — the key difference is that we are supplied with an additional parameter that contains information about the configuration of that object.
Let’s examine a ButtonStyle
for encapsulating an animation behavior:
struct PressableStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.97 : 1)
.opacity(configuration.isPressed ? 0.8 : 1)
}
}
In the example above, we are returning the configuration’s label
, which is of type some View
. some View
is not guaranteed to be a button. In order to make the style above composable with other styles, we need to ensure that we return a Button
.
struct PressableStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
Button(configuration)
.scaleEffect(configuration.isPressed ? 0.97 : 1)
.opacity(configuration.isPressed ? 0.8 : 1)
}
}