Add support for conditional and optional views

This commit is contained in:
david-swift 2024-08-06 11:32:06 +02:00
parent 05617d2966
commit 1c0d491b02
8 changed files with 95 additions and 8 deletions

View File

@ -26,9 +26,7 @@ extension Array: AnyView where Element == AnyView {
} else {
var modified = self
for (index, view) in modified.enumerated() {
for modifier in modifiers {
modified[safe: index] = modifier(view)
}
modified[safe: index] = view.getModified(modifiers: modifiers, type: type)
}
return Data.WrapperType { modified }
}

View File

@ -15,11 +15,14 @@ public protocol AnyView {
extension AnyView {
func getModified(modifiers: [(AnyView) -> AnyView]) -> AnyView {
func getModified<Data>(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> AnyView where Data: ViewRenderData {
var modified: AnyView = self
for modifier in modifiers {
modified = modifier(modified)
}
if let dummy = modified as? DummyEitherView {
modified = type.EitherViewType(dummy.condition) { dummy.view1 ?? [] } else: { dummy.view2 ?? [] }
}
return modified
}
@ -55,19 +58,19 @@ extension AnyView {
/// - Parameter modifiers: Modify views before being updated.
/// - Returns: The widget.
func widget<Data>(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> Widget where Data: ViewRenderData {
let modified = getModified(modifiers: modifiers)
let modified = getModified(modifiers: modifiers, type: type)
if let peer = modified as? Widget {
return peer
}
if let array = modified as? Body {
return Data.WrapperType { array }
}
return Data.WrapperType { viewContent.map { $0.getModified(modifiers: modifiers) } }
return Data.WrapperType { viewContent.map { $0.getModified(modifiers: modifiers, type: type) } }
}
/// Whether the view can be rendered in a certain environment.
func renderable<Data>(type: Data.Type, modifiers: [(AnyView) -> AnyView]) -> Bool where Data: ViewRenderData {
let result = getModified(modifiers: modifiers)
let result = getModified(modifiers: modifiers, type: type)
return result as? Data.WidgetType != nil
|| result as? SimpleView != nil
|| result as? View != nil

View File

@ -0,0 +1,22 @@
//
// EitherView.swift
// Meta
//
// Created by david-swift on 06.08.24.
//
/// A view building conditional bodies.
public protocol EitherView: AnyView {
/// Initialize the either view.
/// - Parameters:
/// - condition: Whether the first view is visible-
/// - view1: The first view, visible if true.
/// - view2: The second view, visible if false.
init(
_ condition: Bool,
@ViewBuilder view1: () -> Body,
@ViewBuilder else view2: () -> Body
)
}

View File

@ -53,6 +53,27 @@ public enum ViewBuilder {
component
}
/// Enables support for `if` statements without an `else`.
/// - Parameter component: An optional component.
/// - Returns: A nonoptional component.
public static func buildOptional(_ component: Component?) -> Component {
.element(DummyEitherView(condition: component != nil, view1: buildFinalResult(component ?? .element([]))))
}
/// Enables support for `if`-`else` and `switch` statements.
/// - Parameter component: A component.
/// - Returns: The component.
public static func buildEither(first component: Component) -> Component {
.element(DummyEitherView(condition: true, view1: buildFinalResult(component)))
}
/// Enables support for `if`-`else` and `switch` statements.
/// - Parameter component: A component.
/// - Returns: The component.
public static func buildEither(second component: Component) -> Component {
.element(DummyEitherView(condition: false, view2: buildFinalResult(component)))
}
/// Convert a component to an array of elements.
/// - Parameter component: The component to convert.
/// - Returns: The generated array of elements.

View File

@ -12,5 +12,7 @@ public protocol ViewRenderData {
associatedtype WidgetType
/// The wrapper widget.
associatedtype WrapperType: Wrapper
/// The type replacing dummy either views.
associatedtype EitherViewType: EitherView
}

View File

@ -0,0 +1,21 @@
//
// DummyEitherView.swift
// Meta
//
// Created by david-swift on 06.08.24.
//
/// A dummy either view. This will be replaced by the platform-specific either view.
struct DummyEitherView: SimpleView {
/// Whether to present the first view.
var condition: Bool
/// The first view.
var view1: Body?
/// The second view.
var view2: Body?
/// By default, show the first view.
var view: Body { view1 ?? view2 ?? [] }
}

View File

@ -33,9 +33,14 @@ struct DemoView: View {
@State private var model = TestModel()
var app: any AppStorage
let condition = false
var view: Body {
Backend1.TestWidget1()
if condition {
Backend1.TestWidget1()
} else {
Backend1.TestWidget3()
}
Backend1.Button(model.test) {
Task {
app.addSceneElement("main")

View File

@ -131,10 +131,25 @@ public enum Backend1 {
}
public struct EitherView: BackendWidget, Meta.EitherView {
public init(_ condition: Bool, view1: () -> Body, else view2: () -> Body) {
}
public func container<Data>(modifiers: [(any AnyView) -> any AnyView], type: Data.Type) -> ViewStorage where Data : ViewRenderData {
.init(nil)
}
public func update<Data>(_ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, type: Data.Type) where Data : ViewRenderData {
}
}
public struct MainViewRenderData: ViewRenderData {
public typealias WidgetType = BackendWidget
public typealias WrapperType = Wrapper
public typealias EitherViewType = EitherView
}