Skip to content

Commit 97ac67b

Browse files
committed
Document Skip Fuse .composeModifier
1 parent 7f03408 commit 97ac67b

File tree

1 file changed

+116
-16
lines changed

1 file changed

+116
-16
lines changed

README.md

Lines changed: 116 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,24 @@ SkipUI enhances all SwiftUI views with a `Compose(context:)` method, allowing yo
102102

103103
Skip Fuse:
104104

105+
```swift
106+
#if os(Android)
107+
ComposeView { ColumnComposer() }
108+
#endif
109+
110+
...
111+
112+
#if SKIP
113+
struct ColumnComposer : ContentComposer {
114+
@Composable func Compose(context: ComposeContext) {
115+
androidx.compose.foundation.layout.Column(modifier: context.modifier) {
116+
Text("Hello from SwiftUI").Compose(context: context.content())
117+
androidx.compose.material3.Text("Hello from Compose")
118+
}
119+
}
120+
}
121+
#endif
122+
```
105123

106124
Skip Lite:
107125

@@ -118,8 +136,6 @@ ComposeView { context in
118136

119137
With `ComposeView` and the `Compose(context:)` function, you can move fluidly between SwiftUI and Compose code. These techniques work not only with standard SwiftUI and Compose components, but with your own custom SwiftUI views and Compose functions as well.
120138

121-
`ComposeView` and the `Compose()` function are only available in Android, so you must guard all uses with the `#if SKIP` compiler directives.
122-
123139
### Additional Considerations
124140

125141
There are additional considerations when integrating SwiftUI into a Compose application that is **not** managed by Skip. SwiftUI relies on its own mechanisms to save and restore `Activity` UI state, such as `@AppStorage` and navigation path bindings. It is not compatible with Android's `Activity` UI state restoration. Use a pattern like the following to exclude SwiftUI from `Activity` state restoration when integrating SwiftUI views:
@@ -136,7 +152,51 @@ This pattern allows SkipUI to take advantage of Compose's UI state mechanisms in
136152

137153
## composeModifier
138154

139-
In addition to `ComposeView` above, SkipUI offers the `composeModifier` SwiftUI modifier. This modifier allows you to apply any Compose modifiers to the underlying Compose view. It takes a block that accepts a single `androidx.compose.ui.Modifier` parameter and returns a `Modifier` as well. For example:
155+
In addition to `ComposeView` above, SkipUI offers the `composeModifier` SwiftUI modifier. This modifier allows you to apply any Compose modifiers to the underlying Compose view.
156+
157+
### Skip Fuse
158+
159+
Using `composeModifier` from Skip Fuse is much like using `ComposeView`:
160+
161+
1. Define a `ContentModifier` in a transpiled `#if SKIP` block.
162+
1. Use `.composeModifier` to apply the `ContentModifier ` to the target SwiftUI view.
163+
164+
Within your `ContentModifier`, apply any SkipUI modifiers to the given target view. This includes the [Material](#material) modifiers we describe below, or the same-named transpiled `composeModifier`, which takes a block that accepts a single `androidx.compose.ui.Modifier` parameter and returns a `Modifier` as well. The following example applies Compose's `imePadding` modifier our SwiftUI on Android:
165+
166+
```swift
167+
import SkipFuseUI
168+
169+
...
170+
171+
VStack {
172+
TextField("Enter username:", text: $username)
173+
#if os(Android)
174+
.composeModifier { IMEPaddingModifier() }
175+
#endif
176+
}
177+
178+
#if SKIP
179+
import androidx.compose.foundation.layout.imePadding
180+
181+
struct IMEPaddingModifier : ContentModifier {
182+
func modify(view: any View) -> any View {
183+
view.composeModifier { $0.imePadding() }
184+
}
185+
}
186+
#endif
187+
```
188+
189+
The `ContentModfiier` protocol consists of a single function:
190+
191+
```swift
192+
public protocol ContentModifier {
193+
func modify(view: any View) -> any View
194+
}
195+
```
196+
197+
### Skip Lite
198+
199+
If you are writing your SwiftUI using Skip Lite, you don't need to define a `ContentModfifier`. You can apply [Material](#material) modifiers or `.composeModifier` directly:
140200

141201
```swift
142202
#if SKIP
@@ -146,17 +206,11 @@ import androidx.compose.foundation.layout.imePadding
146206
...
147207

148208
TextField("Enter username:", text: $username)
149-
.textFieldStyle(.plain)
150209
#if SKIP
151210
.composeModifier { $0.imePadding() }
152211
#endif
153212
```
154213

155-
Like `ComposeView`, the `composeModifier` function is only available in Android, so you must guard all uses with the `#if SKIP` compiler directives.
156-
157-
`composeModifier` is currently only available for Skip Lite transpiled Swift.
158-
{: class="callout info"}
159-
160214
## Material
161215

162216
Under the hood, SkipUI uses Android's Material 3 colors and components. While we expect you to use SwiftUI's built-in color schemes (`.preferredColorScheme`) and modifiers (`.background`, `.foregroundStyle`, `.tint`, and so on) for most UI styling, there are some Android customizations that have no SwiftUI equivalent. Skip therefore adds additional, Android-only API for manipulating Material colors and components.
@@ -184,7 +238,30 @@ internal fun PresentationRootView(context: ComposeContext) {
184238
}
185239
```
186240

187-
Skip also provides the SwiftUI `.material3ColorScheme(_:)` modifier to customize a SwiftUI view hierarchy. The modifier takes the same closure as the `Material3ColorScheme` Kotlin function. It is only available for Android, so you must use it within a `#if SKIP` block. For example:
241+
Skip also provides the SwiftUI `.material3ColorScheme(_:)` modifier to customize a SwiftUI view hierarchy. The modifier takes the same closure as the `Material3ColorScheme` Kotlin function. Apply this modifier using the `.composeModifier` techniques discussed in the previous section. For example:
242+
243+
Skip Fuse:
244+
245+
```swift
246+
MyView()
247+
#if os(Android)
248+
.composeModifier { ColorSchemeModifier() }
249+
#endif
250+
251+
...
252+
253+
#if SKIP
254+
struct ColorSchemeModifier : ContentModifier {
255+
func modify(view: any View) -> any View {
256+
view.material3ColorScheme { colors, isDark in
257+
colors.copy(surface: isDark ? Color.purple.asComposeColor() : Color.yellow.asComposeColor())
258+
}
259+
}
260+
}
261+
#endif
262+
```
263+
264+
Skip Lite:
188265

189266
```swift
190267
MyView()
@@ -203,9 +280,6 @@ Skip's built-in components use the following Material 3 colors, if you'd like to
203280
- `outline`
204281
- `outlineVariant`
205282

206-
The `material3ColorScheme` modifier is currently only available for Skip Lite transpiled Swift.
207-
{: class="callout info"}
208-
209283
### Material Components
210284

211285
In addition to the `.material3ColorScheme` modifier detailed above, Skip includes many other `.material3` modifiers for its underlying Material 3 components. This family of modifiers share a common API pattern:
@@ -215,11 +289,37 @@ In addition to the `.material3ColorScheme` modifier detailed above, Skip include
215289
- The modifiers place your closure into the SwiftUI `Environment`. This means that you can apply the modifier on a root view, and it will affect all subviews. While you may be used to placing navigation and tab bar modifiers on the views *within* the `NavigationStack` or `TabView`, the `.material3` family of modifiers always go *on or outside* the views you want to affect.
216290
- Because they are designed to reach beneath Skip's SwiftUI covers, the modifiers use Compose terminology and types. In fact the properties of the supplied `Material3<Component>Options` structs typically exactly match the corresponding `androidx.compose.material3` component function parameters.
217291

218-
You can find details on Material 3 component API in [this Android API documentation](https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary). All `material3` modifiers are currently only available for Skip Lite transpiled Swift.
292+
You can find details on Material 3 component API in [this Android API documentation](https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary).
219293
{: class="callout info"}
220294

221295
Here is an example of changing the selected indicator color on your Android tab bar, which is implemented by the Material 3 `NavigationBar` component:
222296

297+
Skip Fuse:
298+
299+
```swift
300+
TabView {
301+
...
302+
}
303+
#if os(Android)
304+
.composeModifier { NavigationBarModifier() }
305+
#endif
306+
307+
...
308+
309+
#if SKIP
310+
struct NavigationBarModifier : ContentModifier {
311+
func modify(view: any View) -> any View {
312+
view.material3NavigationBar { options in
313+
let updatedColors = options.itemColors.copy(selectedIndicatorColor: Color.green.asComposeColor())
314+
return options.copy(itemColors: updatedColors)
315+
}
316+
}
317+
}
318+
#endif
319+
```
320+
321+
Skip Lite:
322+
223323
```swift
224324
TabView {
225325
...
@@ -371,11 +471,11 @@ public struct Material3TopAppBarOptions {
371471
}
372472
```
373473

374-
Note that `.material3TopAppBar` involves API that Compose deems experimental, so you must add the following to any `View` where you use it:
474+
Note that `.material3TopAppBar` involves API that Compose deems experimental, so you must add the following to any Skip Fuse `ContentModfifier` or Skip Lite `View` where you use it:
375475

376476
```swift
377477
// SKIP INSERT: @OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)
378-
struct MyView: View {
478+
struct MyContentModifier : ContentModifier {
379479
...
380480
}
381481
```

0 commit comments

Comments
 (0)