the website of owen byron roberts

phonegap minus phonegap (for iOS)

The first iPhone/Android app I made was built with PhoneGap. I got a notice in January that Apple was going to remove it from the iTunes store because I haven't updated it in a while.

That seems dumb to me, because it assumes that it needs to be updated, which I don't think it does, but whatever. PhoneGap, which is now Cordova I guess, was a great tool to get me started making an iOS app because I had only really done web development before. But it was a pain in the ass to install and maintain and debug and I felt like I spent more time debugging PhoneGap than my own app.

I started developing a new app last year, called Butter City, which is about 2/3 done, and I started prototyping in HTML/CSS and JavaScript because that's what I know and the app uses a drawing and animation tool that I wrote in JavaScript. Once I got to the point that I wanted to really test it on the device, I had planned to just use PhoneGap again, but I found PhoneGap didn't exist in the same format and I didn't feel like getting into all the issues of installing it again. I didn't know enough about programming in general, and definitely iOS development, back in 2013 when I made Refresh the first time. I started learning some Swift to work on another new app that I haven't finished yet, and I realized that the UIWebView could most of the work that PhoneGap seemed to do. I also had brief attempts to rewrite the whole thing in Swift or using the open frameworks iOS framework, but they both presented problems,

Anyway, I decided to use Swift to make a simple container for the website based app. I'm using it for Butter City and Refresh, but I finished the new version Refresh today, so I'm sharing the process here. It's pretty easy, so you could do it probably without having done any Swift or iOS programming before. There's a lot of stuff you can do with the UIWebView that I don't cover, like running JavaScript from the Swift program, I'll add some links for stuff like that, but there's probably a point at which the complexity of various additions would defeat the purpose and it would be easier to just write it in Swift or do something with Cordova.


Start with a new Single View Application in Xcode.

Set the options with your developer profile and bundle etc. (If you don't have an account set up you can't test on a device, etc., so maybe start here and do that first.)

Save the project in the same folder where your web project is. My www folder has all of my HTML/CSS and JavaScript and Xcode will create a Refresh folder for the iOS project.

Once Xcode creates the project, open the file structure on the left hand side. We aren't going to use the iOS built in UI, so you can go ahead and delete the default story boards, Main.storyboard and LaunchScreen.storyboard. Delete and choose "Move to Trash".

Now we need to associate the website files with our project. Grab the www folder, or whatever your project is called, and drag it into the file hierarchy in Xcode, inside the main folder (mine is called "Refresh"). The defaults should be fine here, "Create Folder References" and "Add to targets: Refresh" with the main app checked.

Next we're going to set up the AppDelegate.swift file to use the ViewController.swift without the storyboards.

The beginning of the AppDelegate.swift should look something like this:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

Update the var window to:

 var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)

And update the func application:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
	window!.rootViewController = ViewController()
	window!.makeKeyAndVisible()
	
	return true
}

This lets us bypass the Storyboard setup and just use code in ViewController.swift. It's not really necessary, we can set up a UIWebView in the Storyboard, but I prefer to do everything in Swift so it's simpler.

For this to work we need to remove the "Main storyboard file base name" item from the Info.plist file. Just click on the minus button next to the name and that's it.

Start by creating a class variable UIWebView in the ViewController class. This will be initialized in the viewDidLoad function. It doesn't necessarily need to be a class variable but it will help if you need to use it in other functions down the road.

import UIKit

class ViewController: UIViewController {
	
	var web : UIWebView!

	override func viewDidLoad() {

A quick note, Swift introduced the WKWebView or WebKit WebView recently, which I haven't used yet. It will probably become the standard, but for now UIWebView still works fine.

In viewDidLoad() we need to initalized the WebView and then create an HTML request to load the web files and then add it to the view. It's pretty straight forward, though it took me a while of playing around with stuff and Googling to get the right series of steps to load the HTML.

override func viewDidLoad() {
	super.viewDidLoad()
	
	let screenSize : CGRect = UIScreen.main.bounds
	web = UIWebView( frame: CGRect(x:0, y:0, width:screenSize.width, height: screenSize.height) )
	let html = Bundle.main.path(forResource: "www/index", ofType: "html")
	let req = URLRequest( url: URL(fileURLWithPath: html!) )
	web.loadRequest( req )
	view.addSubview(web)
}

That's pretty much it. The other things you need to add are the launch screen and app icons, this stack overflow article is a good place to start for that as well as this one . You can test your app with a emulator, but I recommend testing on a real device, I've found there are some inconsistencies.


There's a few other things I've used that might not be needed but might be helpful.

  • If you're debugging with your phone connected over USB, you can use Safari to inspect the web view like any website on you desktop. Open Safari, go to Develop and look for your iPhone's name. When you click on the appication it will open a Web Inspector window with the JavaScript console, HTML elements, etc. Note that if you change your HTML/CSS and JavaScript files, you need to build the project again from Xcode, you can't just refresh using Safari.
  • One thing that took me forever to figure out: By default there has to be user input to start media like a video or audio. You can fix that with one line of code:
    web.mediaPlaybackRequiresUserAction = false
  • You can exectute JavaScript commands from the ViewController using the .stringByEvaluatingJavascript() method. I don't use that for Refresh, but in Butter City I used to to get and set localStorage values to save in the UserDefaults to save app progress locally on the phone.
  • You can add a appMovedToBackground function to the ViewController class to save values when a user presses the home button to quit the app or open a different app. This is one place it's usefull to have var web: UIWebView! as a class variables.
  • One thing that took a while to figure out was getting rid of the status bar. Add this in the ViewController class:
    override var prefersStatusBarHidden : Bool {
    	return true
    }
    Also add "Status bar is initially hidden" to Info.plist.