More to come. Please read the README.rtf in the meantime.
The Obj-C & C API documentation can be found here. (There is also an Xcode DocSet in the binary distribution which contains a copy of this.)
The LuaCocoa framework includes:
Lua 5.1
LNUM Patch
LPeg
Leg
Also bundled with the framework are command line utilities:
lua
luac
luacocoa_shell
Included Examples:
Included with both the source code and the binary distribution are several example applications.
MinimalAppKit:
Shows how to build a barebones Cocoa/windowed app in pure Lua. Run this with the luacocoa_shell command line utility.
SimpleLuaView:
Shows how to write a view subclass in Lua with scaffolding in Obj-C for the main app and nib file.
SimpleLuaOpenGLView:
Similar to SimpleLuaView but shows off BridgeSupport automatic binding for OpenGL and does the Apple golden triangle example from their docs.
ScriptingBridge_iTunes:
Using Scripting Bridge, shows how to do Applescript things written in Lua instead of Applescript. Simply opens iTunes and plays a song. Run this with the luacocoa_shell command line utility.
HybridCoreAnimationScriptability:
This is the most important example in my opinion. This example was inspired by how I actually used the LuaObjCBridge in a past project. I had a main Obj-C application, but wanted to write the Core Animation UI in Lua. This would allow UI designers to actually implement the UI without needing to recompile the app or work in Obj-C. I also built in a way to edit the Lua script easily while the program was running and would auto-reload the Lua script when modified (detected via FSEvents) without relaunching the entire program.
Test.app:
Also in the Xcode project, there is a target called Test.app. It contains a slew of many different, random things (but sadly not comprehensive) of what the LuaCocoa bridge can do.
Getting started on the Objective-C side:
Embed the LuaCocoa.framework in your app. It uses @rpath so you should set your app's "Runtime Search Paths" (in your Xcode build settings) to @loader_path/../Frameworks.
From your code, use
#import <LuaCocoa/LuaCocoa.h>
which will bring in both LuaCocoa and all the Lua headers.
A typical setup sequence looks like:
- (void) setupLuaCocoa
{
LuaCocoa* lua_cocoa = [[LuaCocoa alloc] init];
luaCocoa = lua_cocoa;
lua_State* lua_state = [lua_cocoa luaState];
NSString* the_path;
int the_error;
the_path = [[NSBundle mainBundle] pathForResource:@"MyLuaScript" ofType:@"lua"];
the_error = luaL_loadfile(lua_state, [the_path fileSystemRepresentation]);
if(the_error)
{
NSLog(@"luaL_loadfile failed: %s", lua_tostring(lua_state, -1));
lua_pop(lua_state, 1); /* pop error message from stack */
exit(0);
}
the_error = lua_pcall(lua_state, 0, 0, 0);
if(the_error)
{
NSLog(@"Lua parse load failed: %s", lua_tostring(lua_state, -1));
lua_pop(lua_state, 1); /* pop error message from stack */
exit(0);
}
}
Getting started on the Lua side:
Embed LuaCocoa.framework in your app and use the API to invoke or use the luacocoa_shell command-line tool for stand-alone Lua scripts.
To access functionality from a framework (i.e. load a framework), use LuaCocoa.import(), e.g.
LuaCocoa.import("Foundation")
LuaCocoa.import("AppKit")
LuaCocoa.import("QuartzCore")
LuaCocoa.import("OpenGL")
If the framework doesn't reside in the standard Frameworks directory, you may specify an optional path.
LuaCocoa.import("CoreGraphics", "/System/Library/Frameworks/ApplicationServices.framework/Frameworks")
Once the framework is loaded, you can access the classes, constants, enums, functions, and structs by variable name.
NSBeep() -- call the function NSBeep
print(NSIntegerMax) -- prints 9223372036854775807 for 64-bit or 2147483647 for 32-bit
local ca_layer = CALayer:alloc():init() -- creates a new instance of CALayer
Like other Obj-C bridges, the convention is that colon parameters are replaced by underscores, e.g.
local hello_string = NSString:alloc():initWithUTF8String_("Hello World")
print(hello_string) -- hello_string is an NSString but has a metatable to behave like a Lua string when appropriate
Also note if you pass non-nil values into methods that return values by reference, those values will be returned as multiple return parameters.
Also note that Objective-C functions that return BOOL will return real Lua booleans and not numbers/integers unlike the LuaObjCBridge.
local file_exists, is_dir = NSFileManager:defaultManager():fileExistsAtPath_isDirectory_("/notthere", isDir))
local dir_list, the_error = NSFileManager:defaultManager():contentsOfDirectoryAtPath_error_("/notthere", true)
print(the_error)
NSString, NSDictionary, NSArray, NSNumber, NSNull are given special metatables so you can use Lua syntax with them.
local dictionary = NSMutableDictionary:alloc():init()
dictionary:setObject_forKey_(goodnight_string, hello_string) -- Obj-C style set
print("Dictionary[hello_string]", dictionary[hello_string]) -- Lua style get
dictionary[goodnight_string] = hello_string -- Lua style set
dictionary["something_new"] = "A new string" -- Lua style set, new string will auto-convert to NSString so it can exist in an NSDictionary
print("dictionary length:", #dictionary)
Converting between Lua and Cocoa types:
While using metatables on native Cocoa objects is fast and flexible, sometimes you need to convert the objects to pure Lua types. The functions LuaCocoa.toLua and LuaCocoa.toCocoa will allow you to explicitly copy and convert between types.
-- Convert the NSDictionary to a Lua table. Elements also get converted.
local lua_dictionary = LuaCocoa.toLua(dictionary)
-- Convert to Cocoa
local ns_dictionary = LuaCocoa.toCocoa(lua_dictionary)
note if you pass non-nil values into methods that return values by reference, those values will be returned as multiple return parameters.
Structs:
Lots of syntactic sugar has been provided for structs to make dealing with them more natural in Lua.
ns_point = NSMakePoint(100, 200)
print("NSPoint", ns_point)
print("NSPoint[1]", ns_point[1])
print("NSPoint[2]", ns_point[2])
print("NSPoint.x, NSPoint.y", ns_point.x, ns_point.y)
ns_rect = NSMakeRect(300, 400, 500, 600)
return_point = ns_rect.origin
return_size = ns_rect.size
print("return_point.x", return_point.x)
print("ns_rect.origin.x and y, ns_rect.size.width and height", ns_rect.origin.x, ns_rect.origin.y, ns_rect.size.width, ns_rect.size.height)
print("set return_point.x = 350.0, y=450.0")
return_point.x = 350.0
return_point.y = 450.0
print("return_point.x, 2", return_point.x, return_point[2])
print("assigning: ns_point = return_point")
ns_point = return_point
print("ns_point[1], y", ns_point[1], ns_point.y)
print("Testing table coercion in Struct setter")
ns_rect.origin = {1000, 2000}
print("Changed ns_rect.origin with table ns_rect.origin = {1000, 2000}", ns_rect)
ns_rect.size = {height=4000, width=3000}
print("Changed ns_rect.size with table ns_rect.size = {height=4000, width=3000}", ns_rect)
print("Testing table coercion in Struct __call")
ns_point({9.0, 10.0})
print("ns_point({9.0, 10.0})", ns_point)
ns_rect({ {1001, 2002}, {3003, 4004}})
print("ns_rect({ {1001, 2002}, {3003, 4004}})", ns_rect)
ns_rect({ 1011, 2022, 3033, 4044})
print("ns_rect({ 1011, 2022, 3033, 4044})", ns_rect)
ns_rect(1111, 2222, 3333, 4444)
print("ns_rect(1111, 2222, 3333, 4444)", ns_rect)
ns_rect({1110, 2220}, {3330, 4440})
print("ns_rect({1110, 2220}, {3330, 4440})", ns_rect)
ns_rect({x=1010, y=2020}, {width=3030, height=4040})
print("ns_rect({x=1010, y=2020}, {width=3030, height=4040})", ns_rect)
ns_rect({x=0010, [2]=0020}, {[1]=0030, height=0040})
print("ns_rect({x=0010, [2]=0020}, {[1]=0030, height=0040})", ns_rect)
ns_rect({origin = {x=10101, y=20202}, size = {width=30303, height=40404}})
print("ns_rect({origin = {x=10101, y=20202}, size = {width=30303, height=40404}})", ns_rect)
ns_rect({origin = {[1]=10111, [2]=20222}, [2] = {width=30333, height=40444}})
print("ns_rect({origin = {[1]=10111, [2]=20222}, [2] = {width=30333, height=40444}})", ns_rect)
print("Testing Struct Constructors")
print("NSPoint should be a function", NSPoint)
local my_new_point = NSPoint({11.0, 12.0})
print("my_new_point = NSPoint({11.0, 12.0})", my_new_point)
print("NSRect should be a function", NSRect)
local my_new_rect = NSRect({origin = {[1]=11111, [2]=22222}, [2] = {width=33333, height=44444}})
print("my_new_rect = NSRect({origin = {[1]=11111, [2]=22222}, [2] = {width=33333, height=44444}})", my_new_rect)
Subclassing:
Use LuaCocoa.CreateClass() to declare a new class. The first parameter is the class name, the second parameter is the base class. Optionally, you can list protocols that the class conforms to.
my_subclass = LuaCocoa.CreateClass("MyLuaClass", NSNumberFormatter)
To override a method, do it like setting a key on a table, where the key is the method name. You must assign it an array of two elements, where one value is a Lua function and the other value is a string representing the Obj-C method signature. The method signature is important for allowing methods to be called from the Obj-C side. As an implementation detail, if you are subclassing an existing method, you don't have to get the method signature correct.
Note calls to super must contain the super class as the parameter.
my_subclass["decimalSeparator"] =
{
"-@@:",
function (self) print("in new_class for decimalSeparator");
local ret_val = self:super(NSNumberFormatter):decimalSeparator();
ret_val = "==" .. ret_val .. "==";
print("ret_val is", ret_val);
return ret_val;
end
}
my_subclass["doSomething2"] =
{
function (self)
print("in subclass doSomething2")
end,
"-v@:"
}
my_subclass["doSomething3withaBool_aDouble_anInteger_aString_anId_"] =
{
function (self, a_bool, a_double, an_integer, a_string, an_id)
print("in subclass doSomething3, arglist:", self, a_bool, a_double, an_integer, a_string, an_id)
local ret_string = NSString:stringWithUTF8String_(a_string)
print("ret string is", ret_string)
print("ret string description is", ret_string:description())
return ret_string
end,
"-@@:Bdi*@"
}
my_subclass["doSomething4withPointer_"] =
{
function (self, a_pointer)
print("doSomething4withPointer_")
self:super(NSNumberFormatter):doSomething4withPointer_(a_pointer)
end,
"-v@:^v"
}
For creating instance variables on the Lua side, there is a special table provided called __ivars that you can add things to.
Subclassing: Back in Objective-C
Class new_class = NSClassFromString(@"MyLuaClass");
id new_instance = [[new_class alloc] init];
[new_instance setLuaCocoaState:lua_state]; // Special requirement: Lua objects need their lua_State
NSString* ret_string = [new_instance doSomething3withaBool:true aDouble:2.0 anInteger:3 aString:"hello world" anId:the_path];
NSLog(@"Ret string: %@", ret_string);
Gotchas:
Variadics:
Variadic functions and methods without printf format strings cannot auto-coerce types, e.g.
local array = NSArray:alloc():initWithObjects_("lua string", true, 1.0, nil)
The above will not auto-coerce to NSString, NSNumber, and NSNumber.
You must explicitly convert those types first,
But variadics with format strings will auto-coerce. e.g.
local nsstring = NSString:alloc():initWithUTF8String_("Cocoa string")
NSLog("%@, %@, %@, %s", "lua string", true, 1.0, nsstring)
Equality:
Lua rules dictate that if the Lua types are different, equality always returns false.
So comparing a Lua string and NSString will always return false unless you coerce the types correctly.
local nsstring = NSString:alloc():initWithUTF8String_("some string")
local lua_string = "some string"
-- Always false
if nsstring == lua_string then
print("Never happens")
end
-- Instead do this:
if tostring(nsstring) == lua_string then
print("equality match")
end
-- Or do this
if nsstriing:isEqualToString_(lua_string) then
print("equality match")
end