How to Increase a SwiftUI View's Internal Reusability and Maintenance
The power of ViewBuilders
One of the strongest points of SwiftUI is its power to make any View/component totally reusable in any possible context it's inserted due to its declarative way of defining and the ease to write a complex interface with just a few lines of code. Also, you can check in real-time the outcomes via previews.
But imagine the following scenario: you have multiple screens with kinda similar content and keeping them would increase the amount of duplicated code.
Wait, but in this case, transforming the common parts into reusable views isn't enough? Well, if the whole portion of common code was the same for both scenes, it would, but how about the case we may have some different pieces of UI right in the middle of each content?
I will illustrate the following scenario in order to allow your understanding:
We have three scenes above: the first corresponding to a simple personal info formulary, the second one for a hiring process and the last one for a health plan.
Three different contexts although the interface is kinda similar, except for the different headers and the availability header view presenting just for the health plan. Wouldn't that be practical if we could define from outside which context we should follow?
Identifying the differences between the contexts
In order to make that View reusable, we should first identify which are the different pieces of UI in that View that could be customizable.
As we said earlier, there are two places that may vary across different scenarios, which are the top header and the availability header, that can be present or not:
So, as we checked, we have 6 possible customizations for our View in this scenario: A personal info header view, a hiring information header view or a health plan header view for the top content and having or not the availability header view at the middle.
Building our view in different contexts
Now let's define how should our View struct be in a declarative way, let's first define our header views that may fill our screen:
Now let's define the root View:
Repair that we simply placed the HealthPlanFormHeaderView
and AvailabilityHeaderView
as raw components, but what we really want is to turn them into customizable parameters. This is when we call the ViewBuilders
!
The ViewBuilders
are two parameters that are closures returning View types that we shall use to populate our root view:
Since, differently from the UIKit, we are dealing with structs
and View
is a protocol, we need to define two generic types which conform to the View protocol. We defined two different parameters to be injected to our RootView
, respectively being closures returning Content1
and Content2
types.
When initializing our root view, we simply take the returned views from both closures and assign to the topContent
and availabilityHeader
properties. We also must tag those two parameters in the init method as ViewBuilders.
Now that we parametrized our top and availability header views, let's simply call it within the body:
Brilliant! Now that we parametrized those two components, we may pass any kind of View into this component that it will be displayed in its right place:
As a SwiftUI developer, you are actually familiar with this concept, since most native components to the framework actually receive ViewBuilders
in order to customize its appearance.
Two great examples are the SwiftUI Button
, which defines a behavior for when clicking it and then a label
closure that describes its appearance. The same can be verified in VStacks
and HStacks
, which take some injected content and display it across an axis, being horizontal our vertical
These are views that are injected into another view as customizable parameters.
Spreading our Reusable View across different scenarios
Now that we have our RootView
, that is a customizable component, we can reuse it inside other screens(Views) injecting the proper data needed for that context:
Respectively, we shall have these scenes:
If some new scenarios and possible headers raise, we just need to inject the corresponding instances to our reusable RootView
, that is called in the screen's body.
ViewBuilders as private functions
As we saw, the ViewBuilder
is a closure that returns a View
type, but it may also be a named function inside your View type that establishes some parametrization for some piece of UI:
Now we created a piece of code that returns a customizable component depending on the hasHeader
boolean.
If you have a too complex UI for your view and don't think creating separated files and Views to fill it is a good approach since these pieces of UI are made just for this context, separating different parts of your view into a ViewBuilder
private method is always a good choice.
Summary
In this article, we described a way of improving a View's reusability, by turning some pieces of UI variations into customizable parameters. As your app is growing, you can always reuse a View by injecting some of its parts from outside.
ViewBuilders work as parameters to your View that are actually subviews. Any time you identify a screen that is kinda reused in different contexts, repair which parts are specific to each one and transform them into parameters.
I hope you enjoyed it and keep improving your code's beauty and reusability ;)