Get started with Lua (3/3)

CodeFlow native bridge

This third article of the Get started with Lua series provides an overview of the native bridge, the software layer allowing you to transparently mix Lua and native code in your application. In this article, you will learn how to use native objects in your Lua code, how you can use C structs, enums, and most other types in Lua, and how easy it is to make your Lua objects visible (and callable) from the native code.

This article suppose that you already know Lua, or at least that you feel comfortable reading basic Lua code. If you are not, you can read the first article in this series, Get started with Lua - The Lua language, that gives a simple but reasonably detailed introduction to the Lua language.

You also need to be familiar with the CodeFlow object framework; if you have no idea of what this object framework is, please read the corresponding section in Get started with Lua - CodeFlow additions to Lua.

This article includes many code examples introduced by short explanation texts. Comments in the code bring explanations and additional information, so they are probably worth reading. All code examples are valid tested code that you can execute in CodeFlow, and you can use the debugger to get an in-depth understanding of their behavior if you want.

There are several ways to use this article: you can read it sequentially, or you can use it as a quick reference of CodeFlow native bridge, by jumping directly to a specific topic via the article's table of content (use the button on the left to display it if hidden).

Part 3 - CodeFlow native bridge

CodeFlow includes a C / Objective-C bridge that allows you to use transparently in Lua, C and Objective-C APIs defined by the target platform SDK, as well as APIs defined by the native code in your application. Swift classes and APIs are supported too, provided they are visible from Objective-C, (for details, see Apple document Using Swift with Cocoa and Objective-C).

The CodeFlow native bridge functionality is provided by software packages called Bindings Libraries. A Bindings Library contains native bridging information for a given target platform SDK or for the native code in an Xcode project. Internally, it includes Lua interface code describing the exposed native API, and binary libraries to be linked with the target application.

Using native objects in Lua

Native objects in CodeFlow are integrated in the CodeFlow object framework. So you basically handle native classes and instances just like you do with classes and instances created in Lua.

But native objects have their own specificities, and this section provides an overview of the main things that you'll need to know in order to use native objects effectively in Lua.

All code examples in this section use classes of the Foundation framework. Therefore you can test any of them directly in a CodeFlow Local Lua Context (press ⌘⇧N to create one), without having to create a separate application. Once you have pasted a code sample in the Lua editor in CodeFlow, you can run it using the debug toolbar or the Execute menu.

Native objects look like Lua objects

The global variable objc gives you access to native classes from Lua.

-- Native classes are accessed via the 'objc' global table
local NSURL = objc.NSURL  -- local variable NSURL is now a reference to the native class NSURL

print (NSURL)  --> Class NSURL

-- You create a new instance using a 'new' method, like 'newXyz', corresponding to init method 'initXyz'
local photosAppUrl = NSURL:newFileURLWithPath ("/Applications/Photos.app") -- create an instance using initFileURLWithPath internally

-- You can naturally call class methods
local codeflowDocUrl = NSURL:URLWithString ("https://www.celedev.com/en/documentation/")

-- Native objects are Lua values, so you can do basically anything with them
local urls = { photosAppUrl, codeflowDocUrl } -- store native instances in a Lua table

print (photosAppUrl)  --  pass a native instance to a Lua function
                      --> file:///Applications/Photos.app/

-- Native properties can be get or set using the classic Lua field syntax
print (photosAppUrl.scheme, photosAppUrl.host)      --> file    nil
print (codeflowDocUrl.scheme, codeflowDocUrl.host)  --> https    www.celedev.com

-- Calling an instance method is also straighforward
local photoAppResourceUrl = photosAppUrl:URLByAppendingPathComponent('Contents'):URLByAppendingPathComponent('Resources')
print (photoAppResourceUrl)  --> file:///Applications/Photos.app/Contents/Resources/

In native method names, '_' fills the gaps

Lua names and Objective-C (or Swift) method names do not follow the same pattern, so CodeFlow had to adopt a certain convention for expressing native method names in Lua:

Lua names of native methods are generated by replacing any ':' characters in the original Objective-C method selector (except the last one) with an underscore ('_'). For example, an ObjC method named indexOfObject:inRange: is translated into a Lua method named indexOfObject_inRange.

Native and Lua types work well together

When calling native methods or getting/setting native object properties, the native bridge automatically does the necessary conversions between Lua types and native types.

-- Conversions between Lua and native types are automatic, and you generally don't need to think about them
local array1 = objc.NSMutableArray:arrayWithObject (2.1)  -- converts 2.1 into a NSNumber and create an NSMutableArray containing it
array1:addObject ("lorem ipsum")                          -- converts "lorem ipsum" into a NSString and adds it to the array
array1:addObject (true)                                   -- converts true into the NSNumber @YES and adds it to the array

local a, b, c = array1[1], array1[2], array1[3]  -- converts objects in the array into the corresponding Lua types and assign them to local variables a, b, and c
                                                 -- notice that native object indexes start at 1, like in Lua tables

-- The actual type conversion depends of the expected native type
local transform = objc.NSAffineTransform:transform()  -- create an affine transform object, initialized to the identity matrix
transform:translateXBy_yBy(100, 25)  -- this method expects CGFloat parameters, so Lua numbers here are converted to GCFloats

