Last active
August 2, 2024 18:21
-
-
Save artyom-stv/a87f572999074aa9afab03d8e96eef2e to your computer and use it in GitHub Desktop.
Visitor-based approach for accessing `@ViewBuilder`-provided content
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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)) | |
} | |
// ... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
Hi Matt!
SwiftUI is written on C++ under the hood. My guess is that SwiftUI utilizes some private C++ API for accessing Swift runtime.
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
Random question from the internet here: how does the use of the visitor pattern here enable your
SampleVisitor
to know whether thecontent
is aTupleView<(C0, C1)>
vsTupleView<(C0, C1, C2)>
, etc? For me, it still remains a mystery how SwiftUI ultimately unpacks itsTupleView<T>
into actual statically typed subviews without the use of type eraser (e.g. AnyView).