Posted on by

Today’s tutorial is an introduction to Corona Enterprise development on iOS, showing Corona developers how to extend or create a Corona project that leverages native iOS functionality. Using Enterprise, Corona developers can combine the best of both the native world and the rich feature set of Corona SDK. Sign up for a 30-day trial and get started today.

Note: If you’re interested in using Enterprise for Android, see the Android quickstart tutorial.

Setting Up

First, we recommend that you update to the latest version of Xcode from within the App Store application. Also, ensure that Corona Enterprise is installed in your root OS X Applications directory (/Applications/CoronaEnterprise).

In this tutorial, we’ll begin with the default template located here:

/Applications/CoronaEnterprise/ProjectTemplates/App

Copy this folder to your project folder or another convenient location. Once copied, you can rename the folder to whatever you want. In this tutorial, we’ll discuss how to turn the device’s flashlight on and off, so “Flashlight” may be an appropriate folder name.

Within this folder are two projects: one to compile the plugin library and one to compile the app itself. Unlike Corona SDK, where you simply provide the Lua files and resources for Corona Labs’ build servers to compile your project, Corona Enterprise builds a native library that has bindings to your Corona SDK app.

Let’s start with building the plugin. First, open the iOS folder and you’ll see several files, two of which must be noted:

  • App.xcodeproj
  • Plugin.xcodeproj

These are Xcode Project files which hold various settings for your project. Double-click on Plugin.xcodeproj file to load the file in Xcode. Once loaded, inspect the left hand column — from here, expand the Plugin folder, then the Source folder. Inside, you’ll see the PluginLibrary.mm and PluginLibrary.h files.

For those with a background in C, the .mm extension may seem a bit odd. In Xcode, a .m file is really a .c file, but it stands for “Methods”. This .mm file allows Xcode to mix Objective-C with C++ and it’s where you put your methods and objects. The .h file is the traditional C “header” file where you place the definitions for the objects.

Methods File

Let’s start with the methods file (.mm). For this tutorial, your application will need access to the AVFoundation framework. As such, you need to include the appropriate header file just after the UIKit include.

#import "PluginLibrary.h"

#include "CoronaRuntime.h"

#import <UIKit/UIKit.h>

#import <AVFoundation/AVFoundation.h>

Next, the PluginLibrary class is declared. This is the interface for binding your plugin to Lua:

class PluginLibrary
{
    public:
        typedef PluginLibrary Self;

    public:
        static const char kName[];
        static const char kEvent[];

    protected:
        PluginLibrary();

    public:
        bool Initialize( CoronaLuaRef listener );

    public:
        CoronaLuaRef GetListener() const { return fListener; }

    public:
        static int Open( lua_State *L );

    protected:
        static int Finalizer( lua_State *L );

    public:
        static Self *ToLibrary( lua_State *L );

    public:
        static int init( lua_State *L );
        static int show( lua_State *L );

    private:
        CoronaLuaRef fListener;
};

Most of this you don’t need to touch, except for the final public: block where two methods are defined:

    public:
        static int init( lua_State *L );
        static int show( lua_State *L );

For our Flashlight app, show does’t make much semantic sense. Methods of on and off would be more logical. So, remove the show method and define the on and off methods instead:

    public:
        static int init( lua_State *L );
        static int on( lua_State *L );
        static int off( lua_State *L );

In each one, lua_State *L is a data structure/object that represents the Lua engine.

Next, we’ll update some values to better represent the actual project on the Lua side and provide the proper linking:

// This corresponds to the name of the library, e.g. [Lua] require "plugin.library"
const char PluginLibrary::kName[] = "plugin.flashlight";

// This corresponds to the event name, e.g. [Lua] event.name
const char PluginLibrary::kEvent[] = "Flashlight";

Next, the PluginLibrary::Open() method needs updating to remove the show method and add the on and off methods:

    // Functions in library
        const luaL_Reg kVTable[] =
        {
            { "init", init },
            { "on", on },
            { "off", off },

            { NULL, NULL }
        };

