|
| 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 | +} |
0 commit comments