Skip to content

Commit 86b5d4a

Browse files
committed
Update StaticIf implementation
1 parent 3e8a4f4 commit 86b5d4a

File tree

3 files changed

+314
-35
lines changed

3 files changed

+314
-35
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
//
2+
// StaticIf.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
import OpenSwiftUICore
9+
10+
/// A container view that conditionally renders one of two views based on a `ViewInputPredicate`.
11+
///
12+
/// `StaticIf` makes view selection decisions based on the evaluation of a `ViewInputPredicate`
13+
/// against view inputs. Unlike regular conditional logic, the `ViewInputPredicate` is
14+
/// specifically designed to only depend on view input parameters rather than arbitrary
15+
/// runtime state.
16+
///
17+
/// This view facilitates optimizations in the view hierarchy by allowing the framework
18+
/// to make view structure decisions based on stable view properties rather than
19+
/// arbitrary runtime values.
20+
///
21+
/// Use this when you need conditional views based on input parameters like
22+
/// environment values, interface idioms, or view context properties.
23+
package struct StaticIf<Predicate, TrueBody, FalseBody> where Predicate: ViewInputPredicate {
24+
package var trueBody: TrueBody
25+
package var falseBody: FalseBody
26+
}
27+
28+
extension StaticIf: PrimitiveView, View where TrueBody: View, FalseBody: View {
29+
/// Creates a new instance that statically selects between two views based on a predicate type.
30+
///
31+
/// - Parameters:
32+
/// - predicate: The predicate type used to evaluate against view inputs.
33+
/// - then: A closure that returns the view to display when the predicate evaluates to `true`.
34+
/// - else: A closure that returns the view to display when the predicate evaluates to `false`.
35+
package init(_ predicate: Predicate.Type, then: () -> TrueBody, else: () -> FalseBody) {
36+
trueBody = then()
37+
falseBody = `else`()
38+
}
39+
40+
/// Creates a new instance that statically selects between two views based on a predicate instance.
41+
///
42+
/// - Parameters:
43+
/// - predicate: The predicate instance used to evaluate against view inputs.
44+
/// - then: A closure that returns the view to display when the predicate evaluates to `true`.
45+
/// - else: A closure that returns the view to display when the predicate evaluates to `false`.
46+
package init(_ predicate: Predicate, then: () -> TrueBody, else: () -> FalseBody) {
47+
trueBody = then()
48+
falseBody = `else`()
49+
}
50+
51+
/// Creates a new instance that statically selects between two views based on an interface idiom.
52+
///
53+
/// - Parameters:
54+
/// - idiom: The interface idiom to evaluate against the current environment.
55+
/// - then: A closure that returns the view to display when the current device matches the specified idiom.
56+
/// - else: A closure that returns the view to display when the current device doesn't match the specified idiom.
57+
init<I>(idiom: I, then: () -> TrueBody, else: () -> FalseBody) where Predicate == InterfaceIdiomPredicate<I>, I: InterfaceIdiom {
58+
trueBody = then()
59+
falseBody = `else`()
60+
}
61+
62+
/// Creates a new instance that statically selects between two views based on a style context.
63+
///
64+
/// - Parameters:
65+
/// - context: The style context to evaluate against the current environment.
66+
/// - then: A closure that returns the view to display when the current context accepts the specified style.
67+
/// - else: A closure that returns the view to display when the current context doesn't accept the specified style.
68+
package init<Context>(in context: Context, then: () -> TrueBody, else: () -> FalseBody) where Predicate == StyleContextAcceptsPredicate<Context>, Context: StyleContext {
69+
trueBody = then()
70+
falseBody = `else`()
71+
}
72+
73+
nonisolated package static func _makeView(view: _GraphValue<Self>, inputs: _ViewInputs) -> _ViewOutputs {
74+
if Predicate.evaluate(inputs: inputs.base) {
75+
TrueBody._makeView(view: view[offset: { .of(&$0.trueBody) }], inputs: inputs)
76+
} else {
77+
FalseBody._makeView(view: view[offset: { .of(&$0.falseBody) }], inputs: inputs)
78+
}
79+
}
80+
81+
nonisolated package static func _makeViewList(view: _GraphValue<Self>, inputs: _ViewListInputs) -> _ViewListOutputs {
82+
if Predicate.evaluate(inputs: inputs.base) {
83+
TrueBody._makeViewList(view: view[offset: { .of(&$0.trueBody) }], inputs: inputs)
84+
} else {
85+
FalseBody._makeViewList(view: view[offset: { .of(&$0.falseBody) }], inputs: inputs)
86+
}
87+
}
88+
89+
nonisolated package static func _viewListCount(inputs: _ViewListCountInputs) -> Int? {
90+
if Predicate.evaluate(inputs: inputs.base) {
91+
TrueBody._viewListCount(inputs: inputs)
92+
} else {
93+
FalseBody._viewListCount(inputs: inputs)
94+
}
95+
}
96+
}
97+
98+
extension StaticIf: PrimitiveViewModifier, ViewModifier where TrueBody: ViewModifier, FalseBody: ViewModifier {
99+
/// Creates a new modifier that statically applies one of two modifiers based on a predicate type.
100+
///
101+
/// - Parameters:
102+
/// - predicate: The predicate type used to evaluate against view inputs.
103+
/// - then: The modifier to apply when the predicate evaluates to `true`.
104+
/// - else: The modifier to apply when the predicate evaluates to `false`.
105+
package init(_ predicate: Predicate.Type, then: TrueBody, else: FalseBody) {
106+
trueBody = then
107+
falseBody = `else`
108+
}
109+
110+
/// Creates a new modifier that statically applies a modifier when the predicate is true,
111+
/// and applies no modification when false.
112+
///
113+
/// - Parameters:
114+
/// - predicate: The predicate type used to evaluate against view inputs.
115+
/// - then: The modifier to apply when the predicate evaluates to `true`.
116+
package init(_ predicate: Predicate.Type, then: TrueBody) where FalseBody == EmptyModifier {
117+
trueBody = then
118+
falseBody = EmptyModifier()
119+
}
120+
121+
/// Creates a new modifier that statically applies one of two modifiers based on an interface idiom.
122+
///
123+
/// - Parameters:
124+
/// - idiom: The interface idiom to evaluate against the current environment.
125+
/// - then: The modifier to apply when the current device matches the specified idiom.
126+
/// - else: The modifier to apply when the current device doesn't match the specified idiom.
127+
package init<I>(idiom: I, then: TrueBody, else: FalseBody) where Predicate == InterfaceIdiomPredicate<I>, I: InterfaceIdiom {
128+
trueBody = then
129+
falseBody = `else`
130+
}
131+
132+
nonisolated package static func _makeView(modifier: _GraphValue<Self>, inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs) -> _ViewOutputs {
133+
if Predicate.evaluate(inputs: inputs.base) {
134+
TrueBody._makeView(modifier: modifier[offset: { .of(&$0.trueBody) }], inputs: inputs, body: body)
135+
} else {
136+
FalseBody._makeView(modifier: modifier[offset: { .of(&$0.falseBody) }], inputs: inputs, body: body)
137+
}
138+
}
139+
140+
nonisolated package static func _makeViewList(modifier: _GraphValue<Self>, inputs: _ViewListInputs, body: @escaping (_Graph, _ViewListInputs) -> _ViewListOutputs) -> _ViewListOutputs {
141+
if Predicate.evaluate(inputs: inputs.base) {
142+
TrueBody._makeViewList(modifier: modifier[offset: { .of(&$0.trueBody) }], inputs: inputs, body: body)
143+
} else {
144+
FalseBody._makeViewList(modifier: modifier[offset: { .of(&$0.falseBody) }], inputs: inputs, body: body)
145+
}
146+
}
147+
148+
nonisolated package static func _viewListCount(inputs: _ViewListCountInputs, body: (_ViewListCountInputs) -> Int?) -> Int? {
149+
if Predicate.evaluate(inputs: inputs.base) {
150+
TrueBody._viewListCount(inputs: inputs, body: body)
151+
} else {
152+
FalseBody._viewListCount(inputs: inputs, body: body)
153+
}
154+
}
155+
}
156+
157+
extension View {
158+
/// Conditionally applies a modifier to a view based on a predicate type.
159+
///
160+
/// Use this method when you want to apply a specific modifier only when the predicate
161+
/// evaluates to true based on view inputs, and leave the view unmodified otherwise.
162+
///
163+
/// - Parameters:
164+
/// - predicate: The predicate type used to evaluate against view inputs.
165+
/// - trueModifier: A closure that takes the current view and returns a modified view when the predicate evaluates to true.
166+
/// - Returns: Either the modified view or the original view, depending on the predicate result.
167+
package func staticIf<Predicate, TrueBody>(
168+
_ predicate: Predicate.Type,
169+
trueModifier: (Self) -> TrueBody
170+
) -> some View where Predicate: ViewInputPredicate, TrueBody: View {
171+
StaticIf(predicate) {
172+
trueModifier(self)
173+
} else: {
174+
self
175+
}
176+
}
177+
178+
/// Conditionally applies one of two modifiers to a view based on a predicate type.
179+
///
180+
/// Use this method when you want to apply different modifiers based on the evaluation
181+
/// of a predicate against view inputs.
182+
///
183+
/// - Parameters:
184+
/// - predicate: The predicate type used to evaluate against view inputs.
185+
/// - trueModifier: A closure that takes the current view and returns a modified view when the predicate evaluates to true.
186+
/// - falseModifier: A closure that takes the current view and returns a modified view when the predicate evaluates to false.
187+
/// - Returns: A view that's been modified by either the true modifier or false modifier.
188+
package func staticIf<Predicate, TrueBody, FalseBody>(
189+
_ predicate: Predicate.Type,
190+
trueModifier: (Self) -> TrueBody,
191+
falseModifier: (Self) -> FalseBody
192+
) -> some View where Predicate: ViewInputPredicate, TrueBody: View, FalseBody: View {
193+
StaticIf(predicate) {
194+
trueModifier(self)
195+
} else: {
196+
falseModifier(self)
197+
}
198+
}
199+
200+
/// Conditionally applies one of two modifiers to a view based on a style context.
201+
///
202+
/// Use this method when you want to apply different modifiers based on whether
203+
/// the current style context accepts a specific style.
204+
///
205+
/// - Parameters:
206+
/// - context: The style context to evaluate against the current environment.
207+
/// - trueModifier: A closure that takes the current view and returns a modified view when the context accepts the style.
208+
/// - falseModifier: A closure that takes the current view and returns a modified view when the context doesn't accept the style.
209+
/// - Returns: A view that's been modified by either the true modifier or false modifier based on the context.
210+
package func staticIf<Context, TrueBody, FalseBody>(
211+
context: Context,
212+
trueModifier: (Self) -> TrueBody,
213+
falseModifier: (Self) -> FalseBody
214+
) -> some View where Context: StyleContext, TrueBody: View, FalseBody: View {
215+
staticIf(
216+
StyleContextAcceptsPredicate<Context>.self,
217+
trueModifier: trueModifier,
218+
falseModifier: falseModifier
219+
)
220+
}
221+
}
222+
223+
extension ViewModifier {
224+
/// Creates a conditional modifier that only applies when a predicate evaluates to true.
225+
///
226+
/// Use this method to create a modifier that is only applied when certain view input
227+
/// conditions are met. If the predicate evaluates to false, an empty modifier is applied instead.
228+
///
229+
/// - Parameter predicate: The predicate type used to evaluate against view inputs.
230+
/// - Returns: A conditional modifier that applies the current modifier only when the predicate is true.
231+
package func requiring<Predicate>(_ predicate: Predicate.Type) -> StaticIf<Predicate, Self, EmptyModifier> where Predicate: ViewInputPredicate {
232+
StaticIf(predicate, then: self)
233+
}
234+
}