Now, scroll down until you see the defined show method. Remove it and replace it with this block of code:

// [Lua] library.on( word )
int PluginLibrary::on( lua_State *L )
{
    NSString *message = @"Device does not appear to have a camera light.";

    // check if flashlight available
    Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
    if (captureDeviceClass != nil) {
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        if ([device hasTorch] && [device hasFlash]){
                message = @"Device light should be on.";
            [device lockForConfiguration:nil];
            if (device.torchMode == AVCaptureTorchModeOff) {
                [device setTorchMode:AVCaptureTorchModeOn];
                [device setFlashMode:AVCaptureFlashModeOn];
            }
            [device unlockForConfiguration];
        }
    }

    Self *library = ToLibrary( L );

        // Create event and add message to it
        CoronaLuaNewEvent( L, kEvent );
        lua_pushstring( L, [message UTF8String] );
        lua_setfield( L, -2, "message" );

        // Dispatch event to library's listener
        CoronaLuaDispatchEvent( L, library->GetListener(), 0 );

        return 0;
}

// [Lua] library.off( word )
int PluginLibrary::off( lua_State *L )
{
    NSString *message = @"Device does not appear to have a camera light."; ;

    // check if flashlight available
    Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
    if (captureDeviceClass != nil) {
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        if ([device hasTorch] && [device hasFlash]){
                message = @"Device light should be off.";
            [device lockForConfiguration:nil];
            if (device.torchMode == AVCaptureTorchModeOn) {
                [device setTorchMode:AVCaptureTorchModeOff];
                [device setFlashMode:AVCaptureFlashModeOff];
            } 
            [device unlockForConfiguration];
        }
    }

    Self *library = ToLibrary( L );

    // Create event and add message to it
    CoronaLuaNewEvent( L, kEvent );
    lua_pushstring( L, [message UTF8String] );
    lua_setfield( L, -2, "message" );

    // Dispatch event to library's listener
    CoronaLuaDispatchEvent( L, library->GetListener(), 0 );

    return 0;
}

The code for the flashlight code came from a posting on StackOverflow, but it’s been modified here to adjust from a toggle to two separate actions.

Following the Objective-C code which manages the device’s LED lamp, there’s some Corona Enterprise code that constructs an event and dispatches it to your Lua app. Let’s examine that code:

    Self *library = ToLibrary( L );

        // Create event and add message to it
        CoronaLuaNewEvent( L, kEvent );
        lua_pushstring( L, [message UTF8String] );
        lua_setfield( L, -2, "message" );

        // Dispatch event to library's listener
        CoronaLuaDispatchEvent( L, library->GetListener(), 0 );

This code creates a C object called library. This is the pathway to your Lua app. Next, it calls CoronaLuaNewEvent() and passes it the kEvent object. It then adds the string message and tells Lua it’s the second entry in the table. Finally, it dispatches the event to the library. Later, we’ll discuss the Lua code for handling this event.

The last block of code on the page will be:

CORONA_EXPORT int luaopen_plugin_library( lua_State *L )
{
    return PluginLibrary::Open( L );
}

Because of the “suffix” _library, the name of the plugin on the Corona side will be library. That name is vague if you decide to build several plugins, so let’s rename the plugin flashlight by changing the code to luaopen_plugin_flashlight:

CORONA_EXPORT int luaopen_plugin_flashlight( lua_State *L )
{
    return PluginLibrary::Open( L );
}

Header File

In the header file (.h), you must rename the definition to match. In the left column, select PluginLibrary.h and change the CORONA_EXPORT line to the following:

CORONA_EXPORT int luaopen_plugin_flashlight( lua_State *L );

At this point, select Product → Build (Command-B) to build the project. If Xcode does so without errors, you can close this project for now.

App File

