@webview-native-ui/engine 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -12
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/native/session.d.ts +5 -0
- package/dist/native/session.js +83 -14
- package/dist/native/updates.d.ts +16 -0
- package/dist/native/updates.js +108 -0
- package/dist/native/values.d.ts +5 -0
- package/dist/native/values.js +27 -0
- package/dist/projection/surface.d.ts +35 -0
- package/dist/projection/surface.js +81 -0
- package/dist/projection/surfaces.d.ts +20 -0
- package/dist/projection/surfaces.js +116 -0
- package/docs/README.md +70 -0
- package/docs/advanced/README.md +20 -0
- package/docs/advanced/android-placement.md +48 -0
- package/docs/advanced/native-handoff.md +89 -0
- package/docs/advanced/renderer.md +148 -0
- package/docs/advanced/update-queue.md +58 -0
- package/docs/advanced/web-projection.md +88 -0
- package/docs/assets/ios-native-capture-comparison.png +0 -0
- package/docs/concepts.md +97 -0
- package/docs/configuration/README.md +29 -0
- package/docs/configuration/capture.md +27 -0
- package/docs/configuration/events.md +35 -0
- package/docs/configuration/ionic-overlays.md +65 -0
- package/docs/configuration/values.md +125 -0
- package/docs/cordova.md +1 -1
- package/docs/{capacitor-plugin.md → getting-started/capacitor.md} +45 -47
- package/docs/{cordova-plugin.md → getting-started/cordova.md} +32 -15
- package/docs/getting-started/initialization.md +79 -0
- package/docs/maintenance.md +14 -2
- package/docs/{projection-containers.md → reference/containers.md} +6 -2
- package/docs/reference/platforms.md +137 -0
- package/docs/reference/projection.md +134 -0
- package/docs/reference/state.md +39 -0
- package/ios/Sources/WebViewNativeUIEngine/ClippedView.swift +12 -12
- package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +68 -7
- package/ios/Sources/WebViewNativeUIEngine/Frame.swift +10 -10
- package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +74 -39
- package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -3
- package/ios/Sources/WebViewNativeUIEngine/ScrollBindings.swift +2 -0
- package/ios/Sources/WebViewNativeUIEngine/ScrollTracker.swift +9 -7
- package/package.json +9 -2
- package/docs/projection.md +0 -368
|
@@ -4,16 +4,38 @@ The same npm package, Swift library and Android AAR support Capacitor and Cordov
|
|
|
4
4
|
|
|
5
5
|
The consuming plugin still owns its native view and the corresponding DOM, including placement, size and stacking order. Supply an existing selector such as `native-ad` or `#player`. The DOM may be added after initialization. Image preparation, scroll handling, overlays and restoration use the common Engine implementation.
|
|
6
6
|
|
|
7
|
+
See [native initialization](initialization.md) for required inputs and ownership. The platform examples below add Cordova-specific
|
|
8
|
+
startup, notification forwarding and teardown. Set `runtime` to Cordova even
|
|
9
|
+
though it is optional in the constructor.
|
|
10
|
+
|
|
11
|
+
For a Cordova plugin showing a native advertisement in a feed, the application
|
|
12
|
+
already owns a DOM slot and the plugin owns SDK interaction. This route connects
|
|
13
|
+
that slot to projection without adding a Capacitor dependency or replacing the
|
|
14
|
+
SDK's click handling; capture must still be allowed for the loaded content.
|
|
15
|
+
|
|
7
16
|
## Requirements
|
|
8
17
|
|
|
9
18
|
The integration is verified with cordova-ios 8.1.1 and cordova-android 15.1.0 using their standard WebViews. The Engine itself requires iOS 15+ and Android API 23+; the Cordova host or SDK may require a higher minimum. Composited Android capture requires API 26+.
|
|
10
19
|
|
|
20
|
+
## Choose the Web destination
|
|
21
|
+
|
|
22
|
+
Provide a laid-out container such as `native-ad` or `#player` in the layer where
|
|
23
|
+
the projected appearance should live. It may be inserted after registration, but
|
|
24
|
+
must exist when projection starts. The native view's rectangle cannot identify
|
|
25
|
+
its intended place in the DOM stacking tree. Check the
|
|
26
|
+
[container support table](../reference/containers.md) before choosing the selector.
|
|
27
|
+
For Ionic applications, add [overlay support](../configuration/ionic-overlays.md) once; other
|
|
28
|
+
frameworks report their own transition lifecycle through notifications.
|
|
29
|
+
|
|
11
30
|
## Dependencies
|
|
12
31
|
|
|
32
|
+
Replace `<engine-version>` in every dependency below with the same published
|
|
33
|
+
release. Read that release tag’s documentation so the examples match its APIs.
|
|
34
|
+
|
|
13
35
|
Use the same Engine release for the Web package and native libraries. The API is pre-1.0; pin versions and retain dependency lockfiles.
|
|
14
36
|
|
|
15
37
|
```sh
|
|
16
|
-
npm install --save-exact @webview-native-ui/engine
|
|
38
|
+
npm install --save-exact '@webview-native-ui/engine@<engine-version>'
|
|
17
39
|
```
|
|
18
40
|
|
|
19
41
|
Declare the native dependencies in the consuming plugin as well. npm installation alone does not link either native library.
|
|
@@ -21,7 +43,7 @@ Declare the native dependencies in the consuming plugin as well. npm installatio
|
|
|
21
43
|
For Swift Package Manager:
|
|
22
44
|
|
|
23
45
|
```swift
|
|
24
|
-
.package(url: "https://github.com/webview-native-ui/engine.git", exact: "
|
|
46
|
+
.package(url: "https://github.com/webview-native-ui/engine.git", exact: "<engine-version>")
|
|
25
47
|
// In the plugin target's dependencies:
|
|
26
48
|
.product(name: "WebViewNativeUIEngine", package: "engine")
|
|
27
49
|
```
|
|
@@ -30,7 +52,7 @@ For the plugin's Android Gradle configuration:
|
|
|
30
52
|
|
|
31
53
|
```groovy
|
|
32
54
|
dependencies {
|
|
33
|
-
implementation 'io.github.webview-native-ui:engine
|
|
55
|
+
implementation 'io.github.webview-native-ui:engine:<engine-version>'
|
|
34
56
|
}
|
|
35
57
|
```
|
|
36
58
|
|
|
@@ -166,7 +188,7 @@ class ExamplePlugin : CordovaPlugin() {
|
|
|
166
188
|
}
|
|
167
189
|
```
|
|
168
190
|
|
|
169
|
-
Use `projection?.receive(eventName, data)` at the existing notification point, as on iOS. Native view creation and placement remain on the UI thread. If the renderer loads asynchronously, keep `install(web)` in `pluginInitialize()` and create the registration after the view is ready. Close the old registration before replacing that renderer.
|
|
191
|
+
Use `projection?.receive(eventName, data)` at the existing notification point, as on iOS. See the shared [notification contract](../configuration/events.md) for update and destination semantics. Native view creation and placement remain on the UI thread. If the renderer loads asynchronously, keep `install(web)` in `pluginInitialize()` and create the registration after the view is ready. Close the old registration before replacing that renderer.
|
|
170
192
|
|
|
171
193
|
The `PathHandler` delegates same-origin image requests to Engine. It serves only the registration's PNG files in app-private cache storage; unrelated paths return `null`. If the plugin already has a handler, combine them with `projection?.handleResource(path) ?: existingHandler.handle(path)`. For multiple registrations, delegate to the matching owner. Keep the handler installed for the WebView lifetime. No WebViewClient replacement or storage permission is needed.
|
|
172
194
|
|
|
@@ -176,7 +198,7 @@ The defaults support Cordova's `https://localhost` and `http://localhost`. If th
|
|
|
176
198
|
|
|
177
199
|
Keep the registration for the WebView lifetime. Engine reconnects after a document reload and clears the old document's image state. If the plugin destroys its native UI in `onReset`, update its view provider and emit the appropriate destination; do not leave a registration pointing at a destroyed view. Dispose registrations when the plugin/WebView is destroyed.
|
|
178
200
|
|
|
179
|
-
|
|
201
|
+
The Cordova integration requires the standard Cordova WebView resource handler. Running a Cordova plugin inside a Capacitor app is not covered by this integration; the host must use the Capacitor integration instead. See the shared [DOM and native surface requirements](../reference/projection.md#dom-and-native-surface-requirements) for selector, layout and capture rules. [Ionic overlay support](../configuration/ionic-overlays.md) remains optional; plain Cordova apps need not enable it.
|
|
180
202
|
|
|
181
203
|
The integration fixture uses Cordova CLI 13, cordova-ios 8.1.1 and cordova-android 15.1.0 with their standard WebViews. It checks actual PNG handoff, native restoration and hidden-state retention across document reload, not merely JS bridge mocks. Older Cordova releases and custom WebView engines require separate verification.
|
|
182
204
|
|
|
@@ -189,14 +211,9 @@ npm run test:cordova -- android emulator-5554
|
|
|
189
211
|
|
|
190
212
|
The script creates a disposable app, uses local SwiftPM/Maven Engine artifacts, and records the application result in the printed temporary directory. It never publishes to npm or Maven. Android requires JDK 21+, SDK 36 and `ANDROID_HOME`; iOS requires Xcode.
|
|
191
213
|
|
|
192
|
-
##
|
|
193
|
-
|
|
194
|
-
Native appearance updates prepare replacement images after a 50 ms debounce by default.
|
|
195
|
-
If your control continues animating after its update notification, set `captureDelayMs`
|
|
196
|
-
on the native registration (Swift: `captureDelayMs: 600`; Kotlin: `captureDelayMs = 600`).
|
|
197
|
-
This changes background image preparation, not overlay presentation timing.
|
|
198
|
-
See [capture timing](projection.md#capture-timing) for the cache behavior and measured guidance.
|
|
199
|
-
|
|
200
|
-
### Projection DOM
|
|
214
|
+
## Next: choose optional behavior
|
|
201
215
|
|
|
202
|
-
|
|
216
|
+
Use the [configuration guide](../configuration/README.md) for notification routing,
|
|
217
|
+
value reflection, capture timing and fallback policy. These shared settings are
|
|
218
|
+
not additional platform setup. For a renderer that already batches controls, see
|
|
219
|
+
[advanced APIs](../advanced/README.md).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Initialize a native view registration
|
|
2
|
+
|
|
3
|
+
Start with `NativeUIProjection` when a plugin owns a native view and a corresponding
|
|
4
|
+
Web container. Registration starts the runtime, binds that container and coordinates
|
|
5
|
+
image handoff and restoration. You do not need the renderer APIs for this route.
|
|
6
|
+
|
|
7
|
+
Use the [Capacitor](capacitor.md) or [Cordova](cordova.md) guide for
|
|
8
|
+
dependency installation, plugin lifecycle hooks and notification forwarding.
|
|
9
|
+
The examples below show the smallest registration within those hooks, followed by
|
|
10
|
+
required inputs. After initialization, the configuration guide lists optional
|
|
11
|
+
behavior; advanced APIs have their own entry point.
|
|
12
|
+
|
|
13
|
+
For example, a plugin already draws a native tab bar, but an application opens a
|
|
14
|
+
Web settings sheet above it. Register the bar and its fallback navigation host so
|
|
15
|
+
the Engine can put the captured bar beneath that sheet and restore native input
|
|
16
|
+
when it closes. The plugin keeps its existing tab selection API.
|
|
17
|
+
|
|
18
|
+
## Minimal initialization
|
|
19
|
+
|
|
20
|
+
Create and retain one registration after your renderer has attached its native
|
|
21
|
+
view. The selector may match a DOM container added later, but native presentation
|
|
22
|
+
waits until that container exists. Give it a [supported layout box](../reference/containers.md).
|
|
23
|
+
|
|
24
|
+
In a Capacitor Swift plugin, inside `load()` or the existing renderer initializer:
|
|
25
|
+
|
|
26
|
+
```swift
|
|
27
|
+
projection = try NativeUIProjection(
|
|
28
|
+
webView: bridge?.webView,
|
|
29
|
+
plugin: jsName,
|
|
30
|
+
projectionSelector: "#native-ui-fallback",
|
|
31
|
+
view: { [weak self] in self?.nativeView }
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
In a Capacitor Kotlin plugin, install the shared channel in `load()` **before the
|
|
36
|
+
first WebView navigation**. The registration may be created later when its view is ready:
|
|
37
|
+
|
|
38
|
+
```kotlin
|
|
39
|
+
NativeUIProjection.install(bridge.webView)
|
|
40
|
+
projection = NativeUIProjection(
|
|
41
|
+
window = activity.window,
|
|
42
|
+
webView = bridge.webView,
|
|
43
|
+
plugin = pluginHandle.id,
|
|
44
|
+
projectionSelector = "#native-ui-fallback",
|
|
45
|
+
view = { nativeView }
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Both snippets belong inside the integration guide's error boundary; retain the
|
|
50
|
+
existing Web fallback if initialization fails. `projection` and `nativeView` are
|
|
51
|
+
properties of your plugin. Dispose the registration with `destroy()` on iOS or
|
|
52
|
+
`close()` on Android when that renderer is retired. All native lifecycle calls
|
|
53
|
+
run on the UI thread.
|
|
54
|
+
|
|
55
|
+
Cordova uses the same registration with `runtime: .cordova` / `runtime = Runtime.CORDOVA`
|
|
56
|
+
and its own WebView and plugin identity. Follow the [Cordova guide](cordova.md)
|
|
57
|
+
for those hooks and Android's resource handler.
|
|
58
|
+
|
|
59
|
+
## Required inputs
|
|
60
|
+
|
|
61
|
+
| Input | Platforms | What it identifies and why |
|
|
62
|
+
| -------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
63
|
+
| `webView` | iOS / Android | The application's WebView, which owns the destination document and bridge. Swift accepts an optional reference so a missing WebView throws through the initialization error boundary. |
|
|
64
|
+
| `plugin` | iOS / Android | The existing plugin identity routes notifications to this plugin rather than another Engine consumer. Capacitor: `jsName` on iOS, `pluginHandle.id` on Android. Cordova: the configured service name. |
|
|
65
|
+
| `projectionSelector` | iOS / Android | A CSS selector matching exactly one existing Web container when presentation begins. It gives the image its correct layout and overlay layer; a native rectangle cannot identify that DOM layer. |
|
|
66
|
+
| `view` | iOS / Android | A callback returning the renderer's current native view, or nil/null when absent. Return the current view within this renderer lifetime. When replacing the renderer, dispose its registration and create a new one; the plugin owns layout and normal visibility. |
|
|
67
|
+
| `window` | Android | The window containing the WebView and native surface, needed for composited capture and interaction tracking. |
|
|
68
|
+
|
|
69
|
+
These inputs establish ownership. They do not tell the Engine when content
|
|
70
|
+
changes or when an application transition should change presentation.
|
|
71
|
+
|
|
72
|
+
## Next: configure changing UI
|
|
73
|
+
|
|
74
|
+
The minimal registration establishes ownership. Continue with [optional settings](../configuration/README.md)
|
|
75
|
+
for event wiring, value reflection, capture and failure policy. Each setting lists
|
|
76
|
+
its default and when it is needed. See [advanced APIs](../advanced/README.md) only when
|
|
77
|
+
your renderer already manages batches, placement or update reconciliation.
|
|
78
|
+
|
|
79
|
+
[Documentation overview](../README.md)
|
package/docs/maintenance.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Development and releases
|
|
2
2
|
|
|
3
|
+
Use this guide when changing or publishing the Engine itself. A native consumer
|
|
4
|
+
may build without Node.js, so source changes must regenerate the bundled runtime.
|
|
5
|
+
A release may reach npm before Maven artifacts are downloadable, so publication
|
|
6
|
+
acceptance and consumer download readiness are checked separately below.
|
|
7
|
+
|
|
3
8
|
## Development
|
|
4
9
|
|
|
5
10
|
The public entry points are `src/index.ts` and `src/ionic.ts`, with shared contracts in `src/types.ts`. Implementation is grouped by responsibility:
|
|
@@ -23,7 +28,11 @@ npm test
|
|
|
23
28
|
xcodebuild -scheme WebViewNativeUIEngine -destination 'platform=iOS Simulator,id=<booted simulator UUID>' test
|
|
24
29
|
```
|
|
25
30
|
|
|
26
|
-
Lint combines strict TypeScript checking, the standard ESLint/TypeScript recommended rules, and typed Promise checks for `src`. Scripts and tests receive syntax-level linting; Prettier owns formatting. `npm run format` applies formatting only. Bridge payloads and test doubles may use explicit `any`; production boundary validation remains necessary. An explicit `void` marks background work, but does not handle a rejected Promise.
|
|
31
|
+
Lint combines strict TypeScript checking, the standard ESLint/TypeScript recommended rules, and typed Promise checks for `src`. Scripts and tests receive syntax-level linting; Prettier owns formatting. `npm run format` applies formatting only. Bridge payloads and test doubles may use explicit `any`; production boundary validation remains necessary. An explicit `void` marks background work, but does not handle a rejected Promise.
|
|
32
|
+
|
|
33
|
+
Native checks run in their platform CI jobs: `npm run lint:swift` uses node-swiftlint with the Capacitor configuration on macOS (`brew install swiftlint`); `npm run lint:android` runs Kotlin formatting checks and Android Lint with warnings treated as errors (JDK 21 and Android SDK 36). Use `npm run format:swift` and `npm run format:kotlin` to apply native formatting. Kotlin uses ktfmt because the Prettier Kotlin plugin cannot parse the existing native test fixture.
|
|
34
|
+
|
|
35
|
+
These checks include native tests and Cordova fixtures. Swift API documentation uses `///`; implementation notes use `//`. Swift size/complexity heuristics do not force state-machine splits or public API changes; fixture-only force unwraps/casts are permitted, while production retains the checks. Swift is also compiled and tested on an iOS simulator.
|
|
27
36
|
|
|
28
37
|
The engine is versioned independently of its consumers. Publish order is Maven publication acceptance → npm engine; consumers upgrade separately. npm releases require a successful Maven publication workflow for the exact tagged commit. Maven Central distribution may still be pending: Android consumers must wait until the matching POM and AAR are downloadable, while iOS and Web consumers can upgrade immediately. No publication is performed by build or test commands. Tests cover plain DOM, fractional/RTL geometry, stale ACKs, image ownership and native WKWebView scroll tracking; consumer tests remain responsible for Ionic/SDK integration.
|
|
29
38
|
|
|
@@ -39,7 +48,8 @@ For local packaging verification, run `python3 scripts/native-ui-engine-release/
|
|
|
39
48
|
|
|
40
49
|
## Maintainer release setup
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
Publish the same version for npm, SwiftPM and Maven; consumers pin matching
|
|
52
|
+
versions across these distributions.
|
|
43
53
|
|
|
44
54
|
The npm organization `webview-native-ui` and Maven Central namespace `io.github.webview-native-ui` are registered. Maintain npm trusted publishing for `webview-native-ui/engine`, workflow `release.yml`, and provision the Maven secrets above in this repository. Credentials are not copied by moving source files.
|
|
45
55
|
|
|
@@ -50,3 +60,5 @@ The npm organization `webview-native-ui` and Maven Central namespace `io.github.
|
|
|
50
60
|
No build or test command publishes a package. Existing npm/Maven versions are never overwritten.
|
|
51
61
|
|
|
52
62
|
To rerun release verification for an existing tag, dispatch `release.yml` with that tag. It checks the original tagged commit and skips npm publication only when the existing package contents match. Keep the tag unchanged. Release tooling uses the Node.js 24 bundled npm, matching CI.
|
|
63
|
+
|
|
64
|
+
[Documentation overview](README.md)
|
|
@@ -10,7 +10,7 @@ Choose the existing DOM element that occupies the native surface's intended loca
|
|
|
10
10
|
- `projectionSelector` uses document CSS selectors (ID, class, attribute or descendant). It must match exactly one element and does not enter Shadow DOM. A missing element can be created later; zero or multiple matches keep Native hidden.
|
|
11
11
|
- The retained image is inserted in the element's open ShadowRoot, or as a light-DOM child otherwise. Closed shadow content must inherit the host's visibility and expose a slot that can display the image.
|
|
12
12
|
- An overlay, collapsed item or inactive page is eligible only while it has a visible layout box. Do not force hidden configuration elements to render just to use them as destinations.
|
|
13
|
-
- Host/ancestor clipping, opacity, transforms and filters still affect the image. The image is noninteractive and freezes the captured appearance. See [projection
|
|
13
|
+
- Host/ancestor clipping, opacity, transforms and filters still affect the image. The image is noninteractive and freezes the captured appearance. See the [projection reference](projection.md#dom-and-native-surface-requirements) for native capture limits and the [renderer guide](../advanced/web-projection.md) for DOM mutation-observer exclusions.
|
|
14
14
|
|
|
15
15
|
## HTML containers
|
|
16
16
|
|
|
@@ -184,7 +184,11 @@ Select `#seat-projection`, not the HTML `select`:
|
|
|
184
184
|
</span>
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
-
The control's DOM, value and event listeners survive handoff
|
|
187
|
+
The control's DOM, value and event listeners survive handoff; the prepared image
|
|
188
|
+
does not accept input. Native behavior and application events remain with the
|
|
189
|
+
plugin. Optional [value bindings](../configuration/values.md) reflect native values on the
|
|
190
|
+
selected host. With the wrapper above, that host is the span, not the inner select;
|
|
191
|
+
the wrapper's component adapter must forward reflected values to its control.
|
|
188
192
|
|
|
189
193
|
## Verification scope
|
|
190
194
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Capture and platform limits
|
|
2
|
+
|
|
3
|
+
Choose capture and fallback settings for the actual native surface. Start with
|
|
4
|
+
the [projection contract](projection.md) and [configuration guide](../configuration/README.md);
|
|
5
|
+
this reference describes platform behavior and limits, not additional setup steps.
|
|
6
|
+
|
|
7
|
+
An opaque card in a feed and a translucent player control need different capture
|
|
8
|
+
choices. Drawing the card alone can avoid capturing neighboring content; drawing
|
|
9
|
+
the translucent control alone loses the background it blends with. Use the cases
|
|
10
|
+
below to choose capture behavior, and retain a Web fallback for content that
|
|
11
|
+
cannot be captured. These choices preserve available pixels, not live video or
|
|
12
|
+
all effects of the platform compositor.
|
|
13
|
+
|
|
14
|
+
## Android capture modes
|
|
15
|
+
|
|
16
|
+
For opaque, software-renderable Android SDK views, use
|
|
17
|
+
`captureMode = NativeUIProjection.CaptureMode.VIEW` together with an explicit
|
|
18
|
+
`failedFallback` policy (`WEB` unless the consumer maintains native placement). This draws only the View's
|
|
19
|
+
pixels with no surrounding padding, so clipping a partially visible ad cannot bake
|
|
20
|
+
in a toolbar or adjacent content. Video, SurfaceView, translucent views, and content
|
|
21
|
+
requiring Window composition must keep the default `COMPOSITED` mode. The consumer
|
|
22
|
+
must provide a matching, clipped DOM host for the full opaque View; arbitrary native
|
|
23
|
+
outline masks, transforms, and custom clipping are not reproduced.
|
|
24
|
+
|
|
25
|
+
VIEW pixels are cached by native View identity, dimensions, and content revision.
|
|
26
|
+
Ordinary scrolling retains the decoded Web image and updates placement without
|
|
27
|
+
capture, PNG encoding, or image decode. The image covers the host border box at
|
|
28
|
+
local origin (0, 0), independently of native placement acknowledgment latency.
|
|
29
|
+
Opaque images remain painted beneath the live native View between scrolls.
|
|
30
|
+
Before hiding that View, the engine waits for WebView visual readiness and a draw;
|
|
31
|
+
VIEW creates no native entry or return image copies. Return acknowledgment waits
|
|
32
|
+
across the native draw and the following animation frame. A generation check prevents
|
|
33
|
+
a delayed cover callback from hiding content after restoration or suspension.
|
|
34
|
+
|
|
35
|
+
The native Bitmap is recycled after PNG encoding in VIEW mode; the PNG and decoded
|
|
36
|
+
Web image remain cached. Plugin update notifications advance the content revision:
|
|
37
|
+
notify an update whenever content changes without replacing the View. Replacing the
|
|
38
|
+
registration, WebView navigation, reset, or close retires the cache. Encoding stays
|
|
39
|
+
off the UI thread; View drawing and cache ownership stay on it, with the existing
|
|
40
|
+
8-million-pixel cap.
|
|
41
|
+
|
|
42
|
+
COMPOSITED retains native handoff copies for translucent content. Entry copies wait
|
|
43
|
+
for WebView visual readiness and a draw before retirement. If the 500 ms entry
|
|
44
|
+
deadline expires, the native View is restored before the copy is removed, and a late
|
|
45
|
+
paint acknowledgment is rejected.
|
|
46
|
+
|
|
47
|
+
## Android library
|
|
48
|
+
|
|
49
|
+
Android uses a Gradle Android library, distributed as an AAR through Maven rather than Capacitor plugin registration. The publication coordinates are `io.github.webview-native-ui:engine:<version>`. Build and verify the local Maven publication with:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
android/gradlew -p android testDebugUnitTest assembleRelease connectedDebugAndroidTest publishReleasePublicationToVerificationRepository
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`NativeUIClippedView` is a low-level helper that applies caller-supplied position and clipping without resetting unchanged dimensions. `NativeUIProjection` manages image handoff and restoration; it does not use this helper to lay out the renderer. The consuming plugin owns normal native placement and size updates. Android does not claim WKWebView-style native overflow binding. High-level registrations share a non-consuming window touch observer: down cancels in-flight capture adoption, up/cancel/focus loss permits preparation again, and teardown removes only that registration. Existing callback dispatch and return values are preserved.
|
|
56
|
+
|
|
57
|
+
`NativeUISnapshots` prepares composited PixelCopy images and local PNG files, then covers/restores from that cache. Encoding runs off the UI thread; only one preparation runs per owner. Notify it of visual changes and interaction, and release tokens on teardown. All view/lifecycle calls run on the UI thread. The plugin retains revision, eligibility and fallback policy.
|
|
58
|
+
|
|
59
|
+
After prepared Web pixels are shown or retired, `NativeUIWebReadiness.prepare` checks readiness for a subsequent WebView draw. Its `current` predicate must validate the owning session, source generation and image revision. The temporary native copy covers this asynchronous handoff; overlay startup itself does not wait. Neither bridge acknowledgement nor this callback proves physical display presentation.
|
|
60
|
+
|
|
61
|
+
An opaque image can remain beneath an opaque native view between handoffs, but
|
|
62
|
+
that policy must not be applied to translucent controls: drawing a translucent
|
|
63
|
+
view over its own captured appearance would composite the content twice. Choose
|
|
64
|
+
capture behavior for the actual view and background, and validate the complete
|
|
65
|
+
integration. Never synthesize clicks through a projected image.
|
|
66
|
+
|
|
67
|
+
### Translucent Android surfaces
|
|
68
|
+
|
|
69
|
+
Capture the composited window pixels, including the background, instead of drawing the translucent view onto an empty bitmap. Keep the prepared Web image decoded but hidden. At handoff, `cover(token, window, reference, views)` installs a temporary opaque image copy before hiding those native views in the same UI turn. Reveal the Web image, confirm Web drawing readiness, then retire the entry copy. The overlay starts immediately; image encoding is never on this path.
|
|
70
|
+
|
|
71
|
+
For return, install the prepared return copy with `restore`, restore the native views underneath it, hide the Web image, and retire the return copy after Web drawing readiness. This avoids compositing live translucent native pixels over their own snapshot. Validate the token and current surface generation at every asynchronous callback. The temporary copy is noninteractive and bounded; teardown releases both copies and cached resources.
|
|
72
|
+
|
|
73
|
+
This preserves the captured background, not a continuously changing scene behind the image. Background changes must invalidate the cache through the same visual-change notifications as content updates. Video and unsupported capture surfaces retain the consumer's fallback policy.
|
|
74
|
+
|
|
75
|
+
Consumer integration tests belong to their respective repositories. Engine CI exercises its browser, Swift and Android contracts without requiring a sibling renderer checkout.
|
|
76
|
+
|
|
77
|
+
## Image color precision on iOS
|
|
78
|
+
|
|
79
|
+
Prepared window snapshots use an extended Display P3 Float16 bitmap during capture
|
|
80
|
+
and are encoded as 16-bit Display P3 PNGs on a background queue. The Web projection
|
|
81
|
+
displays the prepared image directly, without a Canvas or WebGPU conversion. Native
|
|
82
|
+
entry and return copies also use the decoded PNG, so both sides share the same
|
|
83
|
+
prepared pixels after color conversion. Encoding and decoding
|
|
84
|
+
stay outside the overlay handoff; consumers do not need a new setting or dependency.
|
|
85
|
+
|
|
86
|
+
This reduces color loss in wide-gamut content but does not guarantee identical Liquid
|
|
87
|
+
Glass appearance: native compositing and conversion to integer PNG can still change
|
|
88
|
+
pixels. Higher precision also increases capture memory and image size. The existing
|
|
89
|
+
fallback policy applies if preparation fails. Android retains its platform capture path.
|
|
90
|
+
|
|
91
|
+
### Liquid Glass capture differences
|
|
92
|
+
|
|
93
|
+
A captured Liquid Glass control can look different from the live native control
|
|
94
|
+
when it is projected into the WebView. Reflective edge highlights and background
|
|
95
|
+
refraction or blur are not fully preserved by the capture path. Higher color
|
|
96
|
+
precision alone does not resolve this difference.
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
|
|
100
|
+
Left: **iPhone 15 Pro Max, iOS 27.2**. Right: **iPad mini (6th generation),
|
|
101
|
+
iPadOS 27.2**. Each device screenshot shows the captured **16-bit Display P3 PNG
|
|
102
|
+
in WKWebView above** and the **live UIKit tab bar below**, at the same size within
|
|
103
|
+
that device. The screenshots were placed side by side without rescaling or changing
|
|
104
|
+
their shared color profile. These are physical-device results, not Simulator captures.
|
|
105
|
+
|
|
106
|
+
The system screenshot preserves the live appearance, but the application-level
|
|
107
|
+
capture does not reproduce it completely. On the tested iPhone, the difference was
|
|
108
|
+
also present when the unencoded captured image was displayed in a native
|
|
109
|
+
`UIImageView`, before PNG encoding or WebView display. Drawing the entire
|
|
110
|
+
`UIWindow` into a full-window bitmap and then cropping it did not improve the
|
|
111
|
+
result compared with the Engine's cropped bitmap.
|
|
112
|
+
|
|
113
|
+
System screenshots are evidence for this comparison, not an alternative Engine
|
|
114
|
+
capture backend. These screenshots were obtained through developer tooling with
|
|
115
|
+
Developer Mode enabled on the devices. Application screen-capture APIs such as
|
|
116
|
+
ReplayKit require user consent; manually taking a screenshot also requires user
|
|
117
|
+
action. Cropping a system screenshot would preserve its captured pixels, but does
|
|
118
|
+
not remove these acquisition requirements. The Engine therefore does not adopt
|
|
119
|
+
this route for automatic UI handoffs: ordinary projection must not depend on
|
|
120
|
+
Developer Mode, screenshot actions, or a screen-recording permission flow. See
|
|
121
|
+
[Apple's ReplayKit security documentation](https://support.apple.com/guide/security/replaykit-security-seca5fc039dd/web).
|
|
122
|
+
|
|
123
|
+
We have reported this limitation to Apple and requested a supported way to capture
|
|
124
|
+
the complete appearance without starting a screen-recording session. See the
|
|
125
|
+
[Apple Developer Forums discussion](https://developer.apple.com/forums/thread/853543).
|
|
126
|
+
Apple suggested capturing an ancestor that includes the necessary background;
|
|
127
|
+
the full-window comparison above tests that approach. This remains an observed
|
|
128
|
+
capture limitation, not a confirmed fix or a commitment from Apple. Validate
|
|
129
|
+
translucent controls on physical devices with the backgrounds your plugin supports.
|
|
130
|
+
|
|
131
|
+
## Temporary storage and application permissions
|
|
132
|
+
|
|
133
|
+
Prepared images stay in the app's private temporary storage. iOS uses `FileManager.default.temporaryDirectory`. On Android, construct `NativeUISnapshots` with a directory inside `context.cacheDir`; do not use shared or external storage. Neither platform needs a photo-library or storage permission prompt for this path. No Capacitor Filesystem plugin is required. See [Apple's sandbox directories](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) and [Android's app-specific storage](https://developer.android.com/training/data-storage/app-specific).
|
|
134
|
+
|
|
135
|
+
`Capacitor.convertFileSrc` maps the returned file URL to the existing WebView local server. It grants no filesystem permission and does not upload the image. If the application sets a Content Security Policy, its `img-src` must allow the local app origin used by that URL; do not enable broad `file:` access or unrelated WebView file-access settings.
|
|
136
|
+
|
|
137
|
+
`release(token)` and owner teardown remove owned snapshot files. Abrupt process termination can leave temporary images until the operating system clears the cache or the app is uninstalled; immediate deletion after a crash is not guaranteed. Keep this storage private and treat it as disposable, not persistent application data.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Projection reference
|
|
2
|
+
|
|
3
|
+
Use this reference to check integration contracts and platform limits. Start with
|
|
4
|
+
[concepts and integration choices](../concepts.md) to decide whether the Engine fits
|
|
5
|
+
your UI. Use [native initialization](../getting-started/initialization.md) to distinguish required
|
|
6
|
+
inputs, optional settings and advanced APIs, then follow the [Capacitor](../getting-started/capacitor.md),
|
|
7
|
+
[Cordova](../getting-started/cordova.md) or [renderer](../advanced/renderer.md) guide.
|
|
8
|
+
|
|
9
|
+
This page covers DOM lifetime, destination precedence, host requirements and native
|
|
10
|
+
eligibility. See [platform limits](platforms.md) for capture details, [projection state](state.md)
|
|
11
|
+
for the intent attribute, and [container support](containers.md) for HTML/Ionic hosts.
|
|
12
|
+
|
|
13
|
+
Use this contract when deciding what must survive a transition. For example, a
|
|
14
|
+
modal closes while a native restore reply is still in flight, and the framework
|
|
15
|
+
replaces the page DOM. The old reply must not reveal the departed page's controls;
|
|
16
|
+
the lifetime and destination rules below define that boundary.
|
|
17
|
+
|
|
18
|
+
## DOM and page lifetime
|
|
19
|
+
|
|
20
|
+
Native registration is independent of the JavaScript framework. Its selector identifies
|
|
21
|
+
one fallback container in the current document; it may appear after registration.
|
|
22
|
+
When a framework removes or replaces that container, the Engine retires the old
|
|
23
|
+
projection and binds the current matching element. A pending drag or native draw
|
|
24
|
+
acknowledgement from the departed container must not hold up its replacement.
|
|
25
|
+
Moving the same element between parents refreshes its scroll bindings without
|
|
26
|
+
requiring the plugin to register it again.
|
|
27
|
+
|
|
28
|
+
A full HTML navigation creates a new document and runtime session. The native
|
|
29
|
+
registration installs the runtime in the new document and retains the plugin's
|
|
30
|
+
explicit projection destination; images and replies belong to their original
|
|
31
|
+
document. Keep registrations for the native renderer's lifetime, rather than
|
|
32
|
+
recreating them for each framework page event.
|
|
33
|
+
|
|
34
|
+
These rules apply to selector-based native registration. A lower-level
|
|
35
|
+
`createWebProjectionScope(element)` intentionally binds that particular element;
|
|
36
|
+
its renderer remains responsible for replacing the scope when it changes elements.
|
|
37
|
+
|
|
38
|
+
The Engine cannot infer every framework's transition or modal semantics from DOM
|
|
39
|
+
mutations. Use the existing projection notifications for those transitions, and an
|
|
40
|
+
explicit overlay integration when needed. Ionic support is opt-in; it is not required
|
|
41
|
+
for ordinary DOM lifetime handling. Framework-specific events are not installed
|
|
42
|
+
automatically. Native view creation, layout constraints and SDK
|
|
43
|
+
lifecycle remain owned by the plugin.
|
|
44
|
+
|
|
45
|
+
## Projection contract
|
|
46
|
+
|
|
47
|
+
A notification specifies **where the surface belongs**, not how to capture it. The Engine chooses the platform-specific handoff without requiring the plugin to keep another copy of projection state.
|
|
48
|
+
|
|
49
|
+
| Intended destination | Required behavior |
|
|
50
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `native` | Show the interactive native view at confirmed geometry. During motion, use supported native tracking or a prepared image. |
|
|
52
|
+
| `web` | Keep native content below the covering Web layer by removing its native projection; use a prepared image or the plugin's Web fallback. |
|
|
53
|
+
| `none` | Retire native content, projected images and fallback content. Later update/capture callbacks must not show it again. |
|
|
54
|
+
|
|
55
|
+
Overlay coverage can temporarily turn `native` into `web`; it never overrides `none`. A capture failure does not change the intended destination. `failedFallback: native` is only valid for consumers that maintain native placement themselves, and never overrides explicit `web`, `none`, or overlay coverage.
|
|
56
|
+
|
|
57
|
+
The Engine owns image readiness and restoration acknowledgement. It must not hide native pixels before replacement pixels are ready, nor retire replacement pixels before native restoration succeeds. Position-only motion reuses prepared content; appearance, size and renderer identity changes invalidate it. A missing image uses the declared fallback immediately rather than waiting to capture during a transition.
|
|
58
|
+
|
|
59
|
+
Registration and notification forwarding retain the latest intent across startup and reconnection; they do not replay business events. Destruction retires the registration and its cached images. Notifications are intent, not completion promises: an API that promises completed hiding must await the explicit retirement acknowledgement described under DOM and native surface requirements.
|
|
60
|
+
|
|
61
|
+
An existing renderer that checks CSS visibility can call `getUnprojectedVisibility(element)` from the npm package. It reads current authored/inherited visibility without the Engine's hiding rules, and restores projection ownership before returning. Do not use Engine marker attributes to infer projection ownership or mutate them in a consuming plugin. See [renderer DOM integration](../advanced/web-projection.md) for excluding Engine-owned mutations from renderer invalidation. This helper performs a style read and can trigger attribute observers; it does not eliminate measurement or transport costs.
|
|
62
|
+
|
|
63
|
+
## DOM and native surface requirements
|
|
64
|
+
|
|
65
|
+
`projectionSelector` is required and must resolve to **one existing container when projection starts**, using a valid CSS selector. ID, class, attribute and descendant selectors are supported; zero or multiple matches leave Native hidden. The engine does not create the fallback container or choose its size, placement, layer or z-index. Keep the container's normal layout and any interactive Web controls; an empty zero-size placeholder is not a usable fallback. It is a fixed scope, not a discovery root.
|
|
66
|
+
|
|
67
|
+
The container does not have to exist at registration time. A missing DOM waits without showing native content; replacing it creates a new binding and retires old images. The engine preserves the container's children, listeners and values. Pass the native view itself through `view`; a group of controls can use their common native container. The engine manages the internal identifier, so no ID list or view dictionary is required. Independent surfaces use separate registrations.
|
|
68
|
+
|
|
69
|
+
A Web Component host can be the destination, for example `projectionSelector: 'native-ad[placement="home"]'`. Give the host a layout box (`display: block` with the intended dimensions); the engine observes insertion, removal and selector attribute changes. The selector runs in the document and does not search inside shadow roots. The engine suppresses fallback content and pseudo-elements in the light DOM and open shadow roots during projection. Closed shadow content cannot be inspected and must inherit the host's `visibility`; provide a container whose shadow/slot structure can display the inserted image.
|
|
70
|
+
|
|
71
|
+
See [Projection container support](containers.md) for the HTML and complete Ionic element tables, sizing conditions and a wrapper example.
|
|
72
|
+
|
|
73
|
+
### Native surface requirements
|
|
74
|
+
|
|
75
|
+
When a plugin displays several independently moving native views—for example,
|
|
76
|
+
several advertisements in a scrolling feed—create a separate registration for
|
|
77
|
+
each view and give each one a selector matching its own DOM container. This lets
|
|
78
|
+
one view move, hide or be replaced without changing another view's projection.
|
|
79
|
+
|
|
80
|
+
If those registrations share plugin notifications, use `receiveListeners.match`
|
|
81
|
+
to route each notification to its intended registration. The matching fields are
|
|
82
|
+
chosen by your plugin, not reserved Engine identifiers. For example, include
|
|
83
|
+
`viewKey` to identify the displayed item and `instanceId` to distinguish successive
|
|
84
|
+
loaded instances for that item:
|
|
85
|
+
|
|
86
|
+
```swift
|
|
87
|
+
match: ["viewKey": viewKey, "instanceId": instanceId]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```kotlin
|
|
91
|
+
match = mapOf("viewKey" to viewKey, "instanceId" to instanceId)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Include the same fields in the notification payload. Dispose the old registration
|
|
95
|
+
when replacing its loaded content, even if the native container is reused, so
|
|
96
|
+
notifications for the previous instance cannot affect the replacement.
|
|
97
|
+
|
|
98
|
+
For third-party SDK views, keep loading and interaction handling in your plugin.
|
|
99
|
+
Use `canCapture` to report whether the current content may be captured under the
|
|
100
|
+
SDK's rules and your integration's supported capture conditions. For example, an
|
|
101
|
+
advertising SDK may require different handling for video and static content.
|
|
102
|
+
The Engine does not grant permission to capture SDK content or replace its
|
|
103
|
+
click and impression handling.
|
|
104
|
+
|
|
105
|
+
For SDK consumers without an equivalent Web fallback, set `failedFallback: .native`
|
|
106
|
+
on iOS or `failedFallback = NativeUIProjection.FailedFallback.NATIVE` on Android.
|
|
107
|
+
The default is `.web` / `WEB`, preserving existing consumers. If automatic motion
|
|
108
|
+
cannot use a prepared image (not ready, unsupported capture, or rejected handoff),
|
|
109
|
+
`native` preserves the live native view and keeps fallback DOM hidden. The consumer
|
|
110
|
+
must continue positioning that view; this option does not create native scroll
|
|
111
|
+
tracking on Android. Explicit `web`, `none`, and overlay coverage take precedence:
|
|
112
|
+
this option never raises a native view above an intentionally covering Web layer.
|
|
113
|
+
|
|
114
|
+
On Android, consumers whose asynchronous hide method promises that all pixels have
|
|
115
|
+
disappeared must call `projection.hide { completed -> ... }` and resolve only when
|
|
116
|
+
`completed` is true. Native handoff copies disappear immediately; completion waits
|
|
117
|
+
for synchronous Web-proxy retirement. A timeout reports failure, not success.
|
|
118
|
+
Call `projection.show()` when presenting again, in addition to normal plugin events;
|
|
119
|
+
this also preserves presentation intent if event subscription is still starting.
|
|
120
|
+
|
|
121
|
+
Scroll observation follows composed-tree ancestors, including assigned slots.
|
|
122
|
+
Non-composed scroll events inside an Ionic shadow tree are observed at their source;
|
|
123
|
+
applications do not add an extra listener or dispatch synthetic document events.
|
|
124
|
+
Listeners follow host reparenting and slot reassignment and are removed on teardown.
|
|
125
|
+
|
|
126
|
+
Scrolling is internal: iOS uses a uniquely matched native scroll binding when available; otherwise, including Android, scrolling temporarily uses the prepared image and restores native content after motion settles. Automatic scroll handling never overrides explicit `web` or `none`. Unsupported capture or geometry keeps the normal DOM fallback; it does not wait for a new capture on the transition path. The latest explicit destination survives WebView reload, and old-document replies cannot complete a new document's requests.
|
|
127
|
+
|
|
128
|
+
By default, the internal channel is limited to the main frame on Capacitor's localhost origins. Pass the application's exact `allowedOrigins` for a different configured origin. Android requires WebView's `WEB_MESSAGE_LISTENER` and `DOCUMENT_START_SCRIPT` features; registration fails explicitly on unsupported WebView implementations. Do not use wildcard origins for remotely navigable WebViews.
|
|
129
|
+
|
|
130
|
+
For capture modes, translucent surfaces, image color and private storage, see
|
|
131
|
+
[capture and platform limits](platforms.md). Custom renderer bridge and scroll
|
|
132
|
+
bindings are documented under [native handoff](../advanced/native-handoff.md).
|
|
133
|
+
|
|
134
|
+
[Documentation overview](../README.md)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Observe projection state
|
|
2
|
+
|
|
3
|
+
Projection state answers **where the component should be presented**. It is
|
|
4
|
+
independent of its value, such as a toggle's checked state or a selected tab.
|
|
5
|
+
See [value bindings](../configuration/values.md) for that connection.
|
|
6
|
+
|
|
7
|
+
For example, a tab bar unexpectedly remains hidden after dismissing a modal.
|
|
8
|
+
Inspect this attribute to distinguish a remaining `web` request from a `native`
|
|
9
|
+
request whose restoration is still pending. It helps diagnose the ownership
|
|
10
|
+
choice; it is not permission to reveal the bar or discard its image early.
|
|
11
|
+
|
|
12
|
+
A selector-based `NativeUIProjection` registration writes the read-only
|
|
13
|
+
`data-native-ui-destination` attribute on its current DOM container:
|
|
14
|
+
|
|
15
|
+
| Value | Meaning |
|
|
16
|
+
| -------- | ----------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `native` | Native presentation is requested. |
|
|
18
|
+
| `web` | Web presentation is requested, explicitly or because a registered overlay covers the container. |
|
|
19
|
+
| `none` | No presentation is requested. |
|
|
20
|
+
|
|
21
|
+
Without a prior destination notification, startup derives the initial intent from
|
|
22
|
+
native view visibility; a hidden or absent view starts as `none`. The plugin's
|
|
23
|
+
projection events then select `native`, `web` or `none`. Overlay coverage
|
|
24
|
+
changes an otherwise native request to Web; it does not override an explicit
|
|
25
|
+
`none`. The Engine updates the attribute as it reconciles presentation and removes
|
|
26
|
+
it when the container is replaced or the registration is destroyed. Reserve this
|
|
27
|
+
attribute for the Engine rather than setting it from application code.
|
|
28
|
+
|
|
29
|
+
This is presentation **intent**, not an acknowledgement that a native frame or
|
|
30
|
+
image has finished drawing. Web presentation may use a cached native image or the
|
|
31
|
+
existing interactive fallback according to availability and the integration's
|
|
32
|
+
policy. Do not hide the fallback solely because this attribute says `native`:
|
|
33
|
+
the Engine's handoff keeps the previous representation until the next one is ready.
|
|
34
|
+
|
|
35
|
+
Use this attribute for diagnostics and application state that does not take over
|
|
36
|
+
visibility. Continue using the documented projection events to request a change.
|
|
37
|
+
See the [projection contract](projection.md#projection-contract) for ownership,
|
|
38
|
+
fallback and interaction rules. Internal snapshot attributes are not additional
|
|
39
|
+
public state APIs.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import UIKit
|
|
2
2
|
/// Keeps native content at full size while clipping hit testing and visible pixels.
|
|
3
3
|
public final class NativeUIClippedView: UIView {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
public let content: UIView
|
|
5
|
+
public init(content: UIView) { self.content = content; super.init(frame: .zero); addSubview(content) }
|
|
6
|
+
@available(*, unavailable) required init?(coder: NSCoder) { fatalError() }
|
|
7
|
+
public func place(_ frame: CGRect, clip: CGRect? = nil) {
|
|
8
|
+
clipsToBounds = clip != nil
|
|
9
|
+
let visible = clip.map { frame.intersection($0) } ?? frame
|
|
10
|
+
isHidden = visible.isNull || visible.isEmpty
|
|
11
|
+
guard !isHidden else { return }
|
|
12
|
+
if self.frame != visible { self.frame = visible }
|
|
13
|
+
let local = CGRect(x: frame.minX - visible.minX, y: frame.minY - visible.minY, width: frame.width, height: frame.height)
|
|
14
|
+
if content.frame != local { content.frame = local }
|
|
15
|
+
}
|
|
16
16
|
}
|