Crafting SwiftUI UIs: Leveraging Composition with ViewModifiers and Custom Views
In this story, I want to talk about View Composition in SwiftUI.
SwiftUI is all about creating cool interfaces by mixing and matching small, reusable pieces. Composition lies at the heart of SwiftUI, empowering developers to build complex user interfaces from simple, reusable components. It lets us break complex views down into smaller views without incurring much, if any, performance impact.
I know two ways to use Composition in SwiftUI.
Wrap identical parts of views and create a new custom view with them
For example, in this view we have a particular way of styling text, they have some padding, foreground, and background colors:
struct MyView: View {
var body: some View {
Text("Title")
.font(.title)
.padding(12)
.foregroundColor(.white)
.background(Color.blue)
Text("Caption")
.font(.caption)
.padding(12)
.foregroundColor(.white)
.background(Color.blue)
}
}
Because those two texts are identical apart from their text and font, we can wrap them up in a new custom view like this:
struct MyText: View {
let text: String
let font: Font
var body: some View {
Text(text)
.font(font)
.padding(12)
.foregroundColor(.white)
.background(Color.blue)
}
}
Then we can reuse MyText inside our original view:
struct MyView: View {
var body: some View {
MyText(text: "Title", font: .title)
MyText(text: "Caption", font: .caption)
}
}
This approach provides explicitness and fine-grained control over individual modifiers.
Composition with ViewModifiers
ViewModifiers in SwiftUI encapsulate reusable stylings and functionalities, facilitating a clean and modular approach to building UI components. Let’s say we have multiple Texts with different icons in our app:
struct MyPlayer: View {
var body: some View {
HStack {
Image(systemName: "play.fill")
Text("Play")
}
HStack {
Image(systemName: "pause.fill")
Text("Pause")
}
....
}
}
We can use a ViewModifier to hide the complexity and benefit Composition, like this:
extension View {
func iconText(icon: Image) -> some View {
modifier(IconText(icon: icon))
}
}
private struct IconText: ViewModifier {
private var icon: Image
init(icon: Image) {
self.icon = icon
}
func body(content: Content) -> some View {
HStack {
icon
content
}
}
}
and simply apply it to our Texts:
Text("Play")
.iconText(icon: Image(systemName: "play.fill"))
Text("Pause")
.iconText(icon: Image(systemName: "pause.fill"))
Conclusion
SwiftUI’s composition capabilities, whether through ViewModifiers or custom views, offer developers flexibility and efficiency in crafting UI components. ViewModifiers streamline development by encapsulating reusable styling, while composing views without them grants explicit control for precise customization.
Balancing between ViewModifiers and custom view allows developers to achieve the desired UI, whether it’s prioritizing reusability or fine-tuning individual components. I have another story about ViewModifiers. For more info, you can check it out.
By mastering SwiftUI’s composition techniques, developers can craft elegant and maintainable user interfaces, blending efficiency with granular control for optimal app design.