The ForemWebView
instance makes use of WebKit's WKScriptMessageHandler and this is what we commonly refer to as the Native Bridge. With a few clever techniques this allows our web context interact (bi-directionally) with the native implementation.
All these interactions are handled by the WKScriptMessageHandler implementation.
The Podcast Player within the Forem codebase relies on the Native Bridge to communicate with the ForemWebView
implementation in order to use native APIs for audio playback.
Messages are JSON encoded and sent both ways (JavaScript -> Swift and Swift -> JavaScript). These JSON messages all include an "action" and sometimes other optional values. This is how the JavaScript context is able to present a web UI while the actual audio player is an AVPlayer.
You can see supported messages by the Swift context here and the supported messages by the JavaScript context here.
The Video Player works in a very similar way, with JSON encoded messages exchanged between the native and the JavaScript contexts. This is a much more simplified process because the JavaScript context will send in a play
message and the ForemWebView
will create a fully native AVPlayerViewController
.
It's the app's responsibility to implement the ForemWebViewDelegate
and when a video player is initiated by the user the function willStartNativeVideo(playerController: AVPlayerViewController)
will be called, giving you the opportunity to handle it. The most straightforward way of doing this is:
present(playerController, animated: true) {
playerController.player?.play()
}
The Swift context also implements an interface for Native Image Uploads (via Camera or Library picker). This interface allows for a button in the DOM to trigger the native flow and expect the result (uploaded image URL).
The JavaScript event triggered by the user's tap on an "Upload Image" button requires an id
and supports an optional ratio
for image cropping. The id
represents the hidden input field used for receiving the events back from the Swift context. The ratio
allows for cropping the image on a specific ratio if necessary. This is the sample code from ArticleCoverImage.jsx
(here and here):
initNativeImagePicker = (e) => {
e.preventDefault();
window.webkit.messageHandlers.imageUpload.postMessage({
id: 'native-cover-image-upload-message',
ratio: `${100.0 / 42.0}`,
});
};
handleNativeMessage = (e) => {
const message = JSON.parse(e.target.value);
switch (message.action) {
case 'uploading':
this.setState({ uploadingImage: true });
this.clearUploadError();
break;
case 'error':
this.setState({
uploadingImage: false,
uploadError: true,
uploadErrorMessage: message.error,
});
break;
case 'success':
this.props.onMainImageUrlChange({ links: [message.link] });
this.setState({ uploadingImage: false });
break;
}
};
You can also read through how the Swift code handles the imageUpload
message here.