When working with declarative views, you should be able to reach for a full tool suite of functional application. In a typesafe language like Swift, things can prove more difficult than you’d might first think. Consider the following code:
What is core
‘s type? It isn’t Text
. It’s actually an application of modified content, specifically Text
passed through a rotation effect:
Just add a background color and a shadow and the type jumps to this:
You might ask: why is this a problem? After all, Swift is doing all the heavy lifting, right? In my case, the answer lies in my struggle to incorporate this core image into a multi-stage bit of text art using reduce
. Paul Hudson tweeted a step-by-step approach to this and I was sure I could make it simpler and more elegant.
And that’s where I started throwing myself against what at first seemed like an impenetrable wall for a couple of hours. Between SwiftUI’s stroke-style Dysarthria error messages and the typesafe system, my attempt at creating a solution along these lines felt doomed:
[Color.red, .orange, .yellow, .green, .blue, .purple].reduce(core) { view, color in view.padding() .background(color) .rotationEffect(theta) }
The code wouldn’t compile and the error messages couldn’t tell me why. The problem? Each stage created a new layer of modified content, changing the type and rendering reduce
unable to do the work. It was only with the help of some deep-dives into the docs and advice from colleagues that I was able to arrive at a solution.
Type erasure, using SwiftUI’s AnyView
struct enables you to change the type of a given view, destroying the nested hierarchy. Importantly, it creates a single type, allowing a reduce
operation to proceed.
At first, I used AnyView
the way you’d typecast in Swift, namely:
AnyView(view.padding() .background(color) .rotationEffect(theta))
But I hated that. It sticks out as so Un-SwiftUI-y, with the parentheses spanning multiple lines and throwing off the clear logical flow. If you’re going to go fluent, just go fluent. So, eventually, I decided to create a View
type extension to handle this:
extension View { /// Returns a type-erased version of the view. public var typeErased: AnyView { AnyView(self) } }
The result looks, instead, like this:
view.padding() .background(color) .rotationEffect(Angle(radians: .pi / 6)) .typeErased
And yes, I went with a property and not a function as I felt this was expressing a core characteristic inherent to each View
. I can probably argue it the other way as well.
From there, it wasn’t much of a leap to ask “what other fluent interface tricks can I apply”, and I ended up putting together this little View
extensions for inline peeks:
extension View { /// Passes-through the view with debugging output public func passthrough() -> Self { print("\(Self.self): \(self)") return self } }
This prints out an instance’s type and a rendering of the instance, which will vary depending on whether there’s a custom representation, passing the actual instance through to whatever the next stage of chaining is. I don’t use it much but when I do, it’s been pretty handy at taking a peek where Xcode’s normal QuickLook features hit the edge.
In any case, I thought I’d share these in case they’re of use to anyone else. Drop me a note or a tweet or a comment if they help. Cheers!
Update: It suddenly occurred to me that I could make this a lot more general:
extension View { /// Passes-through the view with customizable side effects public func passthrough(applying closure: (_ instance: Self) -> ()) -> Self { closure(self) return self } }
Isn’t that nicer? The equivalent is now:
struct MyView: View { var body: some View { [Color.red, .orange, .yellow, .green, .blue, .purple] .reduce(Text("👭") .font(.largeTitle) .rotationEffect(Angle(radians: .pi)) .typeErased) { view, color in view.padding() .background(color) .rotationEffect(Angle(radians: .pi / 6)) .passthrough { print("\(type(of: $0)), \($0)") } .typeErased } } }
And I can put any behavior in from printouts to timing to any other side effect I desire. To all the functional purists out there, I sincerely apologize. 🙂
One Comment
Thank you, Erica. This helped me pass a view to a function without having to worry about the complex type.