1. Introduction
2. Requirements
3. Installation
4. Getting Started
5. Sample External Projects
6. Interface Definition Language
7. Libraries and Frameworks
8. API
9. Objective-C Naming Requirements
10. Build Process Details
11. Compatibility
12. Troubleshooting
LiveCode provides an iOS externals interface which allows you to extend the existing iOS feature set using a lower level language like C, C++ or Objective C.
THIS RELEASE IS FOR IOS EXTERNALS ONLY
This SDK does not replace the existing LiveCode externals interface for the desktop and web platforms.
You can also extend LiveCode functionality on the desktop platform. For more details see this 'externals tutorial series' here.
Before installing the SDK ensure that your system complies with the following requirements:
First download the latest version of the iOS SDK:
http:www.runrev.com/downloads/livecode/sdk/LiveCodeSDK-R10.zip
Warning: Do not open any of the XCode projects before running LiveCode SDK Support.mpkg
4.1 Video tutorial
The video below provides a step by step guide to creating an iOS external for LiveCode. It guides you through the XCode setup process, coding, compilation and integration of the final external into a LiveCode project.
4.2 Creating a new project
To create a new external, use the 'New Project…' feature of Xcode and select the appropriate template. This creates a project with the following files:
The project has two targets:
At this time it is important that two points are true:
Warning: Failure to observe either of these points will result the external not working.
The SDK comes with a number of sample external projects which have been documented through extensive source code comments. Each project folder contains:
The LiveCode test stacks automatically load the pre-compiled externals allowing you to try out the functionality immediately. Simply load the test stack in LiveCode and test it using the simulator.
5.1 rreMicrophone
Audio recording using AVAudioRecorder.
5.2 rreNarrator
Text-to-speech using 'flite' (CMU speech synthesis project).
5.3 rreSocket
Simple client-side socket implementation using NSStreams.
5.4 rreMail
Simple example demonstrating use of modal view controller.
5.5 rreHardcopy
Exports the visible card to PDF and sends it to the printer over airprint.
5.6 rreCanvas
Demonstrates the use of LCImage calls to 'takeover' an image. This example implements a simple 2d vector path drawing API, and example stack renders some example SVG images.
Each external should have a corresponding interface definition contained in the .lcidl file. The interface definition details the functions, commands and types the external will make available to the programmer and will be specified using the LiveCode Interface Definition Language (IDL). A complete interface definition greatly simplifies implementing externals as all 'type conversions' and error checking on entry to a function is handled automatically. Upon building the external, the interface definition will be compiled and the appropriate hooks will be created.
6.1 Syntax
The syntax of the IDL is as follows:
interface
: 'external' ID
{ use-clause }
{ hook-clause }
{ definition }
use-clause
: 'uses' USE-ID
hook-clause
: 'on' HOOK-ID 'call' NATIVE-ID
definition
: enum-definition
| command-definition
| function-definition
enum-definition
: 'enum' ID
{ STRING as INTEGER }
command-definition
: 'command' ID
{ parameter-definition }
[ return TYPE-ID ]
[ call NATIVE-ID ]
function-definition
: 'function' ID
{ parameter-definition }
{ optional-parameter-definition }
[ return TYPE-ID ]
[ call NATIVE-ID ]
parameter-definition
: ( 'in' | 'out' | 'inout' ) ID 'as' TYPE-ID
optional-parameter-definition
: 'optional' 'in' ID 'as' TYPE-ID 'default' ( STRING | NUMBER )
6.2 External Name
The first entry within the IDL file should be the external name. As before, this must be all lowercase and in the following format:
external rrecanvas
6.3 Use Clause
Typically, after the external name, any options or use clauses will be defined. Here 'USE-ID' describes a particular option to use when generating interface hooks. The following are currently understood:
If you are writing your code purely in Objective-C (.m) of C (.c) no extra directives are required. C++ naming in only required if you are using Objective-C++ (.mm) or C++ (.cpp) where the C++ compiler is adding additional info to function names to take into account overloading etc.
The following is and example use clause:
use c++-naming
6.4 Hook Clause
Here 'HOOK-ID' describes non-handler related hooks that the external needs to be notified of. The following are currently understood:
The following is and example hook clause:
on startup call rreMicrophoneStartup
6.5 Command & Function Definition
Once we have defined the external names and any clauses required, we are ready specify the functions and commands. Here 'ID' and 'NATIVE-ID' describe native code function names.
The following is and example command definition:
command rreMail
6.6 Parameters & Return Values
Each function and command can have a set of parameters and return a value. Here 'TYPE-ID' describes a type. It can be one of:
Binding to native methods provides three parameter 'modes':
Both out and inout parameter modes map to 'reference' parameters in the native methods. When c++-naming is used this corresponds to a '&' parameter, otherwise an extra level of indirection is used (i.e. an extra '*'). For example:
command foobar
in myInParameter as double
out myOutParameter as c-string
inout myInOutParameter as objc-string
Would require the following native function signatures:
The following rules must be observed for in mode:
The following rules must be observed for the out mode:
The inout mode is a combination of the rules for in and out. In particular, in the c-* case, the input buffer should not be modified, and a new buffer should be provided on exit if it has changed.
6.7 Enumerative Types
You can also specify in the interface definition any enumerative types your external will use. An example enumeration is as follows:
enum print-status-enum
"" as 0
"printing not available" as 1
"file does not exist" as 2
"cannot print that file" as 3
"printing canceled" as 4
"printing failed" as 5
Any system frameworks and libraries (i.e. .framework / .dylib files) should be referenced in the .ios file. For example, if you use libz and AVFoundation you would write:
framework AVFoundation
library z
You must not attempt to use any third-party frameworks or dylibs, Apple will not allow apps in the AppStore which attempt to load third-party frameworks - all code in an app not provided by iOS must be statically linked into the main executable.
LCExceptionRaise
LCObjectExists
LCObjectRetain
LCObjectRelease
LCObjectSend
LCObjectPost
LCObjectGet
LCObjectSet
LCContextMe
LCContextTarget
LCContextDefaultStack
LCContextDefaultCard
LCWaitCreate
LCWaitRetain
LCWaitRelease
LCWaitIsRunning
LCWaitRun
LCWaitBreak
LCWaitReset
LCImageAttach
LCImageDetach
LCImageDescribe
LCImageDescribeMask
LCImageUpdate
LCImageUpdateRect
LCRunOnMainThread
LCPostOnMainThread
LCInterfaceQueryView
LCInterfaceQueryViewScale
LCInterfacePresentModalViewController
LCInterfaceDismissModalViewController
| Description |
Set the error to throw on return from the current external handler. The thrown string is constructed using the printf-style formatting string 'format' and any subsequent arguments as appropriate. |
| Parameters | (in) format - const char * |
| Context Safety | Must be called from handler context. |
| Description |
Checks to see if the object referred to by the handle 'object' still exists. The 'r_exists' parameter is set to 'true' if it is still there, or 'false' otherwise. |
| Parameters | (in) object - LCObjectRef (out) r_exists - bool |
| Errors | NoObject - the 'object' parameter was nil. |
| Context Safety | Must be called from dispatch context. |
| Description |
Increase the reference count of the handle passed in 'object'. |
| Parameters | (in) object - LCObjectRef |
| Errors | NoObject - the 'object' parameter was nil. |
| Context Safety | Must be called from dispatch context. |
| Description |
Decrease the reference count of the handle passed in 'object'. If the reference count reaches zero, the handle is destroyed and is no longer valid. |
| Parameters | (in) object - LCObjectRef |
| Errors | NoObject - the 'object' parameter was nil. |
| Context Safety | Must be called from dispatch context. |
| Description |
Sends 'message' to 'object' with the given parameters. The caller blocks until the message has been handled. The parameter list is constructed based on the 'signature' c-string. Each character in the signature determines the type of the subsequent arguments and is used to convert them to a form suitable for LiveCode script. The characters that are currently understood are: 'b' - the parameter is of type 'bool', converts to 'true' or 'false' 'i' - the parameter is of 'int' type, converts to a number 'r' - the parameter is of 'double' type, converts to a number 'z' - the parameter is of 'c-string' type, converts to a (text) string 'y' - the parameter is of 'c-data' type, converts to a (binary) string 'N' - the parameter is of 'NSNumber*' type, converts to a number 'S' - the parameter is of 'NSString*' type, converts to a (text) string 'D' - the parameter is of 'NSData*' type, converts to a (binary) string The parameters appear in the resulting LiveCode message in the same order that they appear in the signature. The 'z' type should be passed a 'const char *' (zero-terminated) string. The 'y' type should be passed a 'const LCBytes *' type. |
| Parameters | (in) object - LCObjectRef (in) message - const char * (in) signature - const char * (in) ... - variadic argument list |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. NoObject - the 'object' parameter was nil. NoObjectMessage - the 'message' parameter was nil. ObjectDoesNotExist - the object handles target no longer exists. ScriptExited - the message caused 'exit to top' to be called. ScriptFailed - the message caused an error to be thrown. |
| Context Safety | Must be called in dispatch context. |
| Description |
Appends an event to the internal event queue. When the event is dispatched 'message' is sent to the target object with the given parameters. Note: Posting from an auxiliary thread will block until the main thread reaches a suitable point to process the request and schedule the event. |
| Parameters | (in) object - LCObjectRef (in) message - const char * (in) signature - const char * (in) ... - variadic argument list |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. NoObject - the 'object' parameter was nil. NoObjectMessage - the 'message' parameter was nil. ObjectDoesNotExist - the object handles target no longer exists. |
| Context Safety | May be called in universal context. |
| Description |
Fetches the value of the given (array) property from the specified object and returns the value in the way specified by 'options'. The usage of options and values is as described in the section on 'values'. This method works in an identical way to property fetching in script. i.e. LCObjectGet(object, ..., property, NULL, value) is the same as: put the of |
| Parameters | (in) object - LCObjectRef (in) options - unsigned int (in) property - const char * (in) key - const char * (out) value - depends on options |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. NoObject - the 'object' parameter was nil. NoObjectProperty - the 'property' parameter was nil. NoObjectPropertyValue - the 'value' parameter was nil. ObjectDoesNotExist - the object handles target no longer exists. Exited - the message caused 'exit to top' to be called. Failed - the message caused an error to be thrown. NotABoolean - the value was requested as a boolean, and it is not a boolean. NotANumber - the value was requested as a number, and it is not a number. NotAnInteger - the value was requested as an integer, and it is not an integer. NotABinaryString - the value was requested as binary data, and it is not binary data. NotAString - the value was requested as a string, and it is not a string. NotAnArray - the value was requested as an array, and it is not an array. |
| Context Safety | Must be called in dispatch context. |
| Description |
Sets the value of the given (array) property to the given value in the way specified by 'options'. The usage of options and values is as described in the section on 'values'. This method works in an identical way to property setting in script. i.e. LCObjectSet(object, ..., property, NULL, value) is the same as: set the of
i.e. LCObjectSet(object, ..., property, key, value) is the same as: set the [] of
Note that just like the script analogs, if is not an engine property custom properties are used (with setprop messages dispatched as needed), and thus the current custom property set of the object comes into effect when required. |
| Parameters | (in) object - LCObjectRef (in) options - unsigned int (in) property - const char * (in) key - const char * (out) value - depends on options |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. NoObject - the 'object' parameter was nil. NoObjectProperty - the 'property' parameter was nil. NoObjectPropertyValue - the 'value' parameter was nil. ObjectDoesNotExist - the object handles target no longer exists. Exited - the message caused 'exit to top' to be called. Failed - the message caused an error to be thrown. NotABoolean - the value was requested as a boolean, and it is not a boolean. NotANumber - the value was requested as a number, and it is not a number. NotAnInteger - the value was requested as an integer, and it is not an integer. NotABinaryString - the value was requested as binary data, and it is not binary data. NotAString - the value was requested as a string, and it is not a string. NotAnArray - the value was requested as an array, and it is not an array. |
| Context Safety | Must be called in dispatch context. |
| Description |
Returns a weak object handle to object's whose script invoked the external handler. The caller is responsible for calling 'LCObjectRelease' on the handle when it is no longer needed. |
| Parameters | (out) r_me - LCObjectRef |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. |
| Context Safety | Must be called from handler context. |
| Description |
Returns a weak object handle to 'the target'. The caller is responsible for calling 'LCObjectRelease' on the handle when it is no longer needed. |
| Parameters | (out) r_target - LCObjectRef |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. |
| Context Safety | Must be called from handler context. |
| Description |
Returns a weak object handle to 'the defaultStack'. The caller is responsible for calling 'LCObjectRelease' on the handle when it is no longer needed. |
| Parameters | (out) r_default_stack - LCObjectRef |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. |
| Context Safety | Must be called from handler context. |
| Description |
Returns a weak object handle to 'this card of the defaultStack'. The caller is responsible for calling 'LCObjectRelease' on the handle when it is no longer needed. |
| Parameters | (out) r_default_card - LCObjectRef |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. |
| Context Safety | Must be called from handler context. |
| Description |
Creates and returns a handle to a 'Wait' object, allowing pausing of script execution in both blocking or dispatching modes. The caller is responsible for callback 'LCWaitDestroy' on the handle when it is no longer needed. |
| Parameters | (in) options - unsigned int |
| Errors | OutOfMemory - memory ran out while attempting to perform the operation. |
| Context Safety | Must be called from handler context. |
| Description |
Increases the reference count for the given wait object. |
| Parameters | (in) wait - LCWaitRef |
| Errors | NoWait - the 'wait' parameter was nil. |
| Context Safety | May be called in any context from any thread. |
| Description |
Reduces the reference count for the given wait object, destroying it if the reference count reaches zero. |
| Parameters | (in) wait - LCWaitRef |
| Errors | NoWait - the 'wait' parameter was nil. |
| Context Safety | May be called in any context from any thread. |
| Description |
Returns 'true' if the wait object is currently running, false otherwise. Note: Although this call is thread-safe, the return value may be immediately invalid if the wait object finishes just after its current state is fetched. |
| Parameters | (in) wait - LCWaitRef (out) running - bool |
| Errors | NoWait - the 'wait' parameter was nil. |
| Context Safety | May be called in any context from any thread. |
| Description |
Blocks the caller's execution and runs the main application event loop until either 'LCWaitBreak' is called on the wait object, or the wait is aborted (due to application exit). In the latter case, 'kLCWaitAborted' is returned. If 'LCWaitBreak' has already been called on the wait object LCWaitRun returns without running the event loop. To re-use the wait object after it has been run, use LCWaitReset. |
| Parameters | (in) wait - LCWaitRef |
| Errors | NoWait - the 'wait' parameter was nil. WaitRunning - the 'wait' object is already running. WaitAborted - the 'wait' ran but was aborted rather than broken. |
| Context Safety | Must be called from handler context. |
| Description |
Marks the given wait object as being 'broken' and interrupts the event loop if LCWaitRun was invoked on the wait. |
| Parameters | (in) wait - LCWaitRef |
| Errors | NoWait - the 'wait' parameter was nil. |
| Context Safety | May be called in any context from any thread. |
| Description |
Resets the wait object so that it can be run again. |
| Parameters | (in) wait - LCWaitRef |
| Errors | NoWait - the 'wait' parameter was nil. WaitRunning - the 'wait' object is already running. |
| Context Safety | Must be called from handler context. |
| Description |
Creates and attaches rasters to the given image object allowing direct access to the bits of the main (color) raster, and it's mask (if any). The attachment locks the size of the image leaving complete control of the contents to the external. When detached, the image returns to its previous state (including contents). |
| Parameters | (in) object - LCObjectRef (in) options - unsigned int (out) r_image - LCImageRef |
| Errors | OutOfMemory - there was not enough memory to complete the operation. ObjectDoesNotExist - the given object no longer exists. NotAnImageObject - the given object is not an image object. |
| Context Safety | Must be called from dispatch context. |
| Description |
Detaches from an image, previously attached to using LCImageAttach. |
| Parameters | (in) image - LCImageRef |
| Errors | NoImage - the 'image' parameter was nil. |
| Context Safety | Must be called from dispatch context. |
| Description |
Returns the details of the image's main raster, allowing access to the RGB values that make up the image. The raster is always in the native format provided by the engine on the current platform. On iOS this is 8-bit components, organized in memory as RGBx where the last byte is ignored. |
| Parameters | (in) image - LCImageRef (out) r_raster - LCImageRaster |
| Errors | NoImage - the 'image' parameter was nil. |
| Context Safety | Can be called from any context. |
| Description |
Returns the details of the image's mask raster, allowing access to the 1-bit mask that controls transparency of the image. The raster is always in the 1-bit gray format. |
| Parameters | (in) image - LCImageRef (out) r_raster - LCImageRaster |
| Errors | NoImage - the 'image' parameter was nil. |
| Context Safety | Can be called from any context. |
| Description |
Mark the image object to which 'image' is attached as needing a redraw. |
| Parameters | (in) image - LCImageRef |
| Errors | NoImage - the 'image' parameter was nil. |
| Context Safety | Must be called from dispatch context. |
| Description |
Mark the image object to which 'image' is attached as needing a redraw but only within the specified rectangle. The co-ordinates are relative to the top-left of the image. |
| Parameters | (in) image - LCImageRef |
| Errors | NoImage - the 'image' parameter was nil. |
| Context Safety | Must be called from dispatch context. |
| Description |
Executes the given callback on the main (engine) thread as soon as is possible. If the call is made from the main thread and 'Wait' is passed as an option, the callback is invoked before LCRunOnMainThread returns; otherwise the callback is scheduled to run at the next invocation of the run loop (cf. send ... to me in 0 millisecs). If the call is made from an auxiliary thread then the callback is scheduled on the main thread's run loop to be executed at the next possible opportunity. If 'Wait' is specified in this case, the caller will block until the main thread has executed the callback; otherwise the caller will block only until the request has been posted. When invoked, the context of the callback is native - only certain API calls can be made. |
| Parameters | (in) options - unsigned int (in) callback - LCRunOnMainThreadCallback (in) state - void * |
| Errors | OutOfMemory - there was not enough memory to service the request. |
| Context Safety | May be called in any context. |
| Description |
Posts an event to the event queue that when dispatched causes the given callback to be invoked. If the event is never reached (due to application exit), the callback is still invoked, but with the 'canceled' flag this allows the callback to clean-up any dynamic state associated with it. The call will block until the request has been successfully posted to the event queue and then return. When invoked, the callback is executed in dispatch context - all but handler context requiring API calls can be made. |
| Parameters | (in) options - unsigned int (in) callback - LCPostOnMainThreadCallback (in) context - void * |
| Errors | OutOfMemory - there was not enough memory to service the request. |
| Context Safety | May be called in any context. |
| Description |
Returns the UIView for the currently visible stack that makes up the main view of the application. |
| Parameters | (out) r_view - UIView * |
| Errors | (none) |
| Context Safety | May be called in any context on the main thread. |
| Description |
Returns the multiplier currently applied to LiveCode co-ordinates when mapping to and from UIKit co-ordinates. In particular, if running on a device with a Retina display, and 'UseDeviceResolution' is true, the scale with be 2.0, otherwise it will be 1.0. |
| Parameters | (out) r_scale - double |
| Errors | (none) |
| Context Safety | May be called on any context on the main thread. |
LCInterfacePresentModalViewController
| Description |
Presents the given view controller modally, with or without animation as specified by the 'animated' parameter. This call corresponds to doing: [ presentModalViewController: controller animated: animated] |
| Parameters | (in) controller - UIViewController * (in) animated - bool |
| Errors | (none) |
| Context Safety | Must be called from handler context. |
LCInterfaceDismissModalViewController
| Description |
Dismisses the given view controller that was previously presented using LCInterfacePresentModalViewController. The dismissal is performed with or without animation, depending on the value of the 'animated' parameter. This call corresponds to doing: [ dismissModalViewController: controller animated: animated] |
| Parameters | (in) controller - UIViewController * (in) animated - bool |
| Errors | (none) |
| Context Safety | Must be called from handler context. |
It is important to be aware that when writing externals in objective-c that it does not have any notion of 'namespaces' - all classes and protocols (and category methods) exist in a flat namespace; this means that if care is not taken conflicts can easily occur - it is not possible to create such things that are 'local' to a particular block of code.
This is particularly problematic in the realm of application extensions or plugins (which externals are). If they have classes with identical names, then it is not possible to use them together - iOS applications must be statically linked, or the application may not be linked!
To resolve this we require that all classes (and protocols) that are used within LiveCode externals must follow a strict naming scheme. All names must be prefixed by a reverse domain pertinent to the producer. e.g.
com_runrev_rremicrophone_MicrophoneDelegate
com_foobar_mycoolexternal_SomeObject
Objective-C provides some support to make this easier (i.e. to stop having to type long identifiers!). Define a class like this:
@interface com_foobar_mycoolexternal_SomeObject : NSObject
…
@end
@compatibility_alias SomeObject com_foobar_mycoolexternal_SomeObject;
@implementation SomeObject
…
@end
Anything after the 'compatibility_alias' line can use SomeObject instead of the more lengthy identifier.
There is no 'built-in' (syntax level) support for protocol definitions, so a define must be used instead:
@protocol com_foobar_mycoolexternal_SomeProtocol
…
@end
#define SomeProtocol com_foobar_mycoolexternal_SomeProtocol
Category methods must also be named uniquely in the same way. A category method is one that is 'added' to an existing class augmenting its behavior - they aren't global, but are 'global' within a given class's method table hence the need for disambiguation. Again, there is no direct support for shortening names, so defines can be used (albeit with care!):
@interface NSString (com_foobar_mycoolexternal_StringStuff)
- (bool) com_foobar_mycoolexternal_superCoolStringOperation: (int)foo
@end
#define StringStuff com_foobar_mycoolexternal_StringStuff
#define superCoolStringOperation com_foobar_mycoolexternal_superCoolStringOperation
@implementation NSString (StringStuff)
- (bool) superCoolStringOperation: (int)foo
{
…
}
@end
Any code files that should be compiled into the external should be added to the target - this includes any static libraries the external may use (see rrenarrator for an example of this). The output of the target is two-fold:
Non-code files that are needed for testing/debugging the app in the simulator / on the device should be added to the 'test' target as a Copy Files/Resources phase (see rrecanvas for an example of this).
The two targets themselves rely on two 'support' shell scripts that are run as last phases on the target (Build External / Build Test).
The 'Build External' phase of target calls the 'lclink.sh' script. This builds the '.dylib' form and the '.lcext' form of the external and ensures that the dependency information (provided by the .ios file) is embedded for access by the standalone builder. It also copies the products into the 'binaries' folder for easy interoperation with the IDE (i.e. just point a 'Copy Files' entry to the folder and the external will be automatically included in iOS builds).
The 'Build Test' phase of the 'test' target constructs an app using some precompiled binaries which are part of the SDK. This results in an iOS standalone which loads the external and runs the test stack - this makes it trivial to run the external in any simulator, or on any device through Xcode. In particular - it enables easy debugging of the native code using breakpoints.
Both these scripts rely on various 'User-Defined' build settings that are present in the project.
The 'test' target relies on the following (both defined at the project level, but could be defined at the 'test' target level instead):
The '' target relies on the following (defined at the target level):
At the project level there is also a LIVECODE_SDKROOT setting. This describes the path to the SDK support files (~/Application Support/RunRev/Components/LiveCodeSDK) and is used in the following places:
Note: If a target / project needs additional header file paths than its important the LiveCode header path not be eliminated by mistake. Either define at the target level and use $(inherited) or be careful just to them along-side at the project level. (See rrenarrator for an example).
We only guarantee source compatibility between engine versions. This means that future engines may not be able to load/work with compiled externals built using current SDK releases - to work in a future engine, it might be necessary to re-compiled externals with newer SDK release that accompany the engines.
12.1 When I try to build one of the sample external projects in XCode I get the following error:
"warning: no rule to process file '$(PROJECT_DIR)/rrenarrator.lcidl' of type text for architecture armv6".
This warning is most likely due to the fact that the project file was opened in XCode before the LiveCode SDK installer was run. XCode has changed the file type of the lcidl file to 'text' from the original 'sourcecode.livecode.idl'. This is because XCode was not aware of the new filetype before the SDK installer was run.
If you have not already run the SDK installer run it now. See installation instructions above. Once installed perform either 1) or 2) to fix the XCode compilation issue.