Skip to content

Commit a9d2c35

Browse files
committed
Update docs
1 parent b21eb81 commit a9d2c35

3 files changed

Lines changed: 166 additions & 7 deletions

File tree

src/content/docs/docs/modules/skip-kit/index.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,145 @@ On iOS it will use an instance of `QLPreviewController` to display the file at t
240240
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.
241241
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.
242242

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+
import SwiftUI
256+
import SkipKit
257+
258+
struct MyView: View {
259+
@State var 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+
public enum WebBrowserPresentationMode {
346+
/// Present as a vertically-sliding modal sheet (default).
347+
case sheet
348+
/// Present as a horizontally-sliding navigation push.
349+
case navigation
350+
}
351+
352+
/// The mode for opening a web page.
353+
public enum WebBrowserMode {
354+
/// Open the URL in the system's default browser application.
355+
case launchBrowser
356+
/// Open the URL in an embedded browser within the app.
357+
case embeddedBrowser(params: EmbeddedParams?)
358+
}
359+
360+
/// Configuration for the embedded browser.
361+
public struct EmbeddedParams {
362+
public var presentationMode: WebBrowserPresentationMode
363+
public var customActions: [WebBrowserAction]
364+
}
365+
366+
/// A custom action available on a web page.
367+
public struct WebBrowserAction {
368+
public let label: String
369+
public let handler: (URL) -> Void
370+
}
371+
372+
/// View modifier to open a web page.
373+
extension View {
374+
public func openWebBrowser(
375+
isPresented: Binding<Bool>,
376+
url: String,
377+
mode: WebBrowserMode
378+
) -> some View
379+
}
380+
```
381+
243382
## Building
244383

245384
This project is a free Swift Package Manager module that uses the

src/content/docs/docs/modules/skip-ui/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,8 @@ Support levels:
732732
<summary><code>DatePicker</code> (<a href="/docs/components/datepicker/">example</a>)</summary>
733733
<ul>
734734
<li><code>init(selection: Binding&lt;Date>, displayedComponents: DatePickerComponents = [.hourAndMinute, .date], @ViewBuilder label: () -> any View)</code></li>
735+
<li><code>init(selection: Binding&lt;Date>, in: ClosedRange&lt;Date>, displayedComponents: DatePickerComponents = [.hourAndMinute, .date], @ViewBuilder label: () -> any View)</code></li>
735736
<li><code>init(_ title: String, selection: Binding&lt;Date>, displayedComponents: DatePickerComponents = [.hourAndMinute, .date])</code></li>
736-
<li>Date range constraints (<code>in: ClosedRange&lt;Date></code>) are supported via Skip Fuse bridging</li>
737737
</ul>
738738
</details>
739739
</td>

src/content/docs/docs/modules/skip-web/index.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,23 @@ struct ConfigurableWebView : View {
7676

7777
```
7878

79+
Profile selection is configured on `WebEngineConfiguration`:
80+
81+
```swift
82+
let config = WebEngineConfiguration(
83+
profile: .named("account-a")
84+
)
85+
```
86+
7987
`WebViewNavigator` can keep a warm `WebEngine` and reuse it across view recreation.
8088
When the same navigator is rebound to an engine that already has content/history, `initialURL`/`initialHTML` are not reloaded.
8189
This lets apps preserve page state when navigating away and back with the same navigator instance.
8290

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+
8396
### JavaScript
8497

8598
JavaScript can be executed against the browser with:
@@ -252,6 +265,7 @@ SkipWeb validates this contract at popup creation time:
252265
For iOS parity, return a child created with `platformContext.makeChildWebEngine(...)`.
253266
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.
254267
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.
255269

256270
### Scroll Delegate
257271

@@ -502,6 +516,7 @@ extension View {
502516
Supporting types:
503517

504518
- `WebCookie` (`name`, `value`, optional `domain`/`path`/`expires`, plus `isSecure`/`isHTTPOnly`)
519+
- `WebProfile` (`.default`, `.named(String)`)
505520
- `WebSiteDataType` (`cookies`, `diskCache`, `memoryCache`, `offlineWebApplicationCache`, `localStorage`, `sessionStorage`, `webSQLDatabases`, `indexedDBDatabases`)
506521

507522
Example:
@@ -528,12 +543,17 @@ try await navigator.removeData(
528543

529544
Platform behavior:
530545

531-
- iOS uses the web view's `websiteDataStore.httpCookieStore`.
532-
- On iOS, cookie scope follows the `WKWebsiteDataStore` attached to that `WKWebView`.
533-
- In default SkipWeb usage, that is WebKit's shared default data store, so cookies are shared across SkipWeb web views in the app.
534-
- On iOS, a custom `WKWebView` with a different data store (for example `WKWebsiteDataStore.nonPersistent()`) uses that store instead.
535-
- Android uses `android.webkit.CookieManager`.
536-
- On Android, `CookieManager` is a process-wide singleton store shared by all `WebView` instances (not per-`WebView` configurable).
546+
- Profile mapping:
547+
548+
| `WebProfile` | iOS data store | Android data store |
549+
| --- | --- | --- |
550+
| `.default` | `WKWebsiteDataStore.default()` | Default process-wide store |
551+
| `.named("id")` | `WKWebsiteDataStore(forIdentifier: "id")` | AndroidX WebKit named profile (requires `WebViewFeature.MULTI_PROFILE`) |
552+
553+
- iOS cookie scope follows the `WKWebsiteDataStore` attached to the `WKWebView`.
554+
- Android `.default` uses `android.webkit.CookieManager` singleton.
555+
- 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).
537557
- `cookies(for:)` returns URL-matching cookies; on Android this is best-effort because `CookieManager` reads as a cookie-header string (limited metadata).
538558
- `setCookie(_:requestURL:)` requires either `cookie.domain` or a `requestURL` host; otherwise it throws `WebCookieError.missingCookieDomain`.
539559
- `removeData(ofTypes:modifiedSince:)` maps to iOS `WKWebsiteDataStore.removeData`.

0 commit comments

Comments
 (0)