diff --git a/Sources/OpenSwiftUICore/Runtime/ConditionalMetadata.swift b/Sources/OpenSwiftUICore/Runtime/ConditionalMetadata.swift
index 735afbe2b..9baa6182c 100644
--- a/Sources/OpenSwiftUICore/Runtime/ConditionalMetadata.swift
+++ b/Sources/OpenSwiftUICore/Runtime/ConditionalMetadata.swift
@@ -76,7 +76,16 @@ package struct ConditionalTypeDescriptor
where P: ConditionalProtocolDescript
if descriptor == conditionalTypeDescriptor {
let falseDescriptor = Self.descriptor(type: metadata.genericType(at: 1))
let trueDescriptor = Self.descriptor(type: metadata.genericType(at: 0))
- storage = .either(type, f: falseDescriptor, t: trueDescriptor)
+ // FIXME: How to get _ConditionalContent.Storage type more easily
+ typealias Accessor = @convention(c) (UInt, Metadata, Metadata) -> Metadata
+ let nominal = Metadata(_ConditionalContent.Storage.self).nominalDescriptor!
+ let accessorRelativePointer = nominal.advanced(by: 12)
+ let accessor = unsafeBitCast(
+ accessorRelativePointer.advanced(by:Int(accessorRelativePointer.assumingMemoryBound(to: Int32.self).pointee)),
+ to: Accessor.self
+ )
+ let type = accessor(0, Metadata(metadata.genericType(at: 0)), Metadata(metadata.genericType(at: 1)))
+ storage = .either(type.type, f: falseDescriptor, t: trueDescriptor)
count = falseDescriptor.count + trueDescriptor.count
} else if descriptor == optionalTypeDescriptor {
let wrappedDescriptor = Self.descriptor(type: metadata.genericType(at: 0))
@@ -150,6 +159,29 @@ extension Optional {
}
}
+// MARK: - ConditionalContent + ConditionalMetadata
+
+extension _ConditionalContent {
+ static func makeConditionalMetadata(_ protocolDescriptor: P.Type) -> ConditionalMetadata
where P: ConditionalProtocolDescriptor {
+ let descriptor: ConditionalTypeDescriptor
+ if let result = P.fetchConditionalType(key: ObjectIdentifier(Self.self)) {
+ descriptor = result
+ } else {
+ descriptor = {
+ let falseDescriptor = ConditionalTypeDescriptor
.descriptor(type: FalseContent.self)
+ let trueDescriptor = ConditionalTypeDescriptor
.descriptor(type: TrueContent.self)
+ return ConditionalTypeDescriptor(
+ storage: .either(Storage.self, f: falseDescriptor, t: trueDescriptor),
+ count: falseDescriptor.count + trueDescriptor.count
+ )
+
+ }()
+ P.insertConditionalType(key: ObjectIdentifier(Self.self), value: descriptor)
+ }
+ return ConditionalMetadata(descriptor)
+ }
+}
+
// MARK: ConditionalMetadata + ViewDescriptor
extension ConditionalMetadata where P == ViewDescriptor {
diff --git a/Sources/OpenSwiftUICore/View/ConditionalContent.swift b/Sources/OpenSwiftUICore/View/ConditionalContent.swift
index 38397d06b..4bf810eee 100644
--- a/Sources/OpenSwiftUICore/View/ConditionalContent.swift
+++ b/Sources/OpenSwiftUICore/View/ConditionalContent.swift
@@ -1,64 +1,247 @@
//
// ConditionalContent.swift
-// OpenSwiftUI
+// OpenSwiftUICore
//
-// Audited for iOS 15.5
-// Status: WIP
-// ID: 1A625ACC143FD8524C590782FD8F4F8C
+// Audited for iOS 18.0
+// Status: Complete
+// ID: 1A625ACC143FD8524C590782FD8F4F8C (SwiftUI)
-import OpenGraphShims
+package import OpenGraphShims
+
+// MARK: - ConditionalContent
/// View content that shows one of two possible children.
@frozen
public struct _ConditionalContent {
- @usableFromInline
@frozen
- enum Storage {
+ public enum Storage {
case trueContent(TrueContent)
case falseContent(FalseContent)
}
- @usableFromInline
- let storage: _ConditionalContent.Storage
+ public let storage: Storage
}
+@available(*, unavailable)
+extension _ConditionalContent.Storage: Sendable {}
+
+@available(*, unavailable)
+extension _ConditionalContent: Sendable {}
+
+extension _ConditionalContent {
+ /// Creates a conditional content.
+ ///
+ /// You don't use this initializer directly. OpenSwiftUI creates a
+ /// _ConditionalContent on your behalf when using conditional
+ /// statements in a variety of result builders.
+ @available(*, deprecated, message: "Do not use this.")
+ @_alwaysEmitIntoClient
+ public init(_storage: Storage) {
+ self.storage = _storage
+ }
+
+ @_alwaysEmitIntoClient
+ package init(__storage: Storage) {
+ self.storage = __storage
+ }
+}
+
+// MARK: - ConditionalContent + View
+
extension _ConditionalContent: View, PrimitiveView where TrueContent: View, FalseContent: View {
@usableFromInline
init(storage: Storage) {
self.storage = storage
}
- public static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs {
+ nonisolated public static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs {
if _SemanticFeature_v2.isEnabled {
- makeImplicitRoot(view: view, inputs: inputs)
+ return makeImplicitRoot(view: view, inputs: inputs)
} else {
- AnyView._makeView(
- view: _GraphValue(ChildView(content: view.value)),
- inputs: inputs
- )
+ let metadata = makeConditionalMetadata(ViewDescriptor.self)
+ return makeDynamicView(metadata: metadata, view: view, inputs: inputs)
}
}
- // public static func _makeViewList(view: _GraphValue<_ConditionalContent>, inputs: _ViewListInputs) -> _ViewListOutputs
- // public static func _viewListCount(inputs: _ViewListCountInputs) -> Swift.Int?
-
- private struct ChildView: Rule, AsyncAttribute {
- @Attribute var content: _ConditionalContent
+ nonisolated public static func _makeViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs {
+ let metadata = makeConditionalMetadata(ViewDescriptor.self)
+ return makeDynamicViewList(metadata: metadata, view: view, inputs: inputs)
+ }
+
+ nonisolated public static func _viewListCount(inputs: _ViewListCountInputs) -> Int? {
+ guard let trueCount = TrueContent._viewListCount(inputs: inputs),
+ trueCount == FalseContent._viewListCount(inputs: inputs) else {
+ return nil
+ }
+ return trueCount
+ }
+}
+
+// MARK: - ConditionalContent + DynamicView
+
+extension _ConditionalContent: DynamicView where TrueContent: View, FalseContent: View {
+ package static var canTransition: Bool {
+ true
+ }
+
+ package func childInfo(metadata: Metadata) -> (any Any.Type, ID?) {
+ withUnsafePointer(to: self) { ptr in
+ metadata.childInfo(ptr: ptr, emptyType: EmptyView.self)
+ }
+ }
+
+ package func makeChildView(metadata: Metadata, view: Attribute, inputs: _ViewInputs) -> _ViewOutputs {
+ withUnsafePointer(to: self) { ptr in
+ metadata.makeView(ptr: ptr, view: view, inputs: inputs)
+ }
+ }
+
+ package func makeChildViewList(metadata: Metadata, view: Attribute, inputs: _ViewListInputs) -> _ViewListOutputs {
+ withUnsafePointer(to: self) { ptr in
+ metadata.makeViewList(ptr: ptr, view: view, inputs: inputs)
+ }
+ }
+
+ package typealias ID = UniqueID
+
+ package typealias Metadata = ConditionalMetadata
+}
- let ids: (UniqueID, UniqueID)
+extension _ConditionalContent {
+ // MARK: - ConditionalContent + Info
- init(content: Attribute<_ConditionalContent>) {
- _content = content
- ids = (UniqueID(), UniqueID())
+ package struct Info {
+ var content: _ConditionalContent
+ var subgraph: Subgraph
+
+ init(content: _ConditionalContent, subgraph: Subgraph) {
+ self.content = content
+ self.subgraph = subgraph
}
- var value: AnyView {
+ func matches(_ other: _ConditionalContent) -> Bool {
switch content.storage {
- case .trueContent(let view):
- AnyView(view)
- case .falseContent(let view):
- AnyView(view)
+ case .trueContent:
+ switch other.storage {
+ case .trueContent: true
+ case .falseContent: false
+ }
+ case .falseContent:
+ switch other.storage {
+ case .trueContent: false
+ case .falseContent: true
+ }
}
}
}
+
+ // MARK: - ConditionalContent + Container
+
+ package struct Container: StatefulRule, AsyncAttribute
+ where TrueContent == Provider.TrueContent,
+ FalseContent == Provider.FalseContent,
+ Provider: ConditionalContentProvider {
+ @Attribute var content: _ConditionalContent
+ let provider: Provider
+ let parentSubgraph: Subgraph
+
+ package init(content: Attribute<_ConditionalContent>, provider: Provider) {
+ self._content = content
+ self.provider = provider
+ self.parentSubgraph = Subgraph.current!
+ }
+
+ package typealias Value = Info
+
+ package mutating func updateValue() {
+ let content = content
+ guard hasValue, value.matches(content) else {
+ if hasValue {
+ eraseInfo(value)
+ }
+ value = makeInfo(content)
+ return
+ }
+ var info = value
+ info.content = content
+ value = info
+ }
+
+ func makeInfo(_ content: _ConditionalContent) -> Info {
+ let current = AnyAttribute.current!
+ let graph = parentSubgraph.graph
+ let newSubgraph = Subgraph(graph: graph)
+ parentSubgraph.addChild(newSubgraph)
+ return newSubgraph.apply {
+ let inputs = provider.makeChildInputs()
+ let outputs: Provider.Outputs
+ switch content.storage {
+ case let .trueContent(trueContent):
+ let trueChild = TrueChild(info: .init(identifier: current))
+ let trueChildAttribute = Attribute(trueChild)
+ trueChildAttribute.value = trueContent
+ outputs = provider.makeTrueOutputs(child: trueChildAttribute, inputs: inputs)
+ case let .falseContent(falseContent):
+ let falseChild = FalseChild(info: .init(identifier: current))
+ let falseChildAttribute = Attribute(falseChild)
+ falseChildAttribute.value = falseContent
+ outputs = provider.makeFalseOutputs(child: falseChildAttribute, inputs: inputs)
+ }
+ provider.attachOutputs(to: outputs)
+ return Info(content: content, subgraph: newSubgraph)
+ }
+ }
+
+ func eraseInfo(_ info: Info) {
+ let subgraph = info.subgraph
+ subgraph.willInvalidate(isInserted: true)
+ subgraph.invalidate()
+ }
+ }
+
+ // MARK: - ConditionalContent + TrueChild
+
+ package struct TrueChild: StatefulRule, AsyncAttribute {
+ @Attribute var info: Info
+
+ package typealias Value = TrueContent
+
+ package mutating func updateValue() {
+ guard case let .trueContent(content) = info.content.storage else {
+ return
+ }
+ value = content
+ }
+ }
+
+ // MARK: - ConditionalContent + FalseChild
+
+ package struct FalseChild: StatefulRule, AsyncAttribute {
+ @Attribute var info: Info
+
+ package typealias Value = FalseContent
+
+ package mutating func updateValue() {
+ guard case let .falseContent(content) = info.content.storage else {
+ return
+ }
+ value = content
+ }
+ }
+}
+
+// MARK: - ConditionalContentProvider
+
+package protocol ConditionalContentProvider {
+ associatedtype TrueContent
+ associatedtype FalseContent
+ associatedtype Inputs
+ associatedtype Outputs
+ var inputs: Inputs { get }
+ var outputs: Outputs { get }
+ func detachOutputs()
+ func attachOutputs(to: Outputs)
+ func makeChildInputs() -> Inputs
+ func makeTrueOutputs(child: Attribute, inputs: Inputs) -> Outputs
+ func makeFalseOutputs(child: Attribute, inputs: Inputs) -> Outputs
}
diff --git a/Sources/OpenSwiftUICore/View/ViewBuilder.swift b/Sources/OpenSwiftUICore/View/ViewBuilder.swift
index 94585e750..91075f92f 100644
--- a/Sources/OpenSwiftUICore/View/ViewBuilder.swift
+++ b/Sources/OpenSwiftUICore/View/ViewBuilder.swift
@@ -2,16 +2,41 @@
// ViewBuilder.swift
// OpenSwiftUICore
//
-// Audited for iOS 18.0
+// Audited for iOS 18.2
// Status: Complete
+/// A custom parameter attribute that constructs views from closures.
+///
+/// You typically use ``ViewBuilder`` as a parameter attribute for child
+/// view-producing closure parameters, allowing those closures to provide
+/// multiple child views. For example, the following `contextMenu` function
+/// accepts a closure that produces one or more views via the view builder.
+///
+/// func contextMenu(
+/// @ViewBuilder menuItems: () -> MenuItems
+/// ) -> some View
+///
+/// Clients of this function can use multiple-statement closures to provide
+/// several child views, as shown in the following example:
+///
+/// myView.contextMenu {
+/// Text("Cut")
+/// Text("Copy")
+/// Text("Paste")
+/// if isSymbol {
+/// Text("Jump to Definition")
+/// }
+/// }
+///
@resultBuilder
public struct ViewBuilder {
+ /// Builds an expression within the builder.
@_alwaysEmitIntoClient
public static func buildExpression(_ content: Content) -> Content where Content: View {
content
}
-
+
+ /// Rejects incompatible expressions within the builder.
@available(*, unavailable, message: "this expression does not conform to 'View'")
@_disfavoredOverload
@_alwaysEmitIntoClient
@@ -19,11 +44,16 @@ public struct ViewBuilder {
fatalError()
}
+ /// Builds an empty view from a block containing no statements.
@_alwaysEmitIntoClient
public static func buildBlock() -> EmptyView {
EmptyView()
}
+ /// Passes a single view written as a child view through unmodified.
+ ///
+ /// An example of a single view written as a child view is
+ /// `{ Text("Hello") }`.
@_alwaysEmitIntoClient
public static func buildBlock(_ content: Content) -> Content where Content: View {
content
@@ -40,16 +70,22 @@ public struct ViewBuilder {
extension ViewBuilder: Sendable {}
extension ViewBuilder {
+ /// Produces an optional view for conditional statements in multi-statement
+ /// closures that's only visible when the condition evaluates to true.
@_alwaysEmitIntoClient
public static func buildIf(_ content: Content?) -> Content? where Content: View {
content
}
+ /// Produces content for a conditional statement in a multi-statement closure
+ /// when the condition is true.
@_alwaysEmitIntoClient
public static func buildEither(first: TrueContent) -> _ConditionalContent where TrueContent: View, FalseContent: View {
.init(storage: .trueContent(first))
}
+ /// Produces content for a conditional statement in a multi-statement closure
+ /// when the condition is false.
@_alwaysEmitIntoClient
public static func buildEither(second: FalseContent) -> _ConditionalContent where TrueContent: View, FalseContent: View {
.init(storage: .falseContent(second))
@@ -57,8 +93,10 @@ extension ViewBuilder {
}
extension ViewBuilder {
+ /// Processes view content for a conditional compiler-control
+ /// statement that performs an availability check.
@_alwaysEmitIntoClient
- public static func buildLimitedAvailability(_ content: some View) -> AnyView {
+ public static func buildLimitedAvailability(_ content: Content) -> AnyView where Content: View {
.init(content)
}
}
diff --git a/Tests/OpenSwiftUICoreTests/Runtime/ConditionalMetadataTests.swift b/Tests/OpenSwiftUICoreTests/Runtime/ConditionalMetadataTests.swift
index 556c7885c..f0316a98d 100644
--- a/Tests/OpenSwiftUICoreTests/Runtime/ConditionalMetadataTests.swift
+++ b/Tests/OpenSwiftUICoreTests/Runtime/ConditionalMetadataTests.swift
@@ -20,6 +20,7 @@ extension TestProtocolDescriptor: ConditionalProtocolDescriptor {
@Suite(.enabled(if: swiftToolchainSupported), .serialized)
struct ConditionalMetadataTests {
+ struct EmptyP: TestProtocol {}
struct P1: TestProtocol {}
struct P2: TestProtocol {}
@@ -53,7 +54,7 @@ struct ConditionalMetadataTests {
}
@Test
- func conditionalTypeDescriptorCaching() throws {
+ func conditionalTypeDescriptorCaching() {
struct P3: TestProtocol {}
let firstMetadata = Optional.makeConditionalMetadata(TestProtocolDescriptor.self)
@@ -62,4 +63,45 @@ struct ConditionalMetadataTests {
let secondMetadata = Optional.makeConditionalMetadata(TestProtocolDescriptor.self)
#expect(firstMetadata.ids != secondMetadata.ids)
}
+
+ #if OPENSWIFTUI_SUPPORT_2024_API
+ @Test
+ func childInfo() throws {
+ // optional
+ let optionalMetadata = Optional.makeConditionalMetadata(TestProtocolDescriptor.self)
+ let value: P1? = P1()
+ withUnsafePointer(to: value) { ptr in
+ let (type, id) = optionalMetadata.childInfo(ptr: ptr, emptyType: EmptyP.self)
+ #expect(type == P1.self)
+ #expect(id != nil)
+ }
+ let nilP: P1? = nil
+ withUnsafePointer(to: nilP) { ptr in
+ let (type, id) = optionalMetadata.childInfo(ptr: ptr, emptyType: EmptyP.self)
+ #expect(type == EmptyP.self)
+ #expect(id != nil)
+ }
+
+ // either
+ let eitherMetadata = ConditionalMetadata(ConditionalTypeDescriptor(EitherType.self))
+ let trueValue = EitherType(__storage: .trueContent(P1()))
+ withUnsafePointer(to: trueValue) { ptr in
+ let (type, id) = eitherMetadata.childInfo(ptr: ptr, emptyType: EmptyP.self)
+ #expect(type == P1.self)
+ #expect(id != nil)
+ }
+ let falseValue = EitherType(__storage: .falseContent(P2()))
+ withUnsafePointer(to: falseValue) { ptr in
+ let (type, id) = eitherMetadata.childInfo(ptr: ptr, emptyType: EmptyP.self)
+ #expect(type == P2.self)
+ #expect(id != nil)
+ }
+ let otherValue = 0
+ withUnsafePointer(to: otherValue) { ptr in
+ let (type, id) = eitherMetadata.childInfo(ptr: ptr, emptyType: EmptyP.self)
+ #expect(type == P1.self)
+ #expect(id != nil)
+ }
+ }
+ #endif
}