You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/content/docs/docs/modules/skip-kit/index.md
+139Lines changed: 139 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -240,6 +240,145 @@ On iOS it will use an instance of `QLPreviewController` to display the file at t
240
240
On iOS there's no need to provide a filename or a mime type, but sometimes on Android is necessary (for example when selecting a document using the document picker). On Android if no mime type is supplied it will try to guess it by the file url. If no mime type can be found the application chooser will be empty.
241
241
A file provider (like the one used for using the `MediaPicker`) is necessary for the Intent to correctly pass reading permission to the receiving app. As long as your Skip already implements the FileProvider and the `file_paths.xml` as described in the `Camera and Media Permission` section there's nothing else needed, otherwise you need to follow the instructions in the mentioned section.
242
242
243
+
## WebBrowser
244
+
245
+
For cases where you want to display a web page without the full power and complexity of an embedded `WebView` (from [SkipWeb](/docs/modules/skip-web)), SkipKit provides the `View.openWebBrowser()` modifier. This opens a URL in the platform's native in-app browser:
246
+
247
+
-**iOS**: [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) — a full-featured Safari experience presented within your app, complete with the address bar, share sheet, and reader mode.
248
+
-**Android**: [Chrome Custom Tabs](https://developer.android.com/develop/ui/views/layout/webapps/in-app-browsing-embedded-web) — a Chrome-powered browsing experience that shares cookies, autofill, and saved passwords with the user's browser.
249
+
250
+
### Basic Usage
251
+
252
+
Open a URL in the platform's native in-app browser:
253
+
254
+
```swift
255
+
importSwiftUI
256
+
importSkipKit
257
+
258
+
structMyView: View {
259
+
@Statevar showPage =false
260
+
261
+
var body: some View {
262
+
Button("Open Documentation") {
263
+
showPage =true
264
+
}
265
+
.openWebBrowser(
266
+
isPresented: $showPage,
267
+
url: "https://skip.dev/docs",
268
+
mode: .embeddedBrowser(params: nil)
269
+
)
270
+
}
271
+
}
272
+
```
273
+
274
+
### Launch in System Browser
275
+
276
+
To open the URL in the user's default browser app instead of an in-app browser:
277
+
278
+
```swift
279
+
Button("Open in Safari / Chrome") {
280
+
showPage =true
281
+
}
282
+
.openWebBrowser(
283
+
isPresented: $showPage,
284
+
url: "https://skip.dev",
285
+
mode: .launchBrowser
286
+
)
287
+
```
288
+
289
+
### Presentation Mode
290
+
291
+
By default the embedded browser slides up vertically as a modal sheet. Set `presentationMode` to `.navigation` for a horizontal slide transition that feels like a navigation push:
292
+
293
+
```swift
294
+
Button("Open with Navigation Style") {
295
+
showPage =true
296
+
}
297
+
.openWebBrowser(
298
+
isPresented: $showPage,
299
+
url: "https://skip.dev",
300
+
mode: .embeddedBrowser(params: EmbeddedParams(
301
+
presentationMode: .navigation
302
+
))
303
+
)
304
+
```
305
+
306
+
| Mode | iOS | Android |
307
+
| --- | --- | --- |
308
+
|`.sheet` (default) | Full-screen cover (slides up vertically) |[Partial Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs/guide-partial-custom-tabs/) bottom sheet (resizable, initially half-screen height). Falls back to full-screen if the browser does not support partial tabs. |
309
+
|`.navigation`| Navigation push (slides in horizontally) | Standard full-screen Chrome Custom Tabs launch |
310
+
311
+
**Limitations:**
312
+
-**iOS:** The `.navigation` presentation mode requires the calling view to be inside a `NavigationStack` (or `NavigationView`). If the view is not hosted in a navigation container, the modifier will have no effect.
313
+
-**Android:** In `.sheet` mode, if the user's browser does not support the Partial Custom Tabs API, the tab launches full-screen as a fallback.
314
+
315
+
### Custom Actions
316
+
317
+
Add custom actions that appear in the share sheet (iOS) or as menu items (Android):
318
+
319
+
```swift
320
+
Button("Open with Actions") {
321
+
showPage =true
322
+
}
323
+
.openWebBrowser(
324
+
isPresented: $showPage,
325
+
url: "https://skip.dev",
326
+
mode: .embeddedBrowser(params: EmbeddedParams(
327
+
customActions: [
328
+
WebBrowserAction(label: "Copy Link") { url in
329
+
// handle the action with the current page URL
330
+
},
331
+
WebBrowserAction(label: "Bookmark") { url in
332
+
// save the URL
333
+
}
334
+
]
335
+
))
336
+
)
337
+
```
338
+
339
+
On iOS, custom actions appear as `UIActivity` items in the Safari share sheet. On Android, they appear as menu items in Chrome Custom Tabs (maximum 5 items).
340
+
341
+
### API Reference
342
+
343
+
```swift
344
+
/// Controls how the embedded browser is presented.
345
+
publicenumWebBrowserPresentationMode {
346
+
/// Present as a vertically-sliding modal sheet (default).
347
+
casesheet
348
+
/// Present as a horizontally-sliding navigation push.
349
+
casenavigation
350
+
}
351
+
352
+
/// The mode for opening a web page.
353
+
publicenumWebBrowserMode {
354
+
/// Open the URL in the system's default browser application.
355
+
caselaunchBrowser
356
+
/// Open the URL in an embedded browser within the app.
Profile selection is configured on `WebEngineConfiguration`:
80
+
81
+
```swift
82
+
let config =WebEngineConfiguration(
83
+
profile: .named("account-a")
84
+
)
85
+
```
86
+
79
87
`WebViewNavigator` can keep a warm `WebEngine` and reuse it across view recreation.
80
88
When the same navigator is rebound to an engine that already has content/history, `initialURL`/`initialHTML` are not reloaded.
81
89
This lets apps preserve page state when navigating away and back with the same navigator instance.
82
90
91
+
Navigation APIs:
92
+
93
+
-`load(url:)` is fire-and-forget and logs load failures.
94
+
-`loadOrThrow(url:)` is async/throwing and should be used when callers need explicit error handling (including `WebProfileError` preflight failures).
95
+
83
96
### JavaScript
84
97
85
98
JavaScript can be executed against the browser with:
@@ -252,6 +265,7 @@ SkipWeb validates this contract at popup creation time:
252
265
For iOS parity, return a child created with `platformContext.makeChildWebEngine(...)`.
253
266
By default this mirrors the parent `WebEngineConfiguration` and inspectability on the popup child. Pass an explicit configuration only when you intentionally want the child to diverge.
254
267
This default mirroring is configuration-level. Platform delegate assignments on the returned child (`WKUIDelegate`, `WKNavigationDelegate`) are not automatically copied from the parent, so assign them explicitly if your app depends on that behavior.
268
+
On Android, once a child is returned from the delegate, SkipWeb mirrors key parent web settings and inherits the parent `WebProfile` onto the child; if profile inheritance fails, popup creation is denied.
255
269
256
270
### Scroll Delegate
257
271
@@ -502,6 +516,7 @@ extension View {
502
516
Supporting types:
503
517
504
518
-`WebCookie` (`name`, `value`, optional `domain`/`path`/`expires`, plus `isSecure`/`isHTTPOnly`)
- Android `.named("id")` requires `WebViewFeature.MULTI_PROFILE`; otherwise profile setup fails with `WebProfileError.unsupportedOnAndroid` (no fallback to default).
556
+
- On Android, always check profile support at runtime before using `.named("id")` profiles. You can use `WebEngine.isAndroidMultiProfileSupported()` (or the underlying `WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE)` check directly).
537
557
-`cookies(for:)` returns URL-matching cookies; on Android this is best-effort because `CookieManager` reads as a cookie-header string (limited metadata).
538
558
-`setCookie(_:requestURL:)` requires either `cookie.domain` or a `requestURL` host; otherwise it throws `WebCookieError.missingCookieDomain`.
539
559
-`removeData(ofTypes:modifiedSince:)` maps to iOS `WKWebsiteDataStore.removeData`.
0 commit comments