Mega Bundle SALE is ON! Get ALL of our amazing iOS app codebases at 95% OFF discount 🔥

Hey everyone, in this article, we are going to learn how to build custom reusable components with ViewModifier, and how to create base views with ViewBuilder. These are powerful mechanisms to implement gorgeous design systems that are consistent across the entire app and that encourage code reusability and good architectural patterns.

It has been more than half a year since the release of SwiftUI, and we certainly had some interesting experiences with it so far. It can create a lot of  excitement, surprise and sometimes a ton of annoyance too. Since SwiftUI is still a baby, we often encounter situations where if we used UIKit, the work would be done in the blink of an eye, but with SwiftUI it can take all day – even more. Understanding that, we will continue our SwiftUI tutorials series.

viewmodifier swiftui

Let’s see how ViewBuilder and ViewModifier help us create reusable components and base views in SwiftUI.

1. The Problems

Using a base view controller that contains the generic components has traditionally been a familiar approach in UIKit. However, with SwiftUI, all of our views are Struct, and the way it works is completely different from the well-known UIViewController. So how can the same reusability mechanism be achieved in the new SwiftUI framework?

Two problems that most SwiftUI developers will face and need to address:a

1. Creating a BaseView – A view has common UI elements, layout helpers and utility functions like setting a background image, adding a tab bar, setting navigation bar title, alerts, loading indicators – all in one place. Any other SwiftUI views should inherit the properties from BaseView.

2. Reusing UI components that are consistent across the app.

2. The Solutions

Let’s consider this example: we have an authentication flow for which the sign in and the sign up screens are quite similar, containing a logo and a button at the bottom, among other things.

1. ViewModifier

In fact, in an iOS project, having to customize buttons, texts, or text fields is almost inevitable. Going back and forth, we will see familiar fonts and designs. Thankfully, SwiftUI can help us with this with the powerful ViewModifier. LetText be an example.

Step 1: Create your custom Text and remember to conform to ViewModifier

struct TextModifier: ViewModifier {
}

Step 2: Xcode will throw you an error: “Your type does not conform to protocol “ViewModifier”. To fix this, we need to add this function:

struct TextModifier: ViewModifier {
    let color: UIColor
    func body(content: Content) -> some View {
       // To-do: define your modifiers here
    }
}

Inside this function, we just need to encapsulate all the modifiers that you would like to add to your custom view.

struct TextModifier: ViewModifier {
    let color: UIColor
    func body(content: Content) -> some View {
        content 
          .fixedSize(horizontal: false, vertical: true) 
          .foregroundColor(Color(color)) 
          .multilineTextAlignment(.center) 
          .lineLimit(nil)
    }
}

Step 3: For any view that needs to use this custom modifier, just embed it as follows:

struct ContentView: View {
    var body: some View {
        Text("iosAppTemplates.com")
            .modifier(TextModifier())
    }
}

Boom! You can now simply apply this text modifier to any custom view that you are using in your app, to apply the same colors, line limits, and size customizations, with only one line of code.

2. ViewBuilder

A mobile application usually has a consistent design – for example, the logo might appear at the top of the screen, all the screens have the same background color and so on. But content can vary drastically. To avoid duplication, we will create a BaseView (inspired by the well known UIKit technique – BaseViewController). In order to create a base view, we will leverage the powerful SwiftUI’s ViewBuilder.

Step 1: Create a BaseView like this

struct BaseView<Content: View>: View {

}

Step 2: Initialization in BaseView

let content: Content

init(@ViewBuilder content: () -> Content) {
    self.content = content()
}

Step 3: Because the BaseView conforms to the View protocol, our struct should have a variable that is very familiar to us already: body

struct BaseView<Content: View>: View {
    let content: Content

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        // To-do: The most important part will go here
    }
}

Step 4: We are going to place all the common UI and layout logic into the BaseView’s body function, as you’ve probably guessed by now.

VStack(alignment: .center, spacing: 25) {
    HStack {
       Image("iosAppTemplates-logo-icon")
     }.padding(.top, 60)
     Spacer()
     Text("iOS App Templates")
     content
}

Step 5: You can consider this BaseView() now just like VStack or HStack. Your task now is only to update the content of the current view. The generic components are already in BaseView. So simply make sure you are wrapping all of the body code of the custom screens into a BaseView entity, and those screens will get a lot of functionality for free, with no extra effort.

struct ContentView: View {
    var body: some View {
        BaseView() {
            Text("iosAppTemplates.com")
        }      
    }
}

3. Conclusion

Well, with these two extremely powerful tools, we were able to reuse components and be able to build a BaseView foundation class similarly to how we used to do it in UIKit. The simplicity of SwiftUI and the intense focus on the UI layer can be a trap to iOS developers, since it’s very tempting now to violate SOLID principles and use bad practices, just because these are quick.

It’s important for SwiftUI programmers to get familiar with concepts such as ViewBuilder and ViewModifier, so that their code stays clean and nice, making it it easy to maintain and reuse big codebases in the long-term.

If you’ve enjoyed this article, please share it out and let us know what you think!


Leave a Reply

Your email address will not be published. Required fields are marked *

Shopping Cart