Announcing iPhone Wax: Native UIKit iPhone Apps Written In Lua

By
On September 30, 2009

Development Goes Faster With iPhone Wax

Early this summer I started playing around with MacRuby, which lets Rubyists create native OS-X applications. While I love Objective-C, scripting languages have speed-of-development, memory management simplicity, and other advantages. Getting Ruby running on the iPhone is challenging; while I’m sure it’ll get there, I wanted something sooner.

I started investigating how I might wire up — and then write native iPhone apps from — a scripting language. Lua was on my radar already. It’s compact, expressive, fast enough, and was designed to be embedded. Took only about 20 minutes to get the Lua interpreter running on the iPhone. The real work was to bridge Lua and all the Objective-C/CocoaTouch classes. The bridge had to work in two directions: it would need to be able to create CocoaTouch objects and also be able to respond to callbacks as part of the familiar delegate/protocol model.

I tweeted about my intentions. Corey Johnson responded that he’d been working along the same lines and, dang-it, his implementation was exactly what I had in mind. It’s called iPhone Wax, it’s brilliant, and Apple has already approved one app using it.

In the remainder of this post, I’ll walk you through getting it set up, show you how to creating a project featuring a UITableView, and close with a section on its roadmap and tools support.

Getting And Installing iPhone Wax

iPhone Wax is available on GitHub. You can either clone its repo or download a zip of the latest code. Once you’ve got the source, change to its directory, and type rake install — this will install an iPhone Wax project template in Xcode.

Hello World in Label, Courtesy of Lua and iPhone Wax

Let’s create a simple project:

  1. Open up Xcode
  2. Select New Project from the File Menu
  3. Under User Templates select the Wax item
  4. Select Wax iPhone App from the template list
  5. Click the Choose… button
  6. Name your project and click the Save button

The iPhone Wax Xcode template does the heavy lifting, creates a project with a UIWindow and an embedded, bridged, ready to use Lua interpreter.

The iPhone Wax project template is a ready-made “Hello World” application. Build and Run the application to see it in action.

Using CocoaTouch/UIKit Classes From Lua With iPhone Wax

While still in the app from the previous step, take a look at the Lua source that drives the application: open init.lua in Xcode’s editor — you’ll find it under the scripts folder under in the Groups & Files panel.

The Lua-to-CocoaTouch/UIKit bridging syntax is easy to pick out from the example. After requiring the wax library, the code contains this instruction:

window = UI.Application:sharedApplication():keyWindow()

The Lua window variable will be assigned keyWindow property of the sharedApplication singleton/class-member of the UIApplication class. Note the absence of an alloc call. iPhone Wax takes care of memory management using hooks in Lua’s garbage collection routines; it sends a release message to the object on your behalf when the Lua disposes of its variable.

With the variable set operations can be performed on it:

window:setBackgroundColor(UI.Color:orangeColor())

Here we’re calling the setBackgroundColor method on our window method and assigning it the orange color.

Next, the code creates a label and configures its attributes:

label = UI.Label:initWithFrame(CGRect(0, 100, 320, 40))
label:setFont(UI.Font:boldSystemFontOfSize(30))
label:setColor(UI.Color:orangeColor())
label:setText("Hello Lua!")
label:setTextAlignment(UITextAlignmentCenter)

Finally, the label is made visible by adding it as a subview:

window:addSubview(label)

Beyond Hello World: Delegates And A UITableView

To do real work, our Lua code needs to be able to respond to delegate callbacks. Let’s modify our sample to include a UITableView and use Lua to implement the UITableViewDelegate and UITableViewDataSource protocols.

Start by adding a new, blank file called TableViewController.lua to the project:

  1. Right-click or control-click on the scripts folder in the Groups & Files panel
  2. Choose Add > New File from the context menu
  3. Click the Other item user the Mac OS-X section
  4. Select the Empty File icon and click Next
  5. Name the file TableViewController.lua and click Finish

Paste this code into the new file:

