The ease with which you can compose views in SwiftUI is a literal miracle. While composition is still doable (and valuable) in UIKit, the level of flexibility is at least an order of magnitude more rich.
But all this freedom brings some tough choices. How often should we be composing views? How many files is too many? Should we throw a component in a variable or an entirely separate named struct?
Here's a few thoughts to guide your choices.
Clarity is your sun
The single most important principle when deciding how to compose your views is clarity. It might seem that goes without saying, but it's easy to get your wires crossed.
There's a difference between optimizing for clarity and adding an explicit category to every possible grouping of views. The former makes your view hierarchy easier to reason about, while the latter just adds noise.
I'll dive into examples below, but if you find yourself feeling a compulsive itch to label stuff, take a step back and think again!
Reuse is your moon
The other reason we employ composition in SwiftUI is to avoid unnecessary duplication of code. In other words, reuse your views.
This can add to the clarity of your view hierarchy but it's also a distinct concern. Not reusing code has additional trade-offs beyond making your code less clear. It can also make it a nightmare to update, while also introducing bugs that come with forgetting to change all places in which a piece of view code has been copied and pasted.
Because of SwiftUI, composition on Apple platforms is easier than it ever has been before. So when you're tempted to copy/paste, remember it might be almost trivial to architect things in a way that your future self won't be cursing your name.
Variables (and functions) are your first line of defense
When composing our views (e.g. breaking them down into digestible components) our first line of defense is variables. This means taking chunks of functionality out of your view's body and moving them elsewhere in the view's overall definition.
Variables are wonderful and definitely have their place, but they can also be misused.
For example, consider Apple's Reminders app. In particular, take a look at the view that displays a list of reminders:
If I were to implement this view myself, it might look something like this:
This is pretty bad, so we turn to some simple variables to clean things up:
Already, the body of the view is vastly easier to reason about. It's now clear that the list has two bunches of things in it: the existing reminders and the button to add a new reminder at the bottom. Even if you stopped your improvements here, it would be way better than before.
First of all, the code in this file is starting to get awfully long. While we've clustered things more effectively, we still haven't done a great job of making the file's scope easier to reason about.
We need additional techniques.
Define separate structs as complexity demands
The obvious solution to address the awkwardness of the function in the example we’re working with is to turn each item into its own view. This both allows us to move a nice chunk of code outside of the list view while also making the API a bit clearer.
Second, it allows me to more meaningfully compose each item in the list into an API with its own component variables:
The question remains, should we continue to break this down further? And the answer would be no.
On top of that, moving the button out into it's own struct doesn't really add much value. Declaring it within the variable isn't very long, and using the variable "statusButton" in the view's body is logical and natural. In fact, creating a separate view would just add more complexity and work for us, so we skip it.
Eliminate noise beyond the scope of your views
Besides, we want to keep our view definitions limited to the level of abstraction that naturally follows from their name. Navigating between views is not inherent in the meaning of a list of reminders. Likewise, we earlier removed a function returning each reminder view so all component variables could be more naturally tied directly to something declared in the body. The variables and functions you use should be limited in number and not nested in scope. Your variables should be flat.
Don't over compose
Finally, there's one more consideration to keep in mind: Don't over compose. The body of your views should always be a clear snapshot of the view's hierarchy, and it should not be needlessly broken down. For example, you would almost never want to see a body with just one variable.
Now, go forward and create cleaner and more sensible SwiftUI compositions!
Triplebyte helps engineers find great jobs by assessing their abilities, not by relying on the prestige of their resume credentials. Take our 30 minute multiple-choice coding quiz to connect with your next big opportunity and join our community of 200,000+ engineers.