Optimize your SwiftUI code: Two ways to avoid duplication

This post is free for all to read thanks to the investment Mindsers Courses members have made in our independent publication. If this work is meaningful to you, I invite you to join our courses today.

Code duplication is a real problem in applications. Having a lot of duplicate code means “complicated maintenance” but also a catastrophic experience.

I have two methods to avoid this problem in my Swift applications. I'll tell you all about them in this article.

Custom components for greater SwiftUI code modularity

As you would with other frameworks, SwiftUI allows you to create views in a declarative/descriptive way.

The views are composed of other views and the pattern can be repeated endlessly on several levels.

struct SignInScreen: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            Text("Connect")
            Spacer()
        }
    }
}

In the example above, the SignInScreen view is composed of a VStack view, a Text view and a Spacer view. Same operation therefore as a web framework or the global component will be composed of several smaller components.

Since views are just assemblies of views, we too can create our own views to bring together repeating elements.

struct SignInScreen: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            TextField("Email address", text: $email)

			DividerCustom()
            
            Button("Connect") {
                // action
            }
        }
    }
}

struct SignUpScreen: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            TextField("Email address", text: $email)

			DividerCustom()
            
            Button("Sign Up") {
                // action
            }
        }
    }
}

struct DividerCustom: View {
    var body: some View {
        HStack(alignment: .center) {
            VStack {
                Divider()
                    .frame(height: 1.0)
                    .background(.green)
            }
            Text("ou")
            VStack {
                Divider()
                    .frame(height: 1.0)
                    .background(.green)
            }
        }
    }
}

The DividerCustom view combines three other views and thus forms a view that is called in the SignInScreen and SignUpScreen view.

Modifying the DividerCustom code will update the element in both components where it is used. This prevents the code from being duplicated several times in the application.

Optimization with modifiers and ViewModifier

Sometimes, what is duplicated is not a set of components but a set of modifications on a single view.

struct Buttons: View {
    var body: some View {
        Button {
            // action
        } label: {
            Label("Connect with Google", image: "GoogleLogo")
                .frame(maxWidth: .infinity)
        }
        .padding()
        .frame(minWidth: 300, alignment: .center)
        .background(backgroundColor)
        .foregroundColor(foregroundColor)
        .cornerRadius(30)
        .font(.Theme.regular(size: 16))

        Button {
            // action
        } label: {
            Label("Connect with Facebook", image: "FacebookLogo")
                .frame(maxWidth: .infinity)
        }
        .padding()
        .frame(minWidth: 300, alignment: .center)
        .background(backgroundColor)
        .foregroundColor(foregroundColor)
        .cornerRadius(30)
        .font(.Theme.regular(size: 16))

        Button {
            // action
        } label: {
            Label("Connect with GitHub", image: "GitHubLogo")
                .frame(maxWidth: .infinity)
        }
        .padding()
        .frame(minWidth: 300, alignment: .center)
        .background(backgroundColor)
        .foregroundColor(foregroundColor)
        .cornerRadius(30)
        .font(.Theme.regular(size: 16))
    }
}

In the example above, we want to display three login buttons for different services.

We don't want to change the behavior of a classic button or even associate it with other views so that they work together. What we want here is to modify the design of the button so that all the buttons look the same.

We will end up with 6 lines of modifications of a button which are duplicated each time a new button is created.

If I ultimately want to change the button's maximum width, I'll have to apply the change manually for each button!

One solution to avoid code duplication (and simplify application maintenance) in this situation is ViewModifiers. This is a very simple way to apply modifiers to multiple views:

struct BasicButton: ViewModifier {
    let backgroundColor: Color
    let foregroundColor: Color
    
    func body(content: Content) -> some View {
        content
            .padding()
            .frame(minWidth: 300, alignment: .center)
            .background(backgroundColor)
            .foregroundColor(foregroundColor)
            .cornerRadius(30)
            .font(.Theme.regular(size: 16))
    }
}

A ViewModifier is a struct with a body function. It is this function that will apply the styles to our views.

To use it we use the generic modifier modifier() as follows:

Button {
  // action
} label: {
  Label("Connexion avec GitHub", image: "GitHubLogo")
    .frame(maxWidth: .infinity)
}
.modifier(BasicButton(backgroundColor: .black, foregroundColor: .white))

And that's it!

BasicButton will apply the styles to our Button view with no more effort.

To make the code a little more elegant, here is a little trick:

  1. We first add a function to the View class that will call the modifier.
    Note also the use of optional parameters so you don't have to mention them if they do not change.
  2. And we call this function from our Button view like classic modifiers

You can also limit the use of this modifier to the Button view by extending the Button class rather than View, as we have done here.

The downside is that you will only be able to call the modifier directly on the view. It cannot be chained after other modifiers, because this one return an object of type View and not Button.

Conclusion

As already discussed, avoiding code duplication is not only a good practice to make your code look "pretty". Rather, it's one of the easiest ways to optimize your code to reduce development time and application maintenance time.

Do not hesitate to use the ViewModifier or the creation of custom components to optimize your SwiftUI code.

Join 250+ developers and get notified every month about new content on the blog.

No spam ever. Unsubscribe in a single click at any time.

If you have any questions or advices, please create a comment below! I'll be really glad to read you. Also if you like this post, don't forget to share it with your friends. It helps a lot!