Skip to content

Commit ba1069a

Browse files
authored
Implement EnvironmentalModifier (#493)
1 parent 8b68a4e commit ba1069a

File tree

4 files changed

+152
-17
lines changed

4 files changed

+152
-17
lines changed

Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,11 @@ extension ViewModifier {
161161
inputs: inout _GraphInputs,
162162
fields: DynamicPropertyCache.Fields
163163
) -> (_GraphValue<Body>, _DynamicPropertyBuffer?) {
164-
let kind = Metadata(Self.self).kind
165-
switch kind {
166-
case .struct, .enum, .optional, .tuple:
167-
return ModifierBodyAccessor().makeBody(container: modifier, inputs: &inputs, fields: fields)
168-
default:
164+
guard Metadata(Self.self).isValueType else {
169165
preconditionFailure("view modifiers must be value types: \(Self.self)")
170166
}
167+
let accessor = ModifierBodyAccessor<Self>()
168+
return accessor.makeBody(container: modifier, inputs: &inputs, fields: fields)
171169
}
172170
}
173171

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//
2+
// EnvironmentalModifier.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
// ID: A1B6966B83442495FADFE75F475ECBE2 (SwiftUICore)
8+
9+
import OpenAttributeGraphShims
10+
11+
// MARK: - EnvironmentalModifier
12+
13+
/// A modifier that must resolve to a concrete modifier in an environment before
14+
/// use.
15+
@available(OpenSwiftUI_v1_0, *)
16+
public protocol EnvironmentalModifier: ViewModifier where Body == Never {
17+
/// The type of modifier to use after being resolved.
18+
associatedtype ResolvedModifier: ViewModifier
19+
20+
/// Resolve to a concrete modifier in the given `environment`.
21+
func resolve(in environment: EnvironmentValues) -> ResolvedModifier
22+
23+
@available(OpenSwiftUI_v3_0, *)
24+
static var _requiresMainThread: Bool { get }
25+
26+
@available(OpenSwiftUI_v5_0, *)
27+
static var _tracksEnvironmentDependencies: Bool { get }
28+
}
29+
30+
// MARK: - EnvironmentalModifier Default Implementations
31+
32+
@available(OpenSwiftUI_v1_0, *)
33+
extension EnvironmentalModifier {
34+
nonisolated public static func _makeView(
35+
modifier: _GraphValue<Self>,
36+
inputs: _ViewInputs,
37+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
38+
) -> _ViewOutputs {
39+
var inputs = inputs
40+
let fields = DynamicPropertyCache.fields(of: Self.self)
41+
let (resolvedModifier, buffer) = makeResolvedModifier(modifier: modifier, inputs: &inputs.base, fields: fields)
42+
let outputs = ResolvedModifier.makeDebuggableView(
43+
modifier: resolvedModifier,
44+
inputs: inputs,
45+
body: body
46+
)
47+
if let buffer {
48+
buffer.traceMountedProperties(to: modifier, fields: fields)
49+
}
50+
return outputs
51+
}
52+
53+
nonisolated public static func _makeViewList(
54+
modifier: _GraphValue<Self>,
55+
inputs: _ViewListInputs,
56+
body: @escaping (_Graph, _ViewListInputs) -> _ViewListOutputs
57+
) -> _ViewListOutputs {
58+
var inputs = inputs
59+
let fields = DynamicPropertyCache.fields(of: Self.self)
60+
let (resolvedModifier, buffer) = makeResolvedModifier(modifier: modifier, inputs: &inputs.base, fields: fields)
61+
let outputs = ResolvedModifier.makeDebuggableViewList(
62+
modifier: resolvedModifier,
63+
inputs: inputs,
64+
body: body
65+
)
66+
if let buffer {
67+
buffer.traceMountedProperties(to: modifier, fields: fields)
68+
}
69+
return outputs
70+
}
71+
72+
nonisolated private static func makeResolvedModifier(
73+
modifier: _GraphValue<Self>,
74+
inputs: inout _GraphInputs,
75+
fields: DynamicPropertyCache.Fields
76+
) -> (_GraphValue<ResolvedModifier>, _DynamicPropertyBuffer?) {
77+
guard Metadata(Self.self).isValueType else {
78+
preconditionFailure("Environmental modifiers must be value types: \(Self.self)")
79+
}
80+
var fields = fields
81+
if !fields.behaviors.contains(.requiresMainThread), !_requiresMainThread {
82+
fields.behaviors.formUnion(
83+
!fields.behaviors.contains(.allowsAsync) && isLinkedOnOrAfter(.v4) ? .allowsAsync : []
84+
)
85+
}
86+
let accessor = EnvironmentalBodyAccessor<Self>(
87+
environment: inputs.environment,
88+
tracksDependencies: _tracksEnvironmentDependencies
89+
)
90+
return accessor.makeBody(container: modifier, inputs: &inputs, fields: fields)
91+
}
92+
93+
@available(OpenSwiftUI_v3_0, *)
94+
public static var _requiresMainThread: Bool {
95+
true
96+
}
97+
98+
@available(OpenSwiftUI_v5_0, *)
99+
public static var _tracksEnvironmentDependencies: Bool {
100+
true
101+
}
102+
}
103+
104+
// MARK: - EnvironmentalBodyAccessor
105+
106+
private struct EnvironmentalBodyAccessor<V>: BodyAccessor where V: EnvironmentalModifier {
107+
typealias Container = V
108+
109+
typealias Body = V.ResolvedModifier
110+
111+
@Attribute
112+
var environment: EnvironmentValues
113+
let tracker: PropertyList.Tracker
114+
let tracksDependencies: Bool
115+
116+
init(
117+
environment: Attribute<EnvironmentValues>,
118+
tracksDependencies: Bool
119+
) {
120+
self._environment = environment
121+
self.tracker = .init()
122+
self.tracksDependencies = tracksDependencies
123+
}
124+
125+
func updateBody(of container: V, changed: Bool) {
126+
let (environment, environmentChanged) = $environment.changedValue()
127+
guard changed ||
128+
(
129+
environmentChanged && (
130+
!tracksDependencies ||
131+
tracker.hasDifferentUsedValues(environment.plist)
132+
)
133+
)
134+
else {
135+
return
136+
}
137+
tracker.reset()
138+
let newEnvironment = EnvironmentValues(environment.plist, tracker: tracker)
139+
setBody {
140+
container.resolve(in: newEnvironment)
141+
}
142+
}
143+
}

Sources/OpenSwiftUICore/View/CustomView.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,11 @@ extension View {
5151
inputs: inout _GraphInputs,
5252
fields: DynamicPropertyCache.Fields
5353
) -> (_GraphValue<Body>, _DynamicPropertyBuffer?) {
54-
let kind = Metadata(Self.self).kind
55-
switch kind {
56-
case .struct, .enum, .optional, .tuple:
57-
let accessor = ViewBodyAccessor<Self>()
58-
return accessor.makeBody(container: view, inputs: &inputs, fields: fields)
59-
default:
54+
guard Metadata(Self.self).isValueType else {
6055
preconditionFailure("views must be value types (either a struct or an enum); \(Self.self) is a class.")
6156
}
57+
let accessor = ViewBodyAccessor<Self>()
58+
return accessor.makeBody(container: view, inputs: &inputs, fields: fields)
6259
}
6360
}
6461

Sources/OpenSwiftUICore/View/VariadicView.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,11 @@ extension _VariadicView.ViewRoot {
348348
inputs: inout _GraphInputs,
349349
fields: DynamicPropertyCache.Fields
350350
) -> (_GraphValue<Body>, _DynamicPropertyBuffer?) {
351-
let kind = Metadata(Self.self).kind
352-
switch kind {
353-
case .struct, .enum, .optional, .tuple:
354-
let accessor = ViewRootBodyAccessor<Self>(list: list, contentSubgraph: .current!)
355-
return accessor.makeBody(container: root, inputs: &inputs, fields: fields)
356-
default:
351+
guard Metadata(Self.self).isValueType else {
357352
preconditionFailure("views root must be value types (either a struct or an enum); \(Self.self) is a class.")
358353
}
354+
let accessor = ViewRootBodyAccessor<Self>(list: list, contentSubgraph: .current!)
355+
return accessor.makeBody(container: root, inputs: &inputs, fields: fields)
359356
}
360357
}
361358

0 commit comments

Comments
 (0)