Add support for environment properties
All checks were successful
Deploy Docs / publish (push) Successful in 50s
SwiftLint / SwiftLint (push) Successful in 4s

This commit is contained in:
david-swift 2024-10-24 13:16:23 +02:00
parent a8ce63a67f
commit ee92f63f86
5 changed files with 152 additions and 2 deletions

View File

@ -0,0 +1,49 @@
//
// Environment.swift
// Meta
//
// Created by david-swift on 23.10.24.
//
/// A property wrapper for properties in a view that should be stored throughout view updates.
@propertyWrapper
public struct Environment<Value>: EnvironmentProtocol {
/// Access the environment value.
public var wrappedValue: Value? {
content.value as? Value
}
/// The value's identifier.
var id: String
/// The content.
let content = EnvironmentContent()
// swiftlint:disable function_default_parameter_at_end
/// Initialize a property representing an environment value in the view.
/// - Parameters:
/// - wrappedValue: The wrapped value.
/// - id: The environment value's identifier.
public init(wrappedValue: Value? = nil, _ id: String) {
self.id = id
}
// swiftlint:enable function_default_parameter_at_end
}
/// An environment property's content.
class EnvironmentContent {
/// The value.
var value: Any?
}
/// The environment property protocol.
protocol EnvironmentProtocol {
/// The content.
var content: EnvironmentContent { get }
/// The identifier.
var id: String { get }
}

View File

@ -32,7 +32,7 @@ extension View {
/// The view's content.
public var viewContent: Body {
[StateWrapper(content: { view }, state: getState())]
[StateWrapper(content: { view }, state: getState(), environment: getEnvironmentVariables())]
}
/// Get the state from the properties.
@ -47,4 +47,16 @@ extension View {
return state
}
/// Get the environment properties.
/// - Returns: The environment properties.
func getEnvironmentVariables() -> [String: any EnvironmentProtocol] {
var environment: [String: any EnvironmentProtocol] = [:]
for property in Mirror(reflecting: self).children {
if let label = property.label, let value = property.value as? any EnvironmentProtocol {
environment[label] = value
}
}
return environment
}
}

View File

@ -0,0 +1,67 @@
//
// StateWrapper.swift
// Meta
//
// Created by david-swift on 09.06.24.
//
/// Assign values to the environment.
///
/// Access the environment in views (``View``) via `@Environment`.
struct DataWrapper: ConvenienceWidget {
/// The content.
var content: Body
/// The identifier for the new environment value.
var label: String
/// The environment value.
var data: Any
/// Get a view storage.
/// - Parameters:
/// - data: Modify views before being updated.
/// - type: The view render data type.
/// - Returns: The view storage.
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
content.storage(data: data.modify { $0.fields[label] = self.data }, type: type)
}
/// Update a view storage.
/// - Parameters:
/// - storage: The view storage.
/// - data: Modify views before being updated.
/// - updateProperties: Whether to update properties.
/// - type: The view render data type.
/// - Returns: The view storage.
func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
content
.updateStorage(
storage,
data: data.modify { $0.fields[label] = self.data },
updateProperties: updateProperties,
type: type
)
}
}
extension AnyView {
/// Assign a value to an environment label.
/// - Parameters:
/// - label: The environment label.
/// - data: The value.
/// - Returns: The view.
public func environment(_ label: String, data: Any) -> AnyView {
DataWrapper(content: [self], label: label, data: data)
}
}

View File

@ -12,6 +12,8 @@ struct StateWrapper: ConvenienceWidget {
var content: () -> Body
/// The state information (from properties with the `State` wrapper).
var state: [String: StateProtocol] = [:]
/// The environment properties.
var environment: [String: any EnvironmentProtocol] = [:]
/// Initialize a `StateWrapper`.
/// - Parameter content: The view content.
@ -23,9 +25,15 @@ struct StateWrapper: ConvenienceWidget {
/// - Parameters:
/// - content: The view content.
/// - state: The state information.
init(content: @escaping () -> Body, state: [String: StateProtocol]) {
/// - environment: The environment properties.
init(
content: @escaping () -> Body,
state: [String: StateProtocol],
environment: [String: any EnvironmentProtocol]
) {
self.content = content
self.state = state
self.environment = environment
}
/// Update a view storage.
@ -51,6 +59,7 @@ struct StateWrapper: ConvenienceWidget {
property.value.content.update = false
}
}
assignEnvironment(data: data)
guard let storage = storage.content[.mainContent]?.first else {
return
}
@ -66,6 +75,7 @@ struct StateWrapper: ConvenienceWidget {
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
assignEnvironment(data: data)
let content = content().storage(data: data, type: type)
let storage = ViewStorage(content.pointer, content: [.mainContent: [content]])
storage.state = state
@ -75,4 +85,12 @@ struct StateWrapper: ConvenienceWidget {
return storage
}
/// Assign an environment value to the environment property.
/// - Parameter data: The widget data.
func assignEnvironment(data: WidgetData) {
for property in environment {
property.value.content.value = data.fields[property.value.id]
}
}
}

View File

@ -23,6 +23,7 @@ struct DemoApp: App {
var scene: Scene {
Backend1.Window("main", spawn: 1) {
DemoView(app: app)
.environment("test", data: 5)
}
}
@ -31,6 +32,8 @@ struct DemoApp: App {
struct DemoView: View {
@State private var model = TestModel()
@Environment("test")
private var test: Int?
var app: any AppStorage
let condition = false
@ -46,6 +49,7 @@ struct DemoView: View {
app.addSceneElement("main")
}
}
.onAppear { print(test ?? 0) }
}
TestView()
testContent