Now, from the project directory, open the main project file App.xcodeproj. First, we’ll rename the project to something more sensible by clicking on the project name (App) in the left column. Change the name to Flashlight. Xcode will then show a dialog box indicating all of the names it will change. Click Rename to make the change. Close the project, re-open it, and make the following important setting changes:

When you open a project, the top section of the middle pane, Identity, should show you the Bundle Identifier, Version, Build, etc. You can edit the com.yourname section of the Bundle Identifier but you can’t change the app name.

xcode

In the Deployment Info section below, go through and edit the options such as Deployment Target, Devices, Device Orientation, etc.

Next, scroll down to the Linked Frameworks and Libraries and, at the bottom, click on the + button. Locate AVFoundation.framework, select it, and click Add.

Now, in the uppermost bar of the middle pane, click on the Build Settings tab and scroll down to Linking. For Other Linker Flags, enter -all_load -ObjC -lobjc -lsqlite3.

Scroll down to the Apple LLVM 5.1 – Language – C++ block. In this section, change the entry for C++ Standard Library to libstdc++ (GNU C++ standard library).

On to Corona

In the left column of the Xcode project, assuming you’ve expanded the Flashlight tree, you’ll see an Xcode group named Corona (look for the folder icon). If you expand this item tree, you’ll see only main.lua listed, although the actual folder contains other files familiar to Corona developers (build.settings, config.lua, etc.).

Now let’s edit the main.lua file. You can edit it in your preferred editor, or simply edit it directly within Xcode:

local flashlight = require( "plugin.flashlight" )
local widget = require( "widget" )

local bg = display.newRect( 0, 0, display.contentWidth, display.contentHeight )
bg:setFillColor( 1,1,1 )
bg.x, bg.y = display.contentCenterX, display.contentCenterY

-- This event is dispatched to the global Runtime object by 'didLoadMain:' in 'MyCoronaDelegate.mm'
local function delegateListener( event )
    native.showAlert(
        "Event dispatched from 'didLoadMain:'",
        "of type: " .. tostring( event.name ),
        { "OK" } )
end
Runtime:addEventListener( "delegate", delegateListener )

local function listener( event )
    native.showAlert( event.name, event.message, { "OK" } )
end

local lightState = "Off"

local onOffSwitch

local function handleButtonEvent( event )
    if ( lightState == "Off" ) then
        flashlight.on()
        lightState = "On"
        onOffSwitch:setLabel( "Turn Off" )
    else
        flashlight.off()
        lightState = "Off"
        onOffSwitch:setLabel( "Turn On" )
    end
    return true
end

onOffSwitch = widget.newButton{
    id = "button1",
    label = "Turn On",
    labelColor = { default={ 0,0,0 }, over={ 0,0,0 } },
    onRelease = handleButtonEvent
}

onOffSwitch.x = display.contentCenterX
onOffSwitch.y = display.contentCenterY

flashlight.init( listener )

The first line loads your plugin into a Corona object called flashlight. The second line require‘s the Corona widget library, and lines 4-6 create a simple white background rectangle.

Next is the function called delegateListener(). This function will be attached to the Runtime event "delegate" and will receive events dispatched from the native side. In this case, it just shows an alert. Basically, this lets you know when your app has been loaded — this sample app won’t use it in a meaningful way.

The next function is the listener that the plugin uses to receive events from the various plugin methods. Again, we simply show an alert.

The rest of the code does the main work from the Corona side. Among other things, we create a widget button that uses the event listener function named handleButtonEvent(). Most importantly, notice that this listener function calls:

  • flashlight.on()
  • flashlight.off()

These are the functions that you wrote in Objective-C within PluginLibrary.mm. Simply call them to activate them.

Test Run

