adam tecle

a pattern for reusing SwiftUI modifiers

When SwiftUI and Combine were announced last WWDC, like many others, I was highly intrigued.

The timing felt like a coincidence for me because I was just working on a React/Redux project where I was learning to appreciate declarative UIs and an architecture with unidirectional data flow. I was also doing more reactive programming.

I’d wanted to experiment with these concepts more on iOS by doing a side project, so I wrote an app built with ReactorKit and RxSwift to implement a Flux-style architecture.

Shipping that app, then watching WWDC and hearing all about this new declarative syntax for building views and a native reactive programming framework got me pretty excited about the future of Apple development. It was cool to see that the engineers at Apple were also influenced by this architectural trend in the industry and adjusting the platform’s development paradigm in response.

Recently I’ve been spending more time with SwiftUI, learning the API, and seeing what kinds of patterns/architectures might come about.

One code reuse pattern that seems useful is creating your own modifiers to reuse styling across views. Let’s see how this works.

So, what is a modifier again? It’s an instance function on a View type that returns a new View, with some transformation applied. Here’s a simple example:

struct ContentView: View {
  var body: some View {
    Text("wassup world?")
      .font(.system(size: 24, weight: .bold, design: .default))
      .foregroundColor(.red)
      .multilineTextAlignment(.center)
  }
}

We’re using modifiers to apply styling to our Text view here. What if we wanted to apply this same styling to several Text views? To accomplish this, we can leverage the ViewModifier protocol to declare a type that knows how to style a View in a certain way. Here’s how we could create a CustomTextStyle modifier.

struct CustomTextStyle: ViewModifier {
   func body(content: Content) -> some View {
      content
        .font(.system(size: 24, weight: .bold, design: .default))
        .foregroundColor(.red)
        .multilineTextAlignment(.center)
   }
}

/// Then apply it in our view

struct ContentView: View {
  var body: some View {
    Text("wassup world?")
      .modifier(CustomTextStyle())
  }
}

Pretty cool. We can take it even further and use property wrappers, new in Swift 5.1, to create a modifier that conditionally presents an ActionSheet.

Imagine you have the following code which uses a @State variable named isPresentingMenu which controls an ActionSheet’s visibility.

struct ContentView: View {

  @State var isPresentingMenu = false

  var body: some View {
    NavigationView {
      Text("wassup world?")
        .navigationBarItems(trailing: Button(action: {
          self.isPresentingMenu = true
        }) {
          Text("Menu")
        })
        .actionSheet(isPresented: $isPresentingMenu) {
          ActionSheet(
            title: Text("Example"),
            buttons: [.default(Text("Dismiss"))]
          )
        }
    }
  }
}

As you can see, in SwiftUI, presenting an ActionSheet works by applying a modifier. Let’s refactor this so we can reuse that ActionSheet wherever we please.

struct CustomActionSheet: ViewModifier {

  @Binding var isPresented: Bool

  init(isPresented: Binding<Bool>) {
    self._isPresented = isPresented
  }

  func body(content: Content) -> some View {
      content
        .actionSheet(isPresented: $isPresented) {
          ActionSheet(
            title: Text("Language"),
            buttons: [.default(Text("Dismiss"))]
          )
      }
  }
}

/// Then in our view
struct ContentView: View {

 @State var isPresentingMenu = false

  var body: some View {
    NavigationView {
      Text("wassup world?")
        .navigationBarItems(trailing: Button(action: {
          self.isPresentingMenu = true
        }) {
          Text("Menu")
        })
        .modifier(CustomActionSheet(isPresented: $isPresentingMenu))
    }
  }
}

We’re using @Binding in the CustomActionSheet struct to indicate that CustomActionSheet does not own this state, and is simply a dependency to the real source of truth, which is @State var isPresentingMenu in ContentView.

There you have it! Is there an even better way to do the same thing? Quite possible. Because SwiftUI is so new, there is a lot to discover when it comes to using it to solve common development problems.

As I play around more with SwiftUI, here are some things I’m especially interested in figuring out and potentially writing about later:


Hi internet guest, I’m Adam. I’m a software engineer based in Brooklyn, NY. I specialize in iOS development, but I approach technology as a generalist and enjoy building software across the stack.

You can find my resume here. If you’d like to say hello, send me an email.