Skip to content
Back to posts

View Modifiers

3 min read

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 ViewModifiers

A ViewModifier is composed of two parts:

  1. A function extended on some View type that makes it accessible to callers.
  2. 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 Views

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)
  }
}