Phaser to iOS without PhoneGap or Cordova


After publishing two games for iOS and experiencing the pain of being locked into that platform, I decided I would try a more cross-platform option the next time around.  I also wanted to be able to do essentially all of the development on my iPad, so that ruled out options like Unity, but made web technologies a great choice.  I opted for the Phaser Game Framework and successfully published Word Fall for Windows and macOS on itch.io.

The next step was to publish Word Fall to the iOS App Store where I hope to reach a wider audience for this fast-paced word game.  I naively thought I would be able to add a WKWebView to my root ViewController, point it at my index.html file and click publish.  I was wrong, oh so wrong.

Just dropping an HTML5 project into a WKWebView fails

Time For A New Adventure

I think it was Bilbo Baggins who said: “It's a dangerous business, Frodo, Googling. You type onto the search bar, and if you don't keep your feet, there's no knowing where you might be swept off to.”  I certainly ended up in some wild places (Javascript Core, WKUserScripts and adding createImageBitMap to Safari just to name a few) as I looked for tutorials on publishing a Phaser game on iOS.  A recurrent theme was the near ubiquitous recommendations to use PhoneGap, Cordova or Cocoon.io.  

I assume these all work, but my very brief attempt to use PhoneGap proved it was going to take some figuring out (Cocoon.io claims to be "1 click" and this Phaser-specific tutorial supports that claim) and I felt like if I was going to spend time figuring something out, I'd rather spend it figuring out how to do it myself (e.g. I'm not on a deadline so I indulged myself).

Cross-Origin Frustration 

Aside from ease (and presumably speed) of conversion, one key point for using these services is that these imbed a web server in your app in order to avoid restrictions on accessing local files as the result of cross-origin security restrictions.  Indeed, when I discussed my naive approach to converting my game in the Phaser Game Makers Facebook group, I was pointed to the lack of an imbedded web server as contributing to my challenges.

With all of that said, my previous experience developing iOS apps and a few references to this WWDC talk made me doubt the need for this imbedded web server.  There are just a few minutes dedicated to using a WKWebView to serve content from local files as (or as a part of) your app, but the whole talk is structured by contrasting this with serving content available on the web.  This convinced me there should be a way to show rich content using local assets and WKWebView without having to use a third party service.

Wiring It Up Right

There are numerous references out there (including Apple's Official Documentation) which suggest using WKWebView's "loadHTMLString" method to present your content.  This works well if everything you're presenting is contained in that String.  If you have scripts, images, audio or any other content saved in other files you'll run into cross-origin security challenges.  This doesn't make a lot of sense to me since iOS apps are sand boxed and can't write to their Bundle after compile time, but I guess that shows what I know.  

If you are presenting content which is too complex to easily include in a single HTML string, you'll need to load your HTML file using WKWebView's "loadFileURL allowingReadAccessTo:" method.  Setting the folder to which you are allowing read access to be your project's main bundle (use "Bundle.main.bundleURL") will prevent trouble with cross-origin security measures.  Once again I had hoped I could now just drop my entire Phaser project into my main bundle and publish my game.  No such luck of course.

Wiring up the WKWebView

Timing Is Everything

One of the great things WebKit enables you to do is to execute JavaScript from your Swift files.  For a long time I thought I needed to do this in order to properly load my game's assets.  In the process I learned that it doesn't matter if you're using a WKUserScript, JavaScriptCore or WKWebView's "evaluateJavaScript" method, you can't access any of the functions, variables or objects in your scripts until the files which contain them are loaded.  I know, "duh", right?  Don't ask me how much time I wasted proving this. 

I tried using the K-V-O method "estimatedProgress", but as this always returned "0.0", I didn't make much headway.  What did work was specifying my ViewController as a WKNavigationDelegate and implementing the "didFinish navigation" method.  When this method is called, the JavaScript files you've imported in your HTML file are loaded, your JavaScript functions, variables and objects exist and they can be called, referenced and modified.  I originally avoided using this method because I didn't think loading the files from the bundle was "navigation" and didn't expect to get coherent results from it, but it is clearly the way to go.

Modifying the HTML with an outside JavaScript file

What About Images

So, in order to simplify the incorporation of Phaser as much as possible, I decided I would use the basic "Add A Sprite" example from the Phaser.io site and try to recreate it in iOS.  To support that, I downloaded the mushroom image used in that example from the GitHub site where it is stored and that is the image you'll see me using through the remainder of this article.

Ok, great! Now we're cooking with gas.  We have our HTML file loading up nicely and we can modify that file using JavaScript stored in another file in the bundle, now we just need to add an image and we'll be off to the races!  One <img> tag coming up!  Bang! Victory!

Adding an image to the HTML page using an <i> tag

Adding Phaser

Adding Phaser should be, and is, as easy as copying the phaser.min.js file to the project's bundle and importing it with a <script> tag.  The challenge comes from getting an image to render onto the stage.  I spent days trying to figure out why I could get the image to render using an <img> tag in my html document, but I couldn't get that same image to display in Phaser's canvas, even if I did all of the work in my html file.  Nothing seemed to work.  Then I tried moving everything into that image's "onload" function.

Once Again, Timing Is King

Once again, before an image can be added to Phaser's cache and then on to the canvas, it needs to be loaded.  Inside the image's "onload" function, the image is added to Phaser's cache and then to the game object as a sprite.  Normally, an image would be loader using Phaser's built-in Loader object, but since I am not controlling the timing of when the image is loaded, I can't do it inside the "preload" function.  That means I either need to add the image directly to Phaser's cache myself (as I am doing) or I could instantiate my own instance of a Phaser.Loader, add the image to that loader's queue and start it.  That second option is certainly the more "Phaser" way to do it and it might make more sense if I was loading more than one image.  

Adding an image to the canvas in Phaser - now we're getting close!

As a side note, I had to specify Phaser's renderer as Phaser.CANVAS vice Phaser.AUTO because in AUTO Phaser defaults to WebGL if it is available (and it is for almost all iPhones out there today), but this doesn't seem to work using WebGL (i.e. the mushroom doesn't show up in the canvas area).  Figuring that out may have to be a project for another day.

Animate It! (Remember The Timing)

The final piece I'll talk about today is animating the image.  Once again, I thought I'd just add an update method and change the image's position each frame and be done.  No.  It still doesn't work that way - and for the same reason.  The update method will be called by Phaser before the image is loaded, so there will be no sprite to animate and the game crashes because it "can't find variable".  A quick solution to this is to check if the sprite exists before changing its position.  A better solution is to wait for the "onload" function to be called before instantiating the game object, then loading the image into Phaser using the built-in loader object and then animating via the update function since we now know the sprite will exist before the update function is called.

Animated mushroom gif

That's All For Now

The next steps are to repeat this process for the audio assets and validate touch actions are working properly.  If I can do that, I should be able to submit Word Fall to the App Store.  The beauty here is that this is very nearly a natural Phaser implementation with a little boilerplate code added in Xcode, so if you start your Phaser project with this in mind, preparing it for the App Store should really just be a matter of dropping your project in and wiring it up to the boiler plate.

Get Word Fall

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.