Swift Example Project

In this guide we will take a look at the example swift project we have created to show how the Ready Player Me web platform can be integrated into a native iOS application.

Swift WebView integration

You can find a working example of this Xcode project on our public GitHub here.

This example project uses UIKit and WKWebview and as such, it is only supported on iOS

Running the example project

To run this example project all you need to do is:

  • Clone the repository from our Ready Player Me GitHub

  • Open the Xcode project

  • Set your Provisioning profile and Signing Certificate by selecting the .xcodeproj file in Xcode and navigating to Signing and Capabilities (picture below)

  • Then building and running on your target iOS device

How it works

To understand better how this example works this guide will walk through the three .swift files highlighting the important parts and providing the information you need to make your own native Ready Player Me avatar implementation.

Web View Controller

The first script to look at is the WebViewController.swift. This custom UIViewController designed to handle the functionality and display the WebView browser.

Setting the Ready Player Me website Url

The readyPlayerMeUrl is set to our main domain by default but you can edit it as per your needs. For example something like https://yoursubdomain.readyplayer.me/avatar

let readyPlayerMeUrl = URL(string: "https://readyplayer.me/avatar")!

Javascript Injection

As mentioned in other pages of our documentation whenever the user hits the "Next" button on the Ready Player Me web platform the avatar asset is baked and a URL with the resulting .glb file is displayed. When this occurs, we send out a Javascript event with a message (string) that contains the generated avatar URL. We can hook into this inside our WebView by injecting some Javascript with an eventlistener.

Inside the WebViewController you will see a variable source that holds the following Javascript snippet.

window.addEventListener('message', function(event){
document.querySelector('.content').remove();
setTimeout(() => {
window.webkit.messageHandlers.iosListener.postMessage(event.data);
}, 1000) });

This Javascript snippet adds an event listener to the window listening for a "message". When the event is triggered the content is removed using document.querySelector.

Next, we run a function that allows us to run a callback function to native swift using webkit.messageHandlers . This callback passes the event.data (which contains the Ready Player Me avatar's .glb file URL). Also, notice here on the messageHandlers there is the ID "iosListener". This is ID important for the setup inside the loadView()function.

window.webkit.messageHandlers.iosListener.postMessage(event.data);

Setting up the WebView

The loadView() function is responsible for setting up the Initializing the WebView, injecting the Javascript snippet and linking the callback function mentioned above. The following code snippets will be lines from inside this loadView() function.

First, we must create a WKWebViewConfiguration object and a WKUserScript, which are important for creating a bridge between the WKWebView's browser Javascript and the Native code. Also, note in the WKUserScript constructor the first parameter source: is set to our source variable containing the JavaScript snippet.

let config = WKWebViewConfiguration()
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)

Next, we must add our newly created WKUserScript to the config object.

config.userContentController.addUserScript(script)

Then we set the delegate function by passing a WKScriptMessageHandler (self) and the name we want to use for the callback function.

config.userContentController.add(self, name: "iosListener")

Because the WebViewController inherits from WKScriptMessageHandler we can pass self as the first parameter. Notice that we use iosListener as the callback function name and it is also what we used inside the Javascript snippet. It is important that these match for the bridge to work.

Lastly, we initialize the WKWebView passing the frame bounds and more importantly the config object. We also assign this view as the WebView to ensure it is then displayed.

webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
view = webView

The next important function is userContentController(). This is actually what is called as the callback function in response to the Javascript event if set up correctly in the loadView() function. Here, we have access to the data from the Javascript event using message.body. In our example, we pass this to an avatarurlDelegate.avatarUrlCallback function which passes the data to the main ViewController which is then displayed in a native popup.

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
avatarUrlDelegate?.avatarUrlCallback(url : "\(message.body)")
}

The rest of the functions in WebViewController are quite self-explanatory.

View Controller

The ViewController.swift file in this project acts as the main view controller. In this example it contains functionality for spawning, displaying and retrieving the information (Ready Player Me avatar URL) from the WebViewController.

First is the relatively straight-forward viewDidLoad() function. Here, we call the createWebView() function and set the visibility values for the buttons to true and hide the webViewController. Lastly, we run the hasCookies() function to determine whether to hide the editAvatarButton. It is only possible to edit an existing avatar if one has been created and avatar data is stored in the browser's cookies. So but running this function we can determine whether to hide or sow the editAvatarButton.

override func viewDidLoad() {
super.viewDidLoad()
createWebView()
webViewController.view.isHidden = true
editAvatarButton.isHidden = !webViewController.hasCookies()
}

Creating the WebViewController

Next is the createWebView() function. As the name suggests it is responsible for creating and configuring the WebViewController. It assigns itself as an avatarUrlDelegate, this is important for receiving the data from the Javascript event in the WebViewController. This function also sets the size of the window and the tag which is used as an identifier inside the destroyWebView() function.

func createWebView(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: webViewIdentifier) as UIViewController
guard let viewController = controller as? WebViewController else {
return
}
webViewController = viewController
webViewController.avatarUrlDelegate = self
addChild(controller)
self.view.addSubview(controller.view)
controller.view.frame = view.bounds
controller.view.tag = webViewControllerTag
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
controller.didMove(toParent: self)
}

Receiving the avatar URL

The avatarUrlCallback(url: string) function is called by the webViewController and it is fired when the avatar creation process is completed thanks to our WebViewController.swift and the Javascript eventListener. Inside this function we simply display an alert with the URL and set the visibility of the webViewController and editAvatarButton.

func avatarUrlCallback(url: String){
showAlert(message: url)
webViewController.view.isHidden = true
editAvatarButton?.isHidden = false
}

Button action functions

Both button functions are similar and controller the visibility of the WebViewController and force a page to reload. However, the onCreateNewAvatarAction function differs as it needs to ensure the removal of all previous avatar data. This is done firstly by destroying and recreating the web view, then also reloading the page and clearing the history with webViewController.reloadPage(clearHistory: true).

@IBAction func onCreateNewAvatarAction(_ sender: Any) {
destroyWebView()
createWebView()
webViewController.reloadPage(clearHistory: true)
webViewController.view.isHidden = false
}
@IBAction func onEditAvatarAction(_ sender: Any) {
webViewController.view.isHidden = false
webViewController.reloadPage(clearHistory: false)
}

The ViewController also contains a method for destroying the WebView as shown below. It uses the view.tag identifier set in the CreateWebView() function to determine which view to remove.

func destroyWebView(){
if let viewWithTag = self.view.viewWithTag(webViewControllerTag) {
webViewController.dismiss(animated: true, completion: nil)
viewWithTag.removeFromSuperview()
}else{
print("No WebView to destroy!")
}

Web Cache Cleaner

This is just a static utility class webCacheCleaner.swift with a single function clean() for clearing the web view browser's cookies and cache.