Follow along at https://www.hackingwithswift.com/100/swiftui/23.
This day covers Part One of Project 3: Views and Modifiers
in the 100 Days of SwiftUI Challenge.
It focuses on several specific topics:
- Views and modifiers: Introduction
- Why does SwiftUI use structs for views?
- What is behind the main SwiftUI view?
- Why modifier order matters
- Why does SwiftUI use “some View” for its view type?
- Conditional modifiers
- Environment modifiers
- Views as properties
- View composition
- Custom modifiers
- Custom containers
Many reasons... but the one that stands out to me most is value semantics. Structs force us to focus creating isolated, idempotent, data-driven view -- very analogous to the notion of a pure function, and very much in line with SwiftUI's declarative-, data-driven-, and reactive-view ethos.
Nothing. All views are isolate pieces of UI. At the topmost level, our entry view gets wrapped in a UIHostingController
to render within a UIKit scene.
Each modifier creates its own View
. This means that successive modifiers are actually modifying the View
generated by the previous modifier -- not the base View
that they're all chained to.
-
Short version: This allows the specific type of the
View
to be determined at compile time. -
Long version: https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html
If we find ourselves repeating the same chain of modifiers on multiple views, we can group all of these up into a custom modifier by creating a ViewModifier
type.
These are similar to custom View
s; a slight difference is that ViewModifier
types have a body
function, rather than a computed property:
struct Watermark: ViewModifier {
var text: String
func body(content: Content) -> some View {
ZStack(alignment: .bottomTrailing) {
content
Text(text)
.font(.caption)
.foregroundColor(.white)
.padding(5)
.background(Color.black)
}
}
}
Custom modifiers are applied by using the View.modifier
function on a View
, and passing in the modifier instance.
We can make this even smoother by extending View
directly -- like so:
extension View {
func watermarked(with text: String) -> some View {
self.modifier(Watermark(text: text))
}
}
Color.blue
.frame(width: 300, height: 200)
.watermarked(with: "Hacking with Swift")
This is insanely useful -- not just for cleaning up our code, but also for encapsulating design requirements so that they can be applied consistently across our app.