![]() > Create a custom `ViewModifier` (and accompanying `View` extension) that makes a view have a large, blue font suitable for prominent titles in a view. |
||
---|---|---|
.. | ||
Projects/ViewsAndModifiers | ||
README.md |
README.md
Day 23: Project 3: Views and Modifiers (Part One)
Follow along at https://www.hackingwithswift.com/100/swiftui/23.
📒 Field Notes
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
Why does SwiftUI use structs for views?
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.
What is behind the main SwiftUI view?
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.
Why modifier order matters
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.
Why does SwiftUI use “some View” for its view type?
-
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
Custom modifiers
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.