diff --git a/Sources/Slipstream/Documentation.docc/Views/Fundamentals/Fundamentals.md b/Sources/Slipstream/Documentation.docc/Views/Fundamentals/Fundamentals.md index 01f3096..38aa992 100644 --- a/Sources/Slipstream/Documentation.docc/Views/Fundamentals/Fundamentals.md +++ b/Sources/Slipstream/Documentation.docc/Views/Fundamentals/Fundamentals.md @@ -17,3 +17,7 @@ conforms to the ``View`` protocol can act as a view in your website. ### Modifying a view - ``ViewModifier`` + +### Supporting view types + +- ``EmptyView`` diff --git a/Sources/Slipstream/Views/Fundamentals/EmptyView.swift b/Sources/Slipstream/Views/Fundamentals/EmptyView.swift new file mode 100644 index 0000000..8ff833b --- /dev/null +++ b/Sources/Slipstream/Views/Fundamentals/EmptyView.swift @@ -0,0 +1,17 @@ +/// A view that doesn't contain any content. +/// +/// You will rarely, if ever, need to create an `EmptyView` directly. Instead, +/// `EmptyView` represents the absence of a view. +/// +/// Slipstream uses `EmptyView` in situations where a Slipstream view type defines one +/// or more child views with generic parameters, and allows the child views to +/// be absent. When absent, the child view's type in the generic type parameter +/// is `EmptyView`. +@available(iOS 17.0, macOS 14.0, *) +public struct EmptyView: View { + public typealias Content = Never + + /// Creates an empty view. + public init() { + } +} diff --git a/Sources/Slipstream/Views/Fundamentals/View.swift b/Sources/Slipstream/Views/Fundamentals/View.swift index 1d60e6c..222e2b9 100644 --- a/Sources/Slipstream/Views/Fundamentals/View.swift +++ b/Sources/Slipstream/Views/Fundamentals/View.swift @@ -115,6 +115,10 @@ extension View where Content == Never { public var body: Never { fatalError("body should never be executed when a view has declared its Content as Never") } + + public func render(_ container: Element, environment: EnvironmentValues) throws { + // Do nothing. + } } @_documentation(visibility: private) diff --git a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ArrayView.swift b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ArrayView.swift index 1f1759a..58dc0c9 100644 --- a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ArrayView.swift +++ b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ArrayView.swift @@ -2,6 +2,7 @@ import SwiftSoup /// A View created from a for loop of View values. @_documentation(visibility: private) +@available(iOS 17.0, macOS 14.0, *) public struct ArrayView: View { public typealias Content = Never diff --git a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ConditionalView.swift b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ConditionalView.swift index c6a2e5d..12cb6bd 100644 --- a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ConditionalView.swift +++ b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ConditionalView.swift @@ -2,6 +2,7 @@ import SwiftSoup /// A View created from an if/else conditional of View values. @_documentation(visibility: private) +@available(iOS 17.0, macOS 14.0, *) public struct ConditionalView: View { enum Condition { case isTrue(T) diff --git a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/TupleView.swift b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/TupleView.swift index 23be31d..eaf4598 100644 --- a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/TupleView.swift +++ b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/TupleView.swift @@ -2,6 +2,7 @@ import SwiftSoup /// A View created from a swift tuple of View values. @_documentation(visibility: private) +@available(iOS 17.0, macOS 14.0, *) public struct TupleView: View { public typealias Content = Never diff --git a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ViewBuilder.swift b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ViewBuilder.swift index c1e0b9d..90fff52 100644 --- a/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ViewBuilder.swift +++ b/Sources/Slipstream/Views/Fundamentals/ViewBuilder/ViewBuilder.swift @@ -34,6 +34,26 @@ public struct ViewBuilder { return TupleView(value: (repeat each content)) } + /// An if statement. + /// + /// An example: + /// + /// ```swift + /// var body: some View { + /// if true { + /// Text("true") + /// } + /// } + /// ``` + public static func buildOptional(_ component: Content?) -> ConditionalView + where Content: View { + if let component { + return ConditionalView(condition: .isTrue(component)) + } else { + return ConditionalView(condition: .isFalse(EmptyView())) + } + } + /// An if/else statement, when the condition returns true. /// /// An example: diff --git a/Tests/SlipstreamTests/ViewBuilderTests.swift b/Tests/SlipstreamTests/ViewBuilderTests.swift index ca46e21..bcd0007 100644 --- a/Tests/SlipstreamTests/ViewBuilderTests.swift +++ b/Tests/SlipstreamTests/ViewBuilderTests.swift @@ -15,7 +15,7 @@ private struct TupleBlockView: View { } } -private struct ConditionalBlockView: View { +private struct IfElseBlockView: View { let bool: Bool var body: some View { if bool { @@ -26,6 +26,15 @@ private struct ConditionalBlockView: View { } } +private struct IfBlockView: View { + let bool: Bool + var body: some View { + if bool { + Text("true") + } + } +} + private struct ArrayBlockView: View { var body: some View { for i in 1...3 { @@ -43,9 +52,14 @@ struct ViewBuilderTests { try #expect(renderHTML(TupleBlockView()) == "Hello, world!") } - @Test func conditionalBlock() throws { - try #expect(renderHTML(ConditionalBlockView(bool: true)) == "true") - try #expect(renderHTML(ConditionalBlockView(bool: false)) == "false") + @Test func ifElseBlock() throws { + try #expect(renderHTML(IfElseBlockView(bool: true)) == "true") + try #expect(renderHTML(IfElseBlockView(bool: false)) == "false") + } + + @Test func ifBlock() throws { + try #expect(renderHTML(IfBlockView(bool: true)) == "true") + try #expect(renderHTML(IfBlockView(bool: false)) == "") } @Test func arrayBlock() throws {