And now for the fun — testing on an actual device!

  1. Assuming you’ve enabled your iPhone as a development device, connect it to your computer with the sync cable.
  2. In the upper-left corner of Xcode, locate the menu of devices, including iPhone Retina (4-inch) and other options. Click on this menu and a popup window should appear with an arrow that lets you scroll up to the actual iPhone that you just connected. Select it and exit the popup.
  3. To the left of that device list, you’ll see a Play button. Click this and Xcode will install the app onto the device and start it up. Because it’s in development mode, it takes a couple of seconds longer to start up.
  4. If everything is working correctly, your screen will turn white and you’ll see the “Turn On” button. Tap it and the flashlight should turn on. Dismiss the alert and tap the button again to turn off the flashlight.
  5. When you’re finished testing, click the Stop button in Xcode.

Conclusion

Clearly this tutorial is just a basic example of what can be done with Corona Enterprise, but hopefully it has shown you what can be accomplished when you combine the nearly unlimited power of native programming with the ease and simplicity of Corona SDK.


Posted by . Thanks for reading...

11 Responses to “Tutorial: Corona Enterprise Quickstart (iOS)”

  1. David MEKERSA

    Amazing! So thank you!
    Next step, the very same tutorial for Android?

    Thanks for this one, it was the missing part of the documentation!!

    Reply
  2. Build failed???

    Sorry this does not build correctly after following the tutorial step by step. I get the following linker error, can you please advise?

    ld: warning: ignoring file /Applications/CoronaEnterprise/Projects/Flashlight/ios/CoronaEnterprise/Corona/ios/lib/libplayer.a, missing required architecture x86_64 in file /Applications/CoronaEnterprise/Projects/Flashlight/ios/CoronaEnterprise/Corona/ios/lib/libplayer.a (2 slices)

    Reply
    • Rob Miracle

      There is likely two causes.

      1. Are you building for your tethered device or the Simulator (4″ 64 bit) or some other Simulator device?
      You cannot build for the 64 bit simulator without making a change to your build settings.

      2. In the build settings screen, there is a list of architectures: armv7, armv7s, arm64. This worked for me, but changing this to leave off arm64 will prevent the 64bit version being built, which might fix the problem.

      Rob

      Reply
  3. Luis

    Excuse me, now I try implemented in file PluginLibrary.mm a method. But I do not understand how I can declare and get data in method with parameter (a table).

    e.g:
    =>Lua (main.lua):
    local variable = require “plugin.library”
    variable.setData( {“170″,”65″,”m”} )

    =>Native & Lua (PluginLibrary.mm):
    public:
    static int init( lua_State *L );
    static int setData( ???? ); // How I declare parameters?

    int PluginLibrary::setData(????)
    {
    // How I get datas from this parameters?
    }

    Thank you for your time and help.

    Reply
    • Rob Miracle

      I would recommend looking at the Enterprise sample /Applications/CoronaEnterprise/Samples/SimpleLuaExtension

      It shows you how to move different data types around.

      Reply
  4. Luis

    Thank you, for you response. And one more question, I need use library ( library build for other ) in “PluginLibrary” file. But, this library (build for other) Is possible add other library in “PluginLibrary” file?

    Reply
    • Rob Miracle

      I’m not sure I follow your question. You might have better luck asking this in the Forums in the Enterprise section.

      Rob

      Reply
    • Luis

      e.g: File “PluginLibrary.mm”


      #import “xxx.h”

      public:
      static int connect( lua_State *L );

      { “connect”, connect },

      int
      PluginLibrary::connect( lua_State *L )
      {
      [[xxx sharedXXX] connect]; <– Show error, I when call method xxx, but if I remove this line code, is perfect run.

      }

      ———–
      [Abstract]
      I need to integrate a third party iOS library which has come with a .a with a Corona app.
      I have been referring to this doc:
      http://docs.coronalabs.com/native/plugin/index.html#ios
      http://coronalabs.com/blog/2014/03/18/tutorial-corona-enterprise-quickstart-ios/

      To create a plugin for Corona, but so far can't understand how to start with it.

      A summary of what I can't understand:

      If I create an application, what folder structure should I create?
      Where should?

      Reply

Leave a Reply

  • (Will Not Be Published)