DEV Community

Mateus Rodrigues
Mateus Rodrigues

Posted on

Understanding SwiftUI: Modifiers

Views are lightweight structures that generally have only the body property and intrinsically necessary properties, such as alignment for VStack/HStack/ZStack and action for Button. They are not very customised by themselves and that is when View Modifiers comes into play.

An important thing to undestand is that when we apply a modifier to a View we are not directly modifying it. There is no properties to be really modified . Instead, when a modifier is applied a ModifiedContent is returned, which wrappers the View that we applied the modifier.

let view = Rectangle().frame(width: 100, height: 100)
type(of: view) // ModifiedContent<Rectangle, _FrameLayout>

ModifiedContent is another very simple struct that holds a Content and Modifier to be applied in runtime.

struct ModifiedContent<Content, Modifier> {

  var content: Content
  var modifier: Modifier

}

Note that Modified Content doesn't implement View protocol in its declaration. Instead, ModifiedContent implements different protocols based on the protocols implemented by the Content and Modifier generic properties. This approach keeps ModifiedContent itself as simple as possible while allow its to be extensible.

ModifiedContent implements View, for example, when Content and Modifier implement View and ViewModifier protocols, respectively.

extension ModifiedContent: View 
where Content: View, Modifier: ViewModifier 
{ ... }

ModifiedContent for Scene and Widget are also implemented in the same way, but using SceneModifier and WidgetModifier.

ViewModifer is a protocol which only requirement is a body function.

public protocol ViewModifier {
  associatedtype Body : SwiftUI.View
  func body(content: Self.Content) -> Self.Body
}

Just like Views, most built-in ViewModifiers actually return Never from body function because SwiftUI uses only its properties.

You can implement yourself this protocol to create custom modifiers.

struct CustomModifier: ViewModifier {
func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.gray)
            .clipShape(RoundedRectangle(cornerRadius: 10))
    }
}

All SwiftUI modifiers are internally backed by a modifier function.

extension View {
  public func modifier<T>(_ modifier: T) -> SwiftUI.ModifiedContent<Self, T> {
        return .init(content: self, modifier: modifier)
    }
}

This function is the one that you should use to apply a custom modifier.

Rectangle().modifier(CustomModifier())

Regardless the built-in modifiers, it's worth to mention that although the following pieces of code are equivalent you won't be able to use the last two because _FrameLayout has an internal initialiser.


Rectangle().frame(width: 100, height: 100)

Rectangle().modifier(_FrameLayout(width: 100, height: 100))

ModifedContent(content: Rectangle(), modifier: _FrameLayout(width: 100, height: 100))

There are some ViewModifiers that for some reason have a public initializer, _PaddingLayout for example, but they are not mean to be used directly and there is no guarantee that they will remain accessible in future versions of SwifUI.

Functional Approach

Modifiers and Stacks are similar in the sense that they both wrap some content. Stacks wrap their content using a closure so you may wonder why not use this same approach for modifiers.

FrameLayout(width: 100, height: 100) {
    Rectangle()
}

I personally think that this approach makes it a little easier in some cases to understand what is going on when we apply a modifier. The problem is that as soon as you start applying multiple modifiers it quickly becomes a Pyramid of Doom. It does not escalate well.

CornerRadius(5) {
    Background(Color.red) {
        Padding(10) {
            FrameLayout(width: 100, height: 100) {
                Rectangle()
            }
        }
    }
}

The functional approach chosen is much more scalable and cleaner and once you get used it becomes clear that it's the right choice.

Rectangle()
    .frame(width: 100, height: 100)
    .padding(10)
    .background(Color.red)
    .cornerRadius(5)

Conclusion

Modifiers are as essential as Views in SwiftUI and allows us to customize Views as much as necessary while keeping a clean code, thanks to its wisely chosen functional approach.

Latest comments (0)