waxClass("TableViewController", UI.TableViewController, protocols = {"UITableViewDelegate", "UITableViewDataSource"})

function init(self)
  self.super:init()
  self.states = {"Michigan", "California", "New York", "Illinois", "Minnesota", "Florida"}
  return self
end

function viewDidLoad(self)
  self:tableView():setDataSource(self)
  self:tableView():setDelegate(self)
end

-- DataSource
-------------
function numberOfSectionsInTableView(self, tableView)
  return 1
end

function tableView_numberOfRowsInSection(self, tableView, section)
  return #self.states
end

function tableView_cellForRowAtIndexPath(self, tableView, indexPath)
  local identifier = "TableViewCell"
  local cell = tableView:dequeueReusableCellWithIdentifier(identifier)
  cell = cell or UI.TableViewCell:initWithStyle_reuseIdentifier(UITableViewCellStyleDefault, identifier)

  cell:setText(self.states[indexPath:row() + 1]) -- Must +1 because lua arrays are 1 based

  return cell
end

-- Delegate
-----------
function tableView_didSelectRowAtIndexPath(self, tableView, indexPath)
  tableView:deselectRowAtIndexPath_animated(indexPath, true)
  -- Do something cool here!
end

[ Update: there was a slight change to the waxClass API. An earlier version -- the one I used in my GitHub code -- expected the third argument to be a hash. So when you look at the GitHub code you'll see: waxClass("TableViewController", UI.TableViewController, {protocols = {"UITableViewDelegate", "UITableViewDataSource"}}) ]

Have a look at the top line, the one beginning with waxClass. This line is key. It declares a Lua class that bridges to CocoaTouch and declares the protocols it implements. The remainder of the code is the implementation of the constructor and the callbacks for the protocols.

Next, update init.lua to use the following:

require "wax"
require "TableViewController"

window = UI.Application:sharedApplication():keyWindow()

tableViewController = TableViewController:init()
window:addSubview(tableViewController:view())

In this update, we've replaced the background color and label-related code with code that instantiates an instance of the TableViewController class and adds it to the view. Build and run the project to see the working table UI.

The source for the finished example is available on GitHub. You can either clone its repo or download a zip of the code.

Roadmap, Tools And A Google Group

Corey's SF Rentals uses iPhone Wax. iPhone Wax complete enough and stable enough to consider for real applications.

A TextMate bundle is in the works. Notably, it'll support transforming Objective-C method signatures copied into the pasteboard -- from, e.g., Xcode's documentation -- into Lua method calls when pasted into the code.

CocoaTouch methods can be verbose; because the bridge directly translates CocoaTouch method names the Lua function calls can be quite long. A similar problem exists with MacRuby; MacRuby includes a sub-project called Hot Cocoa that ads a thin layer of idiomatic Ruby to make the programs written with MacRuby feel more natural. We'll do something similar.

We've created an iPhone Wax Google Group to support developers and an official site is in the works.

I'll follow up this post with articles on the TextMate bundle, more details of how to use the bridge, and details on how the bridge functions.

0 responses to “Announcing iPhone Wax: Native UIKit iPhone Apps Written In Lua”

  1. Are there any restrictions regarding embedded scripting in applications? I’ve heard something along those lines. Maybe that is only when the application will download scripts (for example plugins or logic for new game levels etc.). Does anyone know?

  2. vijay says:

    Hello ,
    I am created simple new wax project, but i am getting following error — “Failed to launch simulated application: Unknown error.”
    pls help me

  3. elai says:

    Are NSDictionaries, NSArrays and what not translated into their lua equivalents, or do we have to do arrayVar:objectWithIndex(5) to access an object array? Are NSNumbers and what not autoboxed?

  4. design says:

    Its got some magic, but not much. It basically mimics luas module method. Any new global or any call to a global first goes through the waxClass table, and they jumps out to the global table.

  5. website says:

    Clever class syntax there. Lua doesnt have native classes, so you have to roll your own via setmetatable. This one seems to involve the module as the class itself, and Im sure that waxClass call has a lot of magic going on behind it.

  6. chiefjeef says:

    Sounds like Corona: https://www.anscamobile.com

  7. Dan Grigsby says:

    Martin: Apple forbids downloading scripts. If you ship everything in the binary everything’s cool.

    chiefjeef: Nope, not Corona. Corona is great — I interviewed the Corona team for the podcast — but this has a different goal. Corona is Lua, graphics primitives rendered with OpenGL and touch-handling. This is is instantiating and using CocoaTouch classes from Lua.

  8. zzajin says:

    How does this compare to LuaCore?
    https://gusmueller.com/lua/

  9. Egor Egorov says:

    Wow, it works! I played around and really liked it.

    Still, the iPhone SDK Agreement (section 3.3.2) says: “No interpreted code may be downloaded or used in an Application”.

    So how come one application was approved in app store? Wax sounds like a very good promise, but how do we use it for serious development?… Can we cross-compile lua scripts to ObjC code at the build stage?

  10. I see a contradiction here:

    Dan: “Apple forbids downloading scripts. If you ship everything in the binary everything’s cool.”

    Egor: “No interpreted code may be downloaded or used in an Application” (my emphasis)

    So how is it?

    A nice feature of Lua is that it can compile to portable byte code. The bytecode interpreter shouldn’t count as a compiler, so as long as you don’t include and compile the source code in your app, you should be fine.

  11. CC says:

    I’d be wary of using Lua as a scripting language in an iPhone app, since, it seems to me, it would count as “interpreted code.” The fact that there is already an app in the App Store that uses it is in no way a guarantee that Apple’s okay with it – it only means that the particular reviewer didn’t catch that it was using compiled code.

    The real issue is whether someone could tinker with a Lua script in a deliverable to cause havoc. If a change to a Lua script resource could cause trouble for the user, it’s probably a bad idea to use Lua, since that’s specifically the scenario Apple is trying to avoid with the “no interpreted code” stipulation, I’d wager. If, instead, you could compile Lua code into the executable, so that it runs as compiled native code instead of being interpreted at run-time, then you should be okay, I’d think. (For instance, this is how Adobe is publishing Flash content to the iPhone.)

  12. John says:

    For those of you concerned about running an interpreter on the iPhone, I don’t think there is much risk. First of all, there are MANY apps on the app store that have a built-in interpreter. Most of them, in fact, are using Lua. Lua comes bundled with the Box3d game engine that so many games are written in.

    There is a great deal of misinformation regarding Apple’s infamous rule 3.3.2. It states:

    3.3.2 — An Application may not itself install or launch other executable code by any means, including without limitation through the use of a plug-in architecture, calling other frameworks, other APIs or otherwise. No interpreted code may be downloaded and used in an Application except for code that is interpreted and run by Apple’s Published APIs and built-in interpreter(s).

    (Emphasis mine). The “and used” part goes with the “downloaded” part. If they meant you couldn’d do either one, then they would have worded it “downloaded or used”. Besides, the last part about built-in interpreter(s) is a pretty strong indication that internal interpreter are allowed.

  13. jeremiah says:

    Dan, Egor, John –

    The SDK agreement 3.3.2 used to read as John quotes; however, it was modified slightly in the latest revision. It now reads:

    3.3.2 — An Application may not itself install or launch other executable code by any means, including without limitation through the use of a plug-in architecture, calling other frameworks, other APIs or otherwise. No interpreted code may be downloaded or used in an Application except for code that is interpreted and run by Apple’s Documented APIs and built-in interpreter(s).

    Note that it used to say “No interpreted code may be downloaded and used…” but not it says “or used”.

    This may be a problem.

  14. John, Jeremiah
    Just fyi, that seemingly minute change was made back in the 3.0 license:
    https://uikit.com/interpret

  15. esummers says:

    It’s not a problem if you compile your scripts before sending to the appstore. See llvm-lua. It supports cross-compiling to native arm code.