Sources/OpenSwiftUICore/Modifier/StaticIf.swift

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// StyleContext.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: WIP
7+
// ID: 2EF43D8D991A83294E93848563DD541B (SwiftUICore)
8+
9+
// MARK: - StyleContext
10+
11+
package protocol StyleContext {
12+
static func accepts<Q>(_: Q.Type, at: Int) -> Bool
13+
14+
static func acceptsAny<each Q>(_ queries: repeat (each Q).Type) -> Bool where repeat each Q: StyleContext
15+
16+
static func visitStyle<V>(_ visitor: inout V) where V: StyleContextVisitor
17+
}
18+
19+
extension StyleContext {
20+
package static func visitStyle<V>(_ visitor: inout V) where V: StyleContextVisitor {
21+
visitor.visit(Self.self)
22+
}
23+
24+
package static func accepts<Q>(_ query: Q.Type, at: Int) -> Bool {
25+
Self.self == query
26+
}
27+
28+
package static func acceptsAny<each Q>(_ queries: repeat (each Q).Type) -> Bool where repeat each Q: StyleContext {
29+
var visitor = QueryVisitor<Self>()
30+
for query in repeat each queries {
31+
query.visitStyle(&visitor)
32+
guard !visitor.accepts else {
33+
return true
34+
}
35+
}
36+
return visitor.accepts
37+
}
38+
39+
package static func acceptsTop<Q>(_ query: Q.Type) -> Bool {
40+
accepts(query, at: 0)
41+
}
42+
}
43+
44+
// TODO
45+
46+
// MARK: - StyleContextAcceptsPredicate [WIP]
47+
48+
package struct StyleContextAcceptsPredicate<Query>: ViewInputPredicate {
49+
package init() {}
50+
51+
package static func evaluate(inputs: _GraphInputs) -> Bool {
52+
preconditionFailure("TODO")
53+
}
54+
}
55+
56+
package struct StyleContextAcceptsAnyPredicate<each Query>: ViewInputPredicate where repeat each Query: StyleContext {
57+
package init() {}
58+
59+
package static func evaluate(inputs: _GraphInputs) -> Bool {
60+
preconditionFailure("TODO")
61+
}
62+
}
63+
64+
// TODO
65+
66+
// MARK: - StyleContextVisitor
67+
68+
package protocol StyleContextVisitor {
69+
mutating func visit<C>(_ context: C.Type) where C: StyleContext
70+
}
71+
72+
// MARK: - QueryVisitor
73+
74+
private struct QueryVisitor<Context>: StyleContextVisitor where Context: StyleContext {
75+
var accepts: Bool = false
76+
77+
mutating func visit<C>(_ context: C.Type) where C : StyleContext {
78+
accepts = accepts ? true : Context.self == context
79+
}
80+
}

0 commit comments

Comments
 (0)