Skip to content

Instantly share code, notes, and snippets.

@artyom-stv
Last active February 23, 2023 00:12
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save artyom-stv/a87f572999074aa9afab03d8e96eef2e to your computer and use it in GitHub Desktop.
Save artyom-stv/a87f572999074aa9afab03d8e96eef2e to your computer and use it in GitHub Desktop.
Visitor-based approach for accessing `@ViewBuilder`-provided content
// SwiftUI public API
public protocol View {
associatedtype Body: View
var body: Self.Body { get }
}
extension Never: View {
public typealias Body = Never
}
extension View where Body == Never {
public var body: Never {
fatalError()
}
}
public struct EmptyView: View {
@inlinable
public init() {}
public typealias Body = Swift.Never
}
public struct TupleView<T>: View {
public let value: T
@inlinable
public init(_ value: T) {
self.value = value
}
}
@_functionBuilder
public struct ViewBuilder {
@_alwaysEmitIntoClient
public static func buildBlock() -> EmptyView {
return EmptyView()
}
@_alwaysEmitIntoClient
public static func buildBlock<Content>(_ content: Content) -> Content where Content: View {
return content
}
@_alwaysEmitIntoClient
public static func buildBlock<C0, C1>(
_ c0: C0,
_ c1: C1) -> TupleView<(C0, C1)>
where C0: View, C1: View
{
TupleView((c0, c1))
}
@_alwaysEmitIntoClient
public static func buildBlock<C0, C1, C2>(
_ c0: C0,
_ c1: C1,
_ c2: C2) -> TupleView<(C0, C1, C2)>
where C0: View, C1: View, C2: View
{
TupleView((c0, c1, c2))
}
// ...
}
// Visitor
protocol _TupleViewVisitor {
associatedtype VisitResult
func visit<T>(_ view: TupleView<T>) -> VisitResult
}
protocol _TupleView {
func accept<Visitor>(_ visitor: Visitor) -> Visitor.VisitResult where Visitor: _TupleViewVisitor
}
extension TupleView: _TupleView {
func accept<Visitor>(_ visitor: Visitor) -> Visitor.VisitResult where Visitor: _TupleViewVisitor {
visitor.visit(self)
}
}
protocol _ViewBuilderContentVisitor {
associatedtype VisitResult
// Visit `EmptyView` content
func visit(_ content: EmptyView) -> VisitResult
// Visit `TupleView<T>` content
func visit<T>(_ content: TupleView<T>) -> VisitResult
// Visit any other type of content
func visit<Content>(_ content: Content) -> VisitResult
}
struct _ViewBuilderTupleVisitor<InnerVisitor>: _TupleViewVisitor where InnerVisitor: _ViewBuilderContentVisitor {
typealias VisitResult = InnerVisitor.VisitResult
let innerVisitor: InnerVisitor
init(innerVisitor: InnerVisitor) {
self.innerVisitor = innerVisitor
}
func visit<T>(_ view: TupleView<T>) -> VisitResult {
innerVisitor.visit(view)
}
}
func _acceptViewBuilderContentVisitor<Content, Visitor>(_ visitor: Visitor, content: Content) where Visitor: _ViewBuilderContentVisitor {
if let content = content as? EmptyView {
visitor.visit(content)
} else if let content = content as? _TupleView {
content.accept(_ViewBuilderTupleVisitor(innerVisitor: visitor))
} else {
visitor.visit(content)
}
}
// Sample usage
public struct MyContainerView<Content>: View where Content: View {
public let content: Content
public init(@ViewBuilder content: () -> Content) {
self.content = content()
}
}
let view = MyContainerView {
EmptyView()
EmptyView()
}
struct SampleVisitor: _ViewBuilderContentVisitor {
func visit(_ content: EmptyView) {
print("Empty content")
}
func visit<T>(_ content: TupleView<T>) {
print("Tuple content")
}
func visit<Content>(_ content: Content) {
print("Other content")
}
}
_acceptViewBuilderContentVisitor(SampleVisitor(), content: view.content)
@mgadda
Copy link

mgadda commented May 31, 2022

Random question from the internet here: how does the use of the visitor pattern here enable your SampleVisitor to know whether the content is a TupleView<(C0, C1)> vs TupleView<(C0, C1, C2)>, etc? For me, it still remains a mystery how SwiftUI ultimately unpacks its TupleView<T> into actual statically typed subviews without the use of type eraser (e.g. AnyView).

@artyom-stv
Copy link
Author

Hi Matt!
SwiftUI is written on C++ under the hood. My guess is that SwiftUI utilizes some private C++ API for accessing Swift runtime.

@mgadda
Copy link

mgadda commented May 31, 2022

Oh that would be a shame! Equally unsurprising and disappointing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment