iOS Externals Guide
LiveCode provides an iOS externals interface with which you can extend the existing iOS feature set using a lower level language like C, C++ or Objective C.
This guide takes you through the steps required to create an iOS external for LiveCode. These steps include the XCode setup process, coding, compilation and integration of the final external into a LiveCode project.
1. Introduction
2. Requirement
3. Installation
4. Getting Started
4.1 Video tutorial
4.2 Creating a new project
5. Sample External Projects
5.1 rreMicrophone
5.2 rreNarrator
5.3 rreSocket
5.4 rreMail
5.5 rreHardcopy
5.6 rreCanvas
6. Interface Definition Language
6.1 Syntax
6.2 External Name
6.3 Use Clause
6.4 Hook Clause
6.5 Command & Function Definition
6.6 Parameters & Return Values
6.7 Enumerative Types
7. Libraries and Frameworks
8. Threading
9. API
10. Objective-C Naming Requirements
11. Build Process Details
12. Compatibility
13. Troubleshooting
1. Introduction
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.
Note: From 5.5.2, the iOS engine’s internal structure has been changed. Unfortunately this means that all existing externals written for that platform must be updated. (Externals updated for 5.5.2 will still load and run in 5.5.1). See section Threading.
2. Requirement
Before installing the SDK ensure that your system complies with the following requirements:
- MacOS 10.6 or higher
- LiveCode 4.6.2 or higher
- XCode 4.2
- iOS5 SDK
3. Installation
First download the latest version of the iOS SDK:
http://downloads.livecode.com/livecode/sdk/LiveCodeSDK-R14.zip
Warning: Do not open any of the XCode projects before running LiveCode SDK Support.mpkg
- Unzip the SDK containing:
- ChangeLog.txt
- LiveCode SDK Support.mpkg
- 6 example external projects
- Run ‘LiveCode SDK Support.mpkg’.
The installer will add a template externals project to your XCode installation. This will appear in your XCode project wizard and provide a ’1 click’ option for creating a new LiveCode iOS external project.
4. Getting Started
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:
- .ios
The list of framework and library dependencies required by the external. - .lcidl
The interface definition for the external. - .mm
The main source file for the external – this can be changed to c/cpp or objective-c (m); ‘mm’ is just the best of both worlds as its obj-c++. - .livecode
An empty test stack. - test-Info.plist
The plist used in the test target.
The project has two targets:
- [external name]
The target that builds the native code block usable by the engine. - test
An application target that uses pre-built iOS standalones installed by SDK Support to enable easy testing/debugging of the target.
At this time it is important that two points are true:
- The external name (target name) must be lower-case.
- The name specified in the ‘external <…>’ clause in the IDL file must match the target name ().
Warning: Failure to observe either of these points will result the external not working.
5. Sample External Projects
The SDK comes with a number of sample external projects which have been documented through extensive source code comments. Each project folder contains:
- The source code for the external
- An XCode project file
- A pre-compiled external
- A LiveCode test stack
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.
- Please make sure to check your system preferences and select a valid microphone input.
- Click ‘Start’ to begin the recording process (A temporary file is created in the apps ‘documents’ folder).
- Click ‘Stop’ to end the recording.
- Click ‘Play’ to playback your recording.
5.2 rreNarrator
Text-to-speech using ‘flite’ (CMU speech synthesis project).
- Button Speak: Blocks the caller completely and does all processing on main thread meaning nothing else runs at the same time (same as doing wait until… without messages).
- Button ‘Speak Without Messages’: Blocks the caller but allows low-level engine event processing to occur – i.e. the animated gif still functions.
- Button ‘Speak With Messages’: Blocks the caller but does normal message dispatch – i.e. you can still press buttons and execute other actions (same as doing wait until… with messages).
- Button ‘Speak In Background’: Does not block the caller. Instead a callback ‘narratorFinished’ / ‘narratorError’ is sent to the button when the task is completed.
5.3 rreSocket
Simple client-side socket implementation using NSStreams.
- Demonstrates HTTP Get to www.runrev.com.
5.4 rreMail
Simple example demonstrating use of modal view controller.
- Fill out your details and click compose. iOS will popup the default ‘compose’ window with the users inputs ready to send.
5.5 rreHardcopy
Exports the visible card to PDF and sends it to the printer over airprint.
- Uses the rect of the target screen to position the popover via a LCObjectGet call.
- Button ‘Is Available’: Checks to see if the airprint facility is available on the host device.
- Button ‘Print’: Creates a PDF of the current screen and sends it to the printer via 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.
- Pulldown: Select an image to render.
6. Interface Definition Language
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
: {'tail'} 'command' ID
{ parameter-definition }
[ return TYPE-ID ]
[ call NATIVE-ID ]
function-definition
: {'tail'} '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:
- c++-exceptions – the external will throw C++ exceptions which should be caught and translated to LiveCode exceptions
- c++-naming – the native methods are exported using C++ name mangling
- objc-exceptions – the external will throw obj-c exceptions which should be caught and translated to LiveCode exceptions (discouraged)
- objc-objects – the native methods use objc-objects and need an associate auto-release pool to manage them
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:
- startup – the given native method will be called when the application starts up. The signature of the hook should be:
bool (void) - shutdown – the given native method will be called when the application shuts down. The signature of the hook should be:
void (void)
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.
Use the ‘tail’ keyword if the command and function is to be invoked on the system thread. See section Threading for further details.
The following is an 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:
- boolean – converted to/from a native bool type.
- c-string – converted to/from a native char * referencing a NUL terminated string.
- c-data – converted to/from a native LCBytes * type describing a block of memory of arbitrary length.
- integer – converted to/from a native int type.
- real – converted to/from a native double type.
- objc-string – converted to/from a native NSString * object.
- objc-number – converted to/from a native NSNumber * object.
- objc-data – converted to/from a native NSData * object.
- any id defined as an enum – converted to/from a native int type, as mapped through the list of strings provided for the enum.
Binding to native methods provides three parameter ‘modes’:
- in – the value is provided to the function and no return is expected.
- out – on entry, the parameter is initialized to nil and on exit any value that is present is copied into the LiveCode variable.
- inout – the current value of the LiveCode variable is provided to the function on exit, and upon exit the value is copied by to it.
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:
- c++-naming:
void foobar(double myInParameter, char*& myOutParameter, NSString*& myInOutParameter) - not c++-naming:
void foobar(double myInParameter, char ** myOutParameter, NSString** myInOutParameter)
The following rules must be observed for in mode:
- c-string – the pointer passed to the native method is const char * and its contents must not be modified.
- c-data – the pointer passed to the native method is const LCBytes * and the memory pointed to by the buffer element must not be modified.
The following rules must be observed for the out mode:
- c-string – the pointer passed back must have been allocated with malloc/calloc/realloc, and ownership passes to the engine.
- c-data – the pointer passed back in the buffer element must have been allocated with malloc/calloc/realloc, and ownership passes to the engine.
- objc-* – the object passed back should be in the current auto-release pool if the external no longer needs it.
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
7. Libraries and Frameworks
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.
8. Threading
The engine runs using two threads in a co-operative fashion with only one executing at a time. The first (main/system) thread is where all system related calls are made – for example, manipulating UIViews or any other system-provided classes. The second (engine/script) thread is where code that executes scripts run.
The reason for this separation is the existence of ‘wait’ – the ability to block an executing handler while events are processed. On iOS, in order to process events, the system thread must return to the core event loop which iOS manages; however waiting requires running the event loop from nested calls. To make this work, when the engine thread invokes a wait, the engine jumps to the system thread, jumping back when an event occurs.
As most iOS system classes are UI / RunLoop related, it is critical that they be manipulated on the system thread, rather than the engine thread. To do this there are two choices:
- If an external handler does not use any LC API calls that might call script (and thus invoke ‘wait’) then it can be marked as a ‘tail’ call in the idl – for example:
tail function rreMicrophoneIsAvailable
If marked as a ‘tail’ call then the handler will be invoked on the system thread, meaning no extra code is needed.
- If an external handler does use LC API calls that might call script (and thus invoke ‘wait’) then ‘tail’ cannot be used. Instead, there are two new LC API calls LCRunOnSystemThread and LCRunBlockOnSystemThread . These allow code to be execute on the system thread, from the engine thread. See the SDK examples for ways this is done.
9. API
LCExceptionRaise
LCObjectExists
LCObjectRetain
LCObjectRelease
LCObjectSend
LCObjectPost
LCObjectGet
LCObjectSet
LCContextMe
LCContextTarget
LCContextDefaultStack
LCContextDefaultCard
LCWaitCard
LCWaitRetain
LCWaitRelease
LCWaitIsRunning
LCWaitRun
LCWaitBreak
LCWaitReset
LCImageAttach
LCImageDetach
LCImageDescribe
LCImageDescribeMask
LCImageUpdate
LCImageUpdateRect
LCRunOnSystemThread
LCRunBlockOnSystemThread
LCRunOnMainThread
LCPostOnMainThread
LCInterfaceQueryView
LCInterfaceQueryViewScale
LCInterfacePresentModalViewController
LCInterfaceDismissModalViewController
LCExceptionRaise
| 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. |
LCObjectExists
| 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. |
LCObjectRetain
| 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. |
LCObjectRelease
| 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. |
LCObjectSend
| 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. |
LCObjectPost
| 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. |
LCObjectGet
| 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 property of object | ||
| 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. |
LCObjectSet
| 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 property of object | ||
| 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. |
LCContextMe
| 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. |
LCContextTarget
| 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_target – LCObjectRef | |
| Errors | OutOfMemory – memory ran out while attempting to perform the operation. | |
| Context Safety | Must be called from handler context. |
LCContextDefaultStack
| 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. |
LCContextDefaultCard
| 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. |
LCWaitCard
| 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. |
LCWaitRetain
| Description | Increases the reference count for the given wait object. | |
| 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. |
LCWaitRelease
| Description | Reduces the reference count for the given wait object, destroying it if the reference count reaches zero. | |
| 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. |
LCWaitIsRunning
| 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. |
LCWaitRun
| 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. |
LCWaitBreak
| 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. |
LCWaitReset
| Description | Resets the wait object so that it can be run again | |
| The caller is responsible for calling ‘LCObjectRelease’ on the handle when it is no longer needed. | ||
| 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. |
LCImageAttach
| 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 – memory ran out while attempting to perform 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. |
LCImageDetach
| 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. |
LCImageDescribe
| 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. |
LCImageDescribeMask
| 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. |
LCImageUpdate
| 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. |
LCImageUpdateRect
| 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. |
LCRunOnSystemThread
| Description | Executes the given callback on the system thread as soon as possible. | |
| If called from the system thread, the callback will be invoked directly. | ||
| If called from the engine thread, the engine will temporarily jump to the system thread to invoke the callback. | ||
| The context in which the callback is invoked should be considered native. In particular, it is illegal to use LCObjectGet, LCObjectSet, LCObjectSend and LCWaitRun – LCObjectPost is fine, however. | ||
| Note that when run in a version of the engine prior to 5.5.2-dp-1, this call executes the callback directly – this means a single version of the external will suffice to run in 5.5/5.5.1 and 5.5.2. | ||
| Parameters | (in) callback – LCRunOnSystemThreadCallback | |
| (in) state – void * | ||
| Errors | OutOfMemory – there was not enough memory to service the request. | |
| Context Safety | May be called in any context from the system or engine thread. |
LCRunBlockOnSystemThread
| Description | Identical to LCRunOnSystemThread except that an Objective-C block is invoked rather than a function. | |
| Parameters | (in) callback – void (^)(void) | |
| Errors | OutOfMemory – there was not enough memory to service the request. | |
| Context Safety | May be called in any context from the system or engine thread. |
LCRunOnMainThread
| 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. |
LCPostOnMainThread
| 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. |
LCInterfaceQueryView
| 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. |
LCInterfaceQueryViewScale
| 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 in 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. |
10. Objective-C Naming Requirements
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
11. Build Process Details
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:
- A ‘.dylib’ file which is used when running through Xcode on a device; or when running in the simulator either in the IDE or through Xcode.
- An ‘.lcext’ file which is a partially linked object file. This is used by the standalone builder when producing device builds. It contains all the non-system code required by the external, and is built so that the only symbols that are exported are the hooks required by the engine.
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):
- LIVECODE_TEST_EXTERNAL – the name of the external target that should be loaded – by default this is , but should be changed if additional targets are added or the name of the external changed.
- LIVECODE_TEST_STACK – the stack to build into the test app for debugging – by default this is .livecode but could be changed as necessary.
The ” target relies on the following (defined at the target level):
- LIVECODE_DEP_FILE – the file to use for dependency information – by default this is .ios
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:
- At the project level the ‘Header Search Paths’ option is defined to be “$(LIVECODE_SDKROOT)/headers” – this allows source-files to find the LiveCode.h file.
- In both the lclink.sh and lclinktest.sh scripts, which use it to find the support files needed by the SDK.
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).
12. Compatibility
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.
13. Troubleshooting
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.
- Change the filetype of the lcidl source file to ‘sourcecode.livecode.idl. This can be done via the properties palette of the file.
- Re-download the SDK to get a fresh copy of the project file.