-- Tables passed to a parameter specified as NSArray or NSDictionary are converted to the expected type
array1:addObjectsFromArray { transform, 100, 25, 'dolor', 'sit', 'amet' } -- the provided Lua table is converted to a NSArray, the expected parameter type of method `addObjectsFromArray`, before being passed to the method

-- On the other hand, if you pass a table parameter to a native method expecting a generic id/AnyObject, 
-- no conversion is made and the method receives a Lua table reference object of class `CIMLuaTable`
array1:addObject { 'consectetur', 'adipiscing', 'elit' } -- this Lua table is NOT converted, as the parameter to `addObject` is typed as an id/AnyObject
print (type (array1.lastObject))  --> table

-- In any case, you can force the conversion of a table to a NSArray or NSDictionary, by using convenience functions `objc.toArray` or `objc.toDictionary`
array1:addObject (objc.toArray { 'sed', 'do', 'eiusmod' })
print (type (array1.lastObject))  --> objcinstance

Strings can use native methods

Strings have access to methods of both Lua string library and Objective-C NSString class, which is quite a powerful mix:

local loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."

-- Using Lua strings library, invert any two successive words separated by spaces
local ipsumLorem = loremIpsum:gsub("(%w+)%s+(%w+)", "%2 %1")
print (ipsumLorem) --> ipsum Lorem sit dolor amet, adipiscing consectetur elit, do sed tempor eiusmod ut incididunt et labore magna dolore aliqua.

-- Using ObjC NSString methods, capitalize the previous string
local ipsumLoremCapitalized = ipsumLorem:capitalizedString()
print (ipsumLoremCapitalized) --> Ipsum Lorem Sit Dolor Amet, Adipiscing Consectetur Elit, Do Sed Tempor Eiusmod Ut Incididunt Et Labore Magna Dolore Aliqua.

Native blocks are functions

Objective-C blocks / Swift closures are equivalent to Lua functions:

-- Download an extract of the Wikipedia article about Lua

local wikipediaLuaUrl = objc.NSURL:URLWithString "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=Lua_(programming_language)"
local urlSession = objc.NSURLSession.sharedSession -- notice how a native class method without parameter can be used as a class field

-- This function is the completion handler for the download task.
-- Here we store it in a local variable, for better readability, but we could also define it inline in the call to `downloadTaskWithURL_completionHandler`
local function downloadCompletionHandler (location, response, error)
    -- location is a temporary file URL where the reply is stored
    local replyDictionary = objc.NSJSONSerialization:JSONObjectWithData_options_error(objc.NSData:newWithContentsOfURL(location), 0)

    if replyDictionary then
        local firstPageInReply = replyDictionary.query.pages.allValues.firstObject -- 'query' and 'pages' are keys in the reply dictionary, 'allValues' is a NSDictionary property returning an array of values in the dictionary, 'firstObject' is a NSArray property

        if firstPageInReply then
            print (firstPageInReply.extract)
        end
    end
end

 -- Create a download task and pass function `downloadCompletionHandler` as the completion handler block parameter
local downloadTask = urlSession:downloadTaskWithURL_completionHandler (wikipediaLuaUrl, downloadCompletionHandler)
-- Start the download
downloadTask:resume()

--> Lua (/ˈluːə/ LOO-ə, from Portuguese: lua [ˈlu.(w)ɐ] meaning moon; explicitly not "LUA" because it is not an acronym) is a lightweight multi-paradigm programming language designed primarily for embedded systems and clients. Lua is cross-platform since it is written in ANSI C, and has a relatively simple C API.
--> Lua was originally designed in 1993 as a language for extending software applications to meet the increasing demand for customization at the time. It provided the basic facilities of most procedural programming languages, but more complicated or domain-specific features were not included; rather, it included mechanisms for extending the language, allowing programmers to implement such features. As Lua was intended to be a general embeddable extension language, the designers of Lua focused on improving its speed, portability, extensibility, and ease-of-use in development.

Out parameters are results

Output parameters in native methods are transformed into Lua function results, which is both logical and elegant:

-- Get the modification date of the file at photosAppUrl

local NSURL = objc.NSURL  -- local variable NSURL is now a reference to NSURL native class
local photosAppUrl = NSURL:newFileURLWithPath ("/Applications/Photos.app") -- create an instance using initFileURLWithPath internally

-- Get URL-related constants by requiring module 'Foundation.NSURL' in the SDK bindings library (returning a Lua table)
local NsURL = require "Foundation.NSURL" -- `require` returns a table containing the constants for this module, and we store it in a variable named according to the module, but not hiding the 'NSURL' variable

-- Output parameters of native methods become method results in Lua (including NSError**/NSErrorPointer parameters)

local isOk, modificationDate, error = photosAppUrl:getResourceValue_forKey_error(NsURL.ContentModificationDateKey)

if isOk then
    print ("Photos app modif date: ", modificationDate) --> Photos app modif date:  2016-03-22 08:37:22 +0000
else
    print ("Cannot get Photos app modif date: ", error)
end

-- Similarly blocks output parameter (like ObjC `BOOL* stop` or Swift `UnsafeMutablePointer<ObjCBool>`) become results of the block function
local photoAppContentsUrl = photosAppUrl:URLByAppendingPathComponent('Contents')
local fileManager = objc.NSFileManager.defaultManager
local photosAppContentsItems = fileManager:contentsOfDirectoryAtURL_includingPropertiesForKeys_options_error (photoAppContentsUrl, {}, 0)

photosAppContentsItems:enumerateObjectsUsingBlock (function (childUrl, index)
                                                       print (childUrl.lastPathComponent) 
                                                       if index == 5 then
                                                           print "stopping enumeration"
                                                           return true -- stop the enumeration
                                                       end
                                                   end)
    --> _CodeSignature    Info.plist    Library    MacOS    PkgInfo    Resources
    --> stopping enumeration

Native collections behave like tables

Native collections have consistent table-like behaviors. So you can use the exact same syntax and operators, whether you use tables or native collection objects in your Lua code.

-- Read the contents of the 'Photos' application bundle
local photoAppContentsUrl = objc.NSURL:newFileURLWithPath ("/Applications/Photos.app/Contents/")
local fileManager = objc.NSFileManager.defaultManager
local NsURL = require "Foundation.NSURL" -- Get URL-related constants by requiring module 'Foundation.NSURL' in the SDK bindings library (returning a Lua table)

-- The contents of a directory is returned as an array of NSURLs
local photosAppContentsItems = fileManager:contentsOfDirectoryAtURL_includingPropertiesForKeys_options_error (photoAppContentsUrl, {NsURL.CreationDateKey}, 0)

-- Use the # operator to get the collection's elements count
print (#photosAppContentsItems) --> 8
-- use indexing to get a collection element (note that array indexes start at 1)
print (photosAppContentsItems [1]) --> file:///Applications/Photos.app/Contents/_CodeSignature/

-- create a native dictionary for storing contents items creation dates
local contentsItemCreationDates = objc.NSMutableDictionary:new()

-- enumerate array elements using Lua for-in loops
for childUrl in photosAppContentsItems do  -- no need to use ipairs here because native collection instances can be passed as generators to for-in loops
    local childName = childUrl.lastPathComponent
    print (childName) --> _CodeSignature    Info.plist    Library    MacOS    PkgInfo    Resources    version.plist    XPCServices

    -- get the child creation date and adds it to the contentsItemCreationDates dictionary
    local hasDate, creationDate = childUrl:getResourceValue_forKey_error(NsURL.CreationDateKey)
    if hasDate then
        contentsItemCreationDates [childName] = creationDate -- set a NSDictionary element using the indexed notation
    end
end

-- Use the contentsItemCreationDates dictionary
print (contentsItemCreationDates["PkgInfo"])  --> 2015-08-24 06:25:27 +0000   (indexed notation)
print (contentsItemCreationDates.XPCServices) --> 2015-08-24 06:25:32 +0000   (field notaion)

for name, date in pairs(contentsItemCreationDates) do -- enumerate the dictionary using 'pairs'
    print(name, date) --> version.plist     2016-02-14 07:54:31 +0000
                      --> _CodeSignature    2015-08-24 06:25:51 +0000
                      --> ...
end

Using Lua objects from native code

In the previous section, we have discussed how CodeFlow native bridge makes native objects and methods available to your Lua code. But you couldn't write an application in Lua if the bridge wasn't also working in the other direction: making your Lua objects and methods visible (and callable) from the native world is mandatory for using common design patterns like delegation, target-action or subclass-to-customize.

This section will present the main mechanisms provided by CodeFlow for exposing Lua objects to native code: methods publishing, native class subclassing, and native class extensions.

Some examples in this section are iOS-specific, and so can not be run in a CodeFlow local Lua context window. If you want to try them anyway, you can create an empty iOS application project in CodeFlow, and integrate the code samples in this application.

Only methods with known interfaces are published

In C / Objective-C native code, a function can only be called if the specific type of each of its parameters is known. In Lua, on the other hand, function parameters are not typed, and a parameter can receive a value of any type when the function is called.

For this reason, methods and properties defined in a Lua class are by default not visible from the native code. For a Lua method to be called by native code, this method must declare an Objective-C method interface. And actually, any Lua object method for which an Objective-C interface is known, can be called from native code, just as if it were a regular Objective-C method.

Lua classes can adopt ObjC protocols

There are a few different ways to declare a native interface for a Lua method. The simplest and most flexible one is to use an Objective-C protocol. If you are not familiar with Objective-C, a protocol is a group of method and properties definitions, that a class can implement for a given purpose, similar to an interface in Java or C#.

By declaring that it conforms to a given protocol, a Lua class publishes its own implementation of the protocol's methods and properties to the native world. This declaration is done by calling a class method named publishObjcProtocols, as shown in the next code sample.

Don't be afraid of this rather long example! It is actually pretty simple. The important points in it are the call to JsonUrlLoader:publishObjcProtocols ('NSURLSessionDownloadDelegate') and the 3 methods of this protocol implemented by the class, that are not in any way different from other methods.

-- A Lua class that loads Json files using ObjC NSURLSession and acting as the delegate of its NSURLSession

local JsonUrlLoader = class.createClass ("JsonUrlLoader")

-- Instance initializer
function JsonUrlLoader:init() 
    -- create an URLSession and pass self (a Lua instance!) as the delegate
    local urlSessionConfiguration = objc.NSURLSessionConfiguration.defaultSessionConfiguration
    self.urlSession = objc.NSURLSession:sessionWithConfiguration_delegate_delegateQueue (urlSessionConfiguration, self, objc.NSOperationQueue.mainQueue)

    -- create a table of completion handlers, so several json downloads can be done in parallel
    self.completionHandlers = {} -- a table [downloadTask] --> completionHandler
end

-- Main method called by a client to download a JSON URL 
function JsonUrlLoader:getJsonAtURL_WithCompletionHandler (url, completionHandler --[[@type function(jsonObject, error)]]) -- the @type comment is only there for documenting the completionHandler parameter
    -- Create a download task
    local downloadTask = self.urlSession:downloadTaskWithURL (url)
    -- Save the completion handler for this task
    self.completionHandlers [downloadTask] = completionHandler -- remember, a Lua table key can be any value except nil, so it is ok to use a native object as a key
    -- Start the download
    downloadTask:resume()
end

-----------------------------------------------------------
-- Declare that class JsonUrlLoader adopts the 'NSURLSessionDownloadDelegate' protocol
JsonUrlLoader:publishObjcProtocols ('NSURLSessionDownloadDelegate')

-- Implement methods declared in this protocol. These 3 methods will be callable from native code

function JsonUrlLoader:URLSession_task_didCompleteWithError (session, task, error)
    -- If an error occurred, call the task's completion handler, else URLSession_downloadTask_didFinishDownloadingToURL will have handled it
    if error then
        self:callCompletionHandler (task, nil, error)
    end        
end

function JsonUrlLoader:URLSession_downloadTask_didFinishDownloadingToURL (session, downloadTask, location)
    -- location is a temporary file URL where the reply is stored
    if location then
        -- convert the downloaded file to a JSON object
        local jsonObject, jsonError = objc.NSJSONSerialization:JSONObjectWithData_options_error(objc.NSData:newWithContentsOfURL(location), 0)
        -- call the completion handler
        self:callCompletionHandler (downloadTask, jsonObject, jsonError)
    end
end

function JsonUrlLoader:URLSession_downloadTask_didWriteData_totalBytesWritten_totalBytesExpectedToWrite (session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
    -- Print a progress message
    print (string.format("Downloaded %d Bytes (of %d) from %s", 
                         totalBytesWritten, totalBytesExpectedToWrite, downloadTask.currentRequest.URL.host))
end
-----------------------------------------------------------

-- internal method
function JsonUrlLoader:callCompletionHandler (downloadTask, ...)
    -- Call the registered completion handler for the specified download task 
    local taskCompletionHandler = self.completionHandlers [downloadTask]
    if taskCompletionHandler then
        -- call the completion handler with the provided additional parameters
        taskCompletionHandler (...)
        -- remove it from the completion handlers table
        self.completionHandlers [downloadTask] = nil
    end
end

return JsonUrlLoader
-- Use the JsonUrlLoader to get the text of the wikipedia page about Lua
local wikipediaLuaUrl = objc.NSURL:URLWithString "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&explaintext=&titles=Lua_(programming_language)"

local JsonUrlLoader = require "JsonUrlLoader" -- Load the JsonLoader class

-- Create a Json URL loader
local jsonLoader = JsonUrlLoader:new()

-- Load the wikipedia page text about Lua
jsonLoader:getJsonAtURL_WithCompletionHandler (wikipediaLuaUrl, 
             function(jsonObject, error)
                 if jsonObject then
                     local firstPageInReply = jsonObject.query.pages.allValues.firstObject -- 'query' and 'pages' are keys in the reply dectionary, 'allValues' is a NSDictionary property returning an array of values in the dictionary, 'firstObject' is a NSArray property

                     if firstPageInReply then
                         print (firstPageInReply.extract)
                     end
                 elseif error then
                     print ("Error", error.localizedDescription)
                 end
             end)

--> Downloaded 6860 Bytes (of -1) from en.wikipedia.org
--> Downloaded 25224 Bytes (of -1) from en.wikipedia.org

--> Lua (/ˈluːə/ LOO-ə, from Portuguese: lua [ˈlu.(w)ɐ] meaning moon; explicitly not "LUA" because it is not an acronym) is a lightweight multi-paradigm programming language designed primarily for embedded systems and clients. Lua is cross-platform since it is written in ANSI C, and has a relatively simple C API.
--> Lua was originally designed in 1993 as a language for extending software applications to meet the increasing demand for customization at the time. It provided the basic facilities of most procedural programming languages, but more complicated or domain-specific features were not included; rather, it included mechanisms for extending the language, allowing programmers to implement such features. As Lua was intended to be a general embeddable extension language, the designers of Lua focused on improving its speed, portability, extensibility, and ease-of-use in development.
--> ...

Lua classes can declare action methods

The target-action design pattern is widely used in iOS and MacOS. The idea behind target-action is simple: a user interface component is dynamically connected to a target object that implements an action method, and when the user interacts with it in a certain way (for example a press on a button), the corresponding action method of the target object is called.

You can define action methods in a Lua class. You just need to publish them, using the class method publishActionMethod, to make them visible from the native code.

local GestureController = class.createClass ("GestureController")

-- Define an action method
function GestureController:handleSwipeGesture (recognizer --[[@type objc.UISwipeGestureRecognizer]])
    -- Handle the swipe gesture
    local swipeDirection = recognizer.direction -- the direction of the swipe (right, left, up or down)
    local location = recognizer:locationInView(self.view) -- the location of the gesture inside the current view
    -- ...
end

-- Declare handleSwipeGesture as an action method, to make it callable from native code
GestureController:publishActionMethod ('handleSwipeGesture') -- the parameter is the method name

-- define a view property for this class
GestureController.view --[[@type objc.UIView]] = property()

function GestureController:SetView (aView) -- setter for the view property
    -- Update the internal view storage
    self._view = aView

    -- Create the gesture recognizer using self as the target and 'handleSwipeGesture' as the action
    local swipeRecognizer= objc.UISwipeGestureRecognizer:newWithTarget_action (self, 'handleSwipeGesture') -- in the action parameter you pass the method name (a string)
    -- Add the gesture recognizer to the view
    self._view:addGestureRecognizer (swipeRecognizer)
end

-- ...

return GestureController

Note that @type comments are used in the above code. They are just comments that have no impact on code execution. What they do is to help the code editor to suggest correct completions when you type the code, in cases where the class/type of a variable just can't be guessed by analyzing the code!

Native classes can be subclassed in Lua

Not surprisingly, the CodeFlow object framework can create subclasses of a native class, or said differently, you can define in Lua a class inheriting from a native Objective-C or Swift class. In practice, you create a subclass of a native class exactly like you create a subclass of a Lua class, by passing the superclass as the second parameter of function class.createCLass.

-- Create a custom view class named "MyViewClass"
local MyViewClass = class.createClass ("MyViewClass", objc.UIView)

-- Implement your custom drawing code
function MyViewClass:drawRect (rect)
    -- Your drawing code here
    -- ...
end

return MyViewClass

You can override methods of the native superclass, like drawRect() in the above example, and these overridden methods are automatically published to the native code. Additional methods in the class can be published if needed, as part of a protocol, or as action methods.

To illustrate this, here is complete runnable example that defines a subclass of iOS UIViewController. This view controller class overrides methods loadView() and viewDidLoad() to create and configure a UITableView. To control the TableView, our ViewController class adopts two protocols: UITableViewDataSource and UITableViewDelegate, and it implements a few basic methods of these protocols.

-- Create a View Controller class by subclassing native class UIViewController
local ViewController = class.createClass ("ViewController", objc.UIViewController)

local superclass = objc.UIViewController -- a convenience variable for calling superclass methods

-- Methods overridding native methods of the superclass

function ViewController:loadView ()
    -- Here the controller's view is created programmatically.    
    self.view = objc.UITableView:new()
end  

function ViewController:viewDidLoad ()
    self[superclass]:viewDidLoad() -- call the viewDidLoad method of the superclass

    self:configureView () -- configure the controller's view
end

local kCellIdentifier = "simple-cell" -- A local variable defining a shared constant string

-- Internal method (visible only from Lua code)

function ViewController:configureView ()
    local tableView = self.view 
    tableView.dataSource = self  -- the view controller is the tableView's data source
    tableView.delegate = self     -- the view controller is the tableView's delegate
    -- register a cell class for the tableView
    tableView:registerClass_forCellReuseIdentifier (objc.UITableViewCell, kCellIdentifier)
end

-- Publish protocols
ViewController:publishObjcProtocols { 'UITableViewDataSource', 'UITableViewDelegate' }

-- Methods defined in protocol UITableViewDataSource 

local sampleDataModel = { "Lorem ipsum", "dolor sit amet", "consectetur adipiscing elit", "sed do eiusmod tempor", "incididunt ut labore",  "et dolore magna aliqua" }

function ViewController:tableView_numberOfRowsInSection (tableView --[[@type objc.UITableView]], section --[[@type integer]])
    return #sampleDataModel
end

function ViewController:tableView_cellForRowAtIndexPath (tableView --[[@type objc.UITableView]], indexPath --[[@type objc.NSIndexPath]])
    local cell = tableView:dequeueReusableCellWithIdentifier_forIndexPath (kCellIdentifier, indexPath)

    cell.textLabel.text = sampleDataModel [indexPath.row + 1] -- +1 because Lua table indexes start at 1

    return cell
end

-- Methods defined in protocol UITableViewDelegate 

function ViewController:tableView_didSelectRowAtIndexPath (tableView --[[@type objc.UITableView]], indexPath --[[@type objc.NSIndexPath]])
    -- Print a message in the Lua console when the user selects a table cell
    print ("selected table row:", indexPath.row + 1)
end

-- return the ViewController class
return ViewController

Here is what you see when running this example in the iPhone simulator. You can select a row in in the table view and see the corresponding message "selected table row: n" printed in the Lua console.

The result of our ViewController

Native classes can have Lua extensions

Subclassing native classes in Lua is not always the most effective way of using Lua for application development. For example, Interface Builder in Xcode only knows about classes, outlets and actions declared in Swift or Objective-C, so you can not directly use in a storyboard a ViewController class defined in Lua. But don't worry, this is where class extensions come to the rescue!

Class extensions are a convenient way to split the definition of a Lua class into several modules, but you can also use a class extension to extend in Lua a native Objective-C or Swift class.

When you extend a native class in Lua, this class is converted into a full-featured Lua class, so you get the best of both worlds: the extended class combines the flexibility of the CodeFlow object framework with the possibility to be used in Xcode storyboards, or to implement some of its methods in Objective-C or Swift.

In addition, when extending a native class, you have the possibility to override native methods of the class in Lua, i.e. to replace the original native implementation of a method by the one you provide in Lua. You can even call the native implementation of an overridden method using the notation self[objc]:method(), as you can see in the next example.

As an example, let's rewrite the ViewController above using a class extension. Here, the ViewController class has been defined in Swift or Objective-C; table-view settings and configuration of the view controller as the table-view's data source and delegate, have been done in a storyboard.

-- ViewController is a class extension of the native 'ViewController' class defined in the associated Xcode project
-- (subclass of UIViewController, adopting protocols UITableViewDelegate and UITableViewDataSource)

local ViewController = class.extendClass (objc.ViewController) -- Convert the native 'ViewController' class into a Lua class

-- Redefine method viewDidLoad, already implemented in Swift
function ViewController:viewDidLoad()
    self[objc]:viewDidLoad() -- call the native version of the 'viewDidLoad' method

    self.tableView.allowsMultipleSelection = true -- property 'tableView' is delared in Swift and set in a storyboard
 end

-- Methods defined in protocol UITableViewDataSource 

local kCellIdentifier = "simple-cell" -- A local variable defining a shared constant string

local sampleDataModel = { "Lorem ipsum", "dolor sit amet", "consectetur adipiscing elit", "sed do eiusmod tempor", "incididunt ut labore",  "et dolore magna aliqua" }

function ViewController:tableView_numberOfRowsInSection (tableView --[[@type objc.UITableView]], section --[[@type integer]])
    return #sampleDataModel
end

function ViewController:tableView_cellForRowAtIndexPath (tableView --[[@type objc.UITableView]], indexPath --[[@type objc.NSIndexPath]])
    local cell = tableView:dequeueReusableCellWithIdentifier_forIndexPath (kCellIdentifier, indexPath)
    cell.textLabel.text = sampleDataModel [indexPath.row + 1] -- +1 because Lua table indexes start at 1
    return cell
end

-- Methods defined in protocol UITableViewDelegate 

function ViewController:tableView_didSelectRowAtIndexPath (tableView --[[@type objc.UITableView]], indexPath --[[@type objc.NSIndexPath]])
    print ("selected table row:", indexPath.row + 1)
end

return ViewController

Note that, with live storyboards, CodeFlow push the flexibility of native class extensions one step further, allowing you to add native properties and methods to a class on-the-fly, and to use them immediately, in storyboards and/or in Lua, without restarting the target application.

Using C entities in Lua

When developing an application, you generally need to use C entities: struct types, enum parameters, string constants, and C functions are widely used in Apple's SDKs, and of course, the CodeFlow native bridge exposes them to your Lua code.

Structs are table-like objects

C structs in CodeFlow are exposed as lightweight objects.

-- You get a struct type via the 'struct' global table
local CGPoint = struct.CGPoint -- variable CGPoint is now a reference to the CGPoint struct type

-- You create struct objects by calling the struct type as a constructor
local point1 = CGPoint (100, 25) -- in this first variant, you provides the struct fields as parameters, in the order in which they are defined
print (point1)  --> <CGPoint 0x60800026d130> { x= 100, y= 25 }

local point2 = CGPoint {x = 42, y = 84} -- in this second variant, you pass a table as the paramater. Field names must match the struct's field names!

-- Struct fields are accessed like table field
point2.y = point1.y  -- You can get and set individual field structs

-- Structs have methods!

-- some methods are predefined in bindings libraries, like GCPoint:equalToPoint()
print (point1:equalToPoint(point2)) --> false
point1.x = 42
print (point1:equalToPoint(point2)) --> true

-- And you can define your own struct methods if you want
function CGPoint:moveByOffset (dx, dy)
    self.x = self.x + (dx or 0)
    self.y = self.y + (dy or 0)
end

-- Call your custom method
point1:moveByOffset(-10, 384)  -- point1: <CGPoint 0x60800026d130> { x= 32, y= 409 }

-- Struct assignment work by reference, like for all Lua table/object values

local point3 = point1
print (point3)  -->  <CGPoint 0x60800026d130> { x= 32, y= 409 }  (point3 and point1 are references to the same struct object)
point3.x = 0
print (point1)  -->  <CGPoint 0x60800026d130> { x= 0, y= 409 }   (point1 is changed, because point1 and point3 are references to the same struct object)

-- If assign-by-reference is not what you want, you use the 'copy' method to duplicate a struct
point3 = point1:copy()
print (point3)  -->  <CGPoint 0x6180002686b0> { x= 0, y= 409 }   (point3 and point1 are references to different struct objects)
point3.x = 222  -- point3: <CGPoint 0x6180002686b0> { x= 222, y= 409 }
print (point1)  -->  <CGPoint 0x60800026d130> { x= 0, y= 409 }   (point1 is not changed, as point1 and point3 are references to different struct objects)

Some structs have fields that are structs:

-- Struct can be composed of structs
local rect = struct.CGRect { origin = point1, size = {width = 1920, height = 1080 }}
print (rect)  -->  <CGRect 0x610000099ee0> { origin= { x= 0, y= 409 }, size= { width= 1920, height= 1080 } }

-- getting and setting sub-fields  behave as expected
rect.size.height = 1200
print (rect)  --> <CGRect 0x610000099ee0> { origin= { x= 0, y= 409 }, size= { width= 1920, height= 1200 } }

-- calling struct method on struct fields also produce the expected result
rect.origin:moveByOffset (100, 120)
print (rect)  --> <CGRect 0x610000099ee0> { origin= { x= 100, y= 529 }, size= { width= 1920, height= 1200 } }

-- keep in mind that assignments are made by reference!
local someSize = rect.size
someSize.width = 1280   -- exactly the same as: rect.size.width = 1280, so it changes the width of rect
print (rect)  --> <CGRect 0x610000099ee0> { origin= { x= 100, y= 529 }, size= { width= 1280, height= 1200 } }

And because struct constructors accept table parameters, you can pass a table in place of a struct in a function, method or property:

-- In the context of an iOS View Controller controlling a table-view
local tableView = self.tableView

-- Set the center of the tableView (CGPoint property)
tableView.center = { x = 150, y = 200 }

-- Scroll the tableView to a certain offset (first parameter expects a CGPoint)
tableView:setContentOffset_animated ({ x = 0, y = 500 }, true)

Enums, constants and functions are in binding modules

To use C enum types, global constants, of global C functions, you first have to load (i.e. require) the Lua bindings module(s) in which they are defined. The result of a Lua bindings module is a table whose entries are the C entities defined in the corresponding native C or Objective-C header file in the SDK.

Let's make this clear through an example. If we consider the UIDevice Lua module in CodeFlow (located in the UIKit framework in the iOS bindings Library), the table it returns looks like this:

-- ...

local UiDevice = {
    Orientation --[[@def enum(UIDeviceOrientation)]] = { Unknown = 0,
                                                         Portrait = 1,
                                                         PortraitUpsideDown = 2,
                                                         LandscapeLeft = 3,
                                                         LandscapeRight = 4,
                                                         FaceUp = 5,
                                                         FaceDown = 6,
                                                       },
    BatteryState --[[@def enum(UIDeviceBatteryState)]] = { Unknown = 0,
                                                           Unplugged = 1,
                                                           Charging = 2,
                                                           Full = 3,
                                                         },
    UIUserInterfaceIdiom --[[@def enum(UIUserInterfaceIdiom)]] = { Unspecified = -1,
                                                                   Phone = 0,
                                                                   Pad = 1,
                                                                   TV = 2,
                                                                   CarPlay = 3,
                                                                 },
    OrientationIsPortrait = function (orientation --[[@type enum(UIDeviceOrientation)]]) --[[C function]] --[[@return bool]] end,
    OrientationIsLandscape = function (orientation --[[@type enum(UIDeviceOrientation)]]) --[[C function]] --[[@return bool]] end,
    OrientationDidChangeNotification --[[@type string]] = "?" --[[ C extern const variable]],
    BatteryStateDidChangeNotification --[[@type string]] = "?" --[[ C extern const variable]],
    BatteryLevelDidChangeNotification --[[@type string]] = "?" --[[ C extern const variable]],
    ProximityStateDidChangeNotification --[[@type string]] = "?" --[[ C extern const variable]],
}

return UiDevice

We can see that the UIDevice module table includes the definition of 3 enums (UiDevice.Orientation, UiDevice.BatteryState and UiDevice.UIUserInterfaceIdiom), 2 C functions (UiDevice.OrientationIsPortrait and UiDevice.OrientationIsLandscape), and 4 C extern const variables (UiDevice. OrientationDidChangeNotification...).

Here is an example showing how constants, enums and functions in this module can be used:

-- This module defines a class extension of a native class 'DeviceInfoViewController'
-- The view of a DeviceInfoViewController contains a UILabels for displaying the battery state and a progress view for the battery level

local UiDevice = require 'UIKit.UIDevice' -- load the UIDevice module located in the iOS bindings library 

local DeviceInfoViewController = class.extendClass (objc.DeviceInfoViewController) --[[@inherits objc.UIViewController]]

local currentDevice = objc.UIDevice.currentDevice

function DeviceInfoViewController:viewDidLoad ()
    self[objc]: viewDidLoad() -- call the native viewDidLoad method if any

    currentDevice.batteryMonitoringEnabled = true -- Enable battery monitoring
    self:updateBatteryInformation() -- Update the battery information

    -- Monitor battery-related notifications. Notification names are stored in the UiDevice table
    local notificationCenter = objc.NSNotificationCenter.defaultCenter
    notificationCenter:addObserver_selector_name_object (self, "updateBatteryInformation", 
                                                         UiDevice.BatteryStateDidChangeNotification, nil)
    notificationCenter:addObserver_selector_name_object (self, "updateBatteryInformation", 
                                                         UiDevice.BatteryLevelDidChangeNotification, nil)

end

-- Define a table that converts battery state enum values into strings to be used in the battery state label
local BatteryState = UiDevice.BatteryState -- put the BatteryState enum in a local variable
local BatteryStateStrings = { [BatteryState.Unplugged] = "Unplugged",
                              [BatteryState.Charging]  = "Charging",
                              [BatteryState.Full]      = "Battery full!"
                            }

function DeviceInfoViewController:updateBatteryInformation(notification)
    -- update the UI components for the current battery state
    self.batteryLevelIndicator.progress = currentDevice.batteryLevel
    self.batteryLevelLabel.text = string.format ("%s - %d%%", 
                                                 BatteryStateStrings [currentDevice.batteryState] or "?",
                                                 currentDevice.batteryLevel * 100)
end

-- publish method updateBatteryInformation (a notification handler has the same interface as a single-parameter action method)
DeviceInfoViewController:publishActionMethod("updateBatteryInformation")

return DeviceInfoViewController

If you run this example, it displays the device battery status like this:

The device info ViewController in action

Notice how elements of the bindings module table can have shorter names when Cocoa naming conventions are respected: for example, the C enum value UIDeviceBatteryStateUnplugged is translated into the full Lua table path UiDevice.BatteryState.Unplugged, and if the battery state enum is stored in a local variable BatteryState, the enum value can be written as BatteryState.Unplugged.

Other C types are opaque values

When you write Lua code using C APIs, you often have to deal with value types that have no Lua equivalent, but that are nevertheless required to use the API. For example, the CoreGraphics framework in iOS makes a heavy usage of "ref" types (CGContextRef, CGColorRef…); "ref" types are actually pointer types and have no meaningful equivalent in Lua or in the CodeFlow object framework. However you can still use them in your Lua code.

The CodeFlow native bridge treats values of unknown C types as opaque values. When you get an opaque C value as the result of a function or method, you can store it in a variable, and you can use it later as a parameter in function or method calls. And actually, this is all you need for dealing with unknown C types in your Lua code.

To illustrate this, here is a code sample using the CoreGraphics framework:

-- This module defines a class extension of 'GradientView', an empty UIView subclass defined in Swift

-- Loads needed SDK bindings modules
local UiGraphics = require 'UIKit.UIGraphics'
local CgContext = require 'CoreGraphics.CGContext'
local CgGradient = require 'CoreGraphics.CGGradient'
local CgColorSpace = require 'CoreGraphics.CGColorSpace'

local GradientView = class.extendClass(objc.GradientView)

-- An internal function that creates a CGGradient
local function createGradientInRGBSpaceWithColorComponents (gradientColorComponents, locations)
    local colorSpace = CgColorSpace.CreateDeviceRGB()  -- colorSpace contains an opaque CGColorSpaceRef value
    local gradient = CgGradient.CreateWithColorComponents(colorSpace, gradientColorComponents, locations, #locations)
    CgColorSpace.Release(colorSpace) -- with opaque C values, you have to take care of memory management by yourself
    return gradient  -- returns the gradient, a CGGradientRef value
end

-- Create the gradient and store it in a local variable, used as an upvalue by the drawRect method
local backgroundGradient = createGradientInRGBSpaceWithColorComponents 
                           ({ 251 / 255, 247 / 255, 234 / 255, 1.0,
                              252 / 255, 205 / 255, 063 / 255, 1.0,
                               20 / 255,  33 / 255, 104 / 255, 1.0,
                              181 / 255,  33 / 255, 134 / 255, 1.0 },
                            {0.0, 0.5, 0.5, 1.0})

-- Define a custom drawRect method that draws the gradient in the view
function GradientView:drawRect (rect)
    local ctx = UiGraphics.GetCurrentContext() -- ctx contains an opaque CGContextRef value
    local startPoint = { x = 0.0, y = 0.0 }
    local endPoint   = { x = 0.0, y = self.bounds.size.height }
    CgContext.DrawLinearGradient (ctx, backgroundGradient, startPoint, endPoint, 0)
end

return GradientView

By setting GradientView as the class of the view in the battery monitor app above, we can see how the gradient background looks like:

The device info ViewController with a GradienView background

Wrapping up

This last part of our Get Started with Lua series is now reaching its end, and I hope that you have a clearer view of what the CodeFlow native bridge does and how to use it.

If you have read all three articles in the series, you should have everything in hand to start writing a first reasonably complex iOS, tvOS or MacOS application in Lua. And if you feel like you need a quick refresh on any of the topics covered in these articles, you can jump directly to the corresponding section using the tables of contents in the article pages. Actually you can use these Get Started with Lua articles as a quick reference to the Lua language and extensions used in CodeFlow: they have also been written with this usage in mind.

Where to go next? If you haven't done it yet I strongly recommend watching the Hello World tutorial video or reading the article (available soon) Get started with CodeFlow. It (will) contains essential information about how to create an application project in CodeFlow, and how to build this application in live-coding mode, by combining storyboard edits in Xcode and Lua code edits in CodeFlow, while running the application on your device.

If you need a deeper understanding of the CodeFlow object framework, the CodeFlow bridge, or other topics not covered here like application resources updates, or messages and asynchronous apis in CodeFlow, you can read the CodeFlow API documentation.

And if you have comments, questions or suggestions about the topics covered in this article, please leave a comment below, or send us an email, or a message on twitter. We will be happy to help.

Post a Comment