Understanding SwiftUI’s ViewModifiers and ViewBuilders


It has been more than 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. If you have started making apps with SwiftUI, you might have encountered situations where if you used UIKit, the work would be done easily, but with SwiftUI it can sometimes take all day – even more. But since SwiftUI is still a baby, these things shouldn’t stop one from learning and unwrapping its mysteries because, with time, it will grow, get better and make our app development way easier.

In this article, we are going to learn two of the key components of SwiftUI, which are ViewModifiers and ViewBuilders. ViewModifiers play a significant role in SwiftUI. Most of the functions called on a SwiftUI View are view modifiers. It is the primary way of modifying a view in SwiftUI. As for ViewBuilders, it is mainly used to create custom container views, which can also become a reusable view component.

In this post, we will take a look at some ready-to-use modifiers, then we will build our own custom view modifier and create a reusable custom container view with ViewBuilder. Using this we can implement gorgeous design systems that are consistent across the entire app and encourage code reusability and good architectural patterns.

Let’s get started!

*  *  *

ViewModifiers: 

SwiftUI lets you modify the appearance or behavior of views by applying view modifiers to them. A view modifier takes a given view, modifies it, and returns a brand new view. This is the core concept on which the view modifiers work and is important to understand.

This is an example of a SwiftUI defined ViewModifier:


struct SwiftUIView: View {
    var body: some View {
         Text(repo.description ?? “")
             .foregroundColor(.secondary)
             .font(.subheadline)
    }
}

Here, both foregroundColor() and font() are view modifiers, applying modification to the Text view and returning a new view on each modification.

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, like having a standard styling for heading throughout the app or having a theme color for the app.
If we were building an app in UIKit, we might have created a base view controller or an extension on that view component and applied our modifications. Thankfully, SwiftUI can help us with this by creating custom view modifiers and we can reuse them easily like a SwiftUI defined view modifier. Let’s create a custom view modifier!

To create a custom view modifier, we need to conform to the ViewModifier protocol, which has only one method requirement: func body(content: Self.Content) -> Self.Body.
You get a parameter as a body content of a caller view and you can return a modified version of that view.

Step 1: Create a struct type for your modifier and conform it to ViewModifier protocol


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

Step 2: Implement the ViewModifier protocol requirement

Inside this function, we just need to encapsulate all the modifiers that we would like to apply to the view. Let say our call-to-action have a background and rounded corners styling, and we want to have that as a standard for styling all call-to-action buttons throughout the app, this is what you would do:


struct MyViewModifier: ViewModifier {
    private let ctaThemeColor = Color.blue
    func body(content: Content) -> some View {
        content
            .foregroundColor(.white)
       	    .padding()
            .background(ctaThemeColor)
            .clipShape(Capsule())
    }
}

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


Button(action: { // Button Tap Action }) {
    Text("Call to action”)
}
.modifier(MyViewModifier())

That’s it! It will apply the custom view modifier to the button or view, giving it the kind of appearance that we want. Although you can improve this and make it better by creating a View extension and applying the modifier in it, like this:


extension Button {
    func ctaStyle() -> some View {
        modifier(MyViewModifier())
    }
}

And use this extended method like this:


Button(action: { /* Button Tap Action */ }) {
    Text("Call to action")
}
.ctaStyle()

Awesome right! You can now simply apply this view modifier to any button that you are using in your app, to apply the same appearance modifications, with only one line of code.

 

*  *  *

ViewBuilders: 

While working on an app, you will most likely find yourself in a situation where you have to use the same view multiple times. In UIKit, this could be done by creating a XIB’s or base view class. In SwiftUI, this can be done using ViewBuilders. You can also use ViewBuilders to create your custom container views, like VStack, HStack, etc. If you look inside the VStack or HStack, you’ll find they use ViewBuilder to construct views from closures.
Let’s try creating a custom container view using ViewBuilders

Step 1Create a View struct like this

Here, we’ll use Generics to let denote that the Content will conform to View protocol


struct MyContainerView<Content: View>: View {
    ...
}

Step 2: Add content as a property and initialize it


struct MyContainerView<Content: View>: View {
    let content: Content 
    init(@ViewBuilder content: () -> Content) { 
        self.content = content() 
    } 
}

Here, you typically used ViewBuilder as a parameter attribute for child view-producing closure parameters, allowing those closures to provide multiple child views.

Step 3: Implementing View protocol requirement in MyContainerView


struct MyContainerView<Content: View>: View {
    ...
    
    var body: some View {
        // To-do: The most important part will go here
    }
}

Step 4: Place all the common UI / layout logic into the body computed property

This will vary as per your requirement. For now, let’s assume that we need to show our content on a yellow background view, with our app name at the top right corner. Let’s write some code:


struct MyContainerView<Content: View>: View {
    ...

    var body: some View {
        ZStack {
            Color.yellow.edgesIgnoringSafeArea(.all)
            HStack {
                Spacer()
                VStack {
                    Text("DemoApp")
                        .foregroundColor(.purple)
                        .font(.system(size: 15, weight: .bold))
                        .padding(.trailing, 15)
                    Spacer()
                }
            }
            content
        }
    }
}

Step 5: Use MyContainerView() just like a VStack or HStack


struct DemoAppView: View {
    var body: some View {
        MyContainerView {
            Text("I ❤️ SwiftUI") // The main content
        }
    }
}

Voila, you just wrapped all of the body code of the custom screens into a MyContainerView entity, and those screens will get the layout and functionality defined in the MyContainerView, with no extra effort.

*  *  *

Conclusion:

Today we talked about some key concepts of SwiftUI. If you find yourself constantly attaching the same set of modifiers to a view or using the same kind of UI multiple places – then you can avoid those duplications by using ViewModifier or ViewBuilders. These are quite compelling concepts that we can use to build composable pieces of our apps.
And it is quite important for SwiftUI developers to get familiar with such concepts, so that their code stays clean and nice, making it easy to maintain and reuse big codebases in the long-term.

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

Source link

Leave a Reply