This content has been archived and is no longer being updated. Links may not function; however, this content may be relevant to outdated versions of the product.
Close popover

Developing custom Pega Mobile Client modules for iOS in Pega 7.2.2

Pega Mobile Client consists of several modules that enable various device functionalities or allow your custom mobile app to communicate with the Pega® Platform.

This tutorial describes how iOS developers can extend the Pega Mobile Client by creating their own modules, which can be developed in Objective-C or Swift, and expose the module's functionality through the JavaScript API. Custom modules can enable features and functions that are available to native iOS applications in Pega Platform apps.

For example, a custom module can allow a Pega Platform mobile app to use an embedded laser scanner to scan and recognize barcodes to be pasted into a product ID field, or to use a tablet's projector module to display the wiring layout of equipment that needs to be serviced.

After they are created, custom modules are bundled with the custom mobile app in Designer Studio. For more information, see Uploading custom modules.

This tutorial describes how to build a custom module.

The content of this tutorial does not apply to Apple App Extensions. Pega Mobile Client does not support App Extensions.

Technical overview

Custom modules that extend the Pega Mobile Client are created in Xcode. They facilitate the process of exposing the features and functions that are available to native applications. Custom modules can consist of several parts, such as plug-in classes (the JavaScript code and the native implementation), extension classes, graphic files, sounds, and other required assets.

Product modules

This tutorial uses the PMBase module, which contains the container's basic functionality and is required for creating a custom module. Your custom module can have dependencies to modules other than PMBase. All Pega Mobile Client modules are available in the Frameworks directory of the distribution package.

Plug-in classes

Custom modules contain one or more plug-in classes. These classes use a JavaScript-native side bridge to respond to JavaScript requests and to initiate JavaScript events.

All plug-in classes on iOS must inherit from the PMJavaScriptPlugin class. Classes that inherit from this class are instantiated for each WKWebView instance when it is created, and remain valid within this instance for its lifetime.

A plug-in is needed to return a JavaScript code as an NSString class. The plug-in is injected into WKWebView JavaScript code that can be hardcoded, generated, or read from the resource bundle. Pega Mobile Client asks for the JavaScript code by calling the provideJavaScriptWithCompletionHandler: method on the PMJavaScriptPlugin instance.

JavaScript code returned by the plug-in is injected into the WKWebView instance during the web application startup before the onLaunchboxLoaded method is called. An instance of the bridge object is available during the injection phase. JavaScript code must use this instance to communicate with the native code.

To learn how to create plug-in classes, refer to Extending the container with plug-in classes.

Extension classes

Use extension classes to extend the container, for example, to grant access to native events. Custom modules contain one or more extension classes. An extension class enables simple initialization, such as creating single instances or registering for UIApplication notifications. Its instance is created once, at container startup, and remains valid within the container for its lifetime. If the custom module library contains more than one extension class, Pega Mobile Client creates an instance for each of them.

All extension classes on iOS must inherit from the PMApplicationPlugin class. Such classes are instantiated when the application is initiated, and remain valid for its lifetime.

To learn how to create extension classes, refer to Extending the container with extension classes.

Sample custom module

This article includes a working example of a custom module for you to analyze. Download the following file to import it in Xcode:

ios-custom-module.zip (2 MB)

Known issues

Because of compatibility issues, you cannot use the networkActivityIndicatorVisible property of the UIApplication method to control the network activity indicator. Use the PMNetworkActivityIndicator class instead, which has been exposed in the Pega Mobile Client's native API. Refer to the documentation provided in the distribution package.

Prerequisites

Before developing a custom module for iOS:

  • Obtain the distribution package for your intended platform from a Pegasystems Global Customer Support representative.
  • Download and configure the following items:
    • Mac OS X 10.11.5 or later (the latest update is recommended)
    • Ruby 2.0.0 or later (already installed with OS X)
    • Bundler gem 1.13.6 or later
      The bundler package must be manually installed, for example, by calling sudo gem install bundler in a terminal window.
    • -->
    • Xcode 8.2.1
    • Java Runtime Environment (JRE) 8 x64
    • Mobile provision profile prepared for the app
      For more information, see Creating Development Provisioning Profiles on the Apple Developer Portal.
    • A valid development and distribution certificate
      For more information, see the Certificate, Key, and Trust Services Programmer's Guide on the Apple Developer Portal.
  • Review the documentation in the doc folder of the distribution package to become familiar with the native custom module API and JavaScript API.

Creating a custom module project

Prepare the Xcode project for your custom module by doing the following steps:

  1. Run Xcode, click New project, and select Cocoa Touch Framework.

  2. Click Next. In the Product name field, enter a custom module name, for example, MyModule, and select your preferred programming language.

  3. Click Next and save your project in the Modules folder of your distribution package.

  4. Browse to the Frameworks folder of the distribution package. Drag the PMBase.framework module to your project in Xcode.

  5. Accept the default settings in Xcode, and click Finish.

  6. Navigate to the Build Settings tab in your project and set the Framework search paths parameter to $(PROJECT_DIR)/../../Frameworks.

Adding third-party dependencies

You can extend the procedure by integrating third-party libraries written in Objective-C or Swift with your custom module. To add a library project, a statically linked library, or a dynamically linked library to your custom module, do the following steps:

To add a library project that includes source code:

  1. Drag the library project into Xcode to create a subproject of your custom module project.
  2. In the Project Navigator pane, select your custom module project to access its settings.
  3. Select your target, and in the Build Phases tab:
    1. Add the third-party library target in the Target Dependency section.
    2. Add the third-party library in the Link Binary With Libraries section.
    3. Optional: If your third-party library requires any system libraries, add them in the Link Binary With Libraries section.

To add a statically linked library that contains Objective-C code:

  1. Drag the library project into Xcode. Select Copy items if needed to add all the files to your project directory.
  2. In the Project Navigator pane, select your custom module project to access its settings.
  3. Select your target, and:
    1. In the Build Phases tab, link the *.a library file against your targets in the Link Binary With Libraries section.
    2. In the Build Settings tab:
      1. Reference the directory in the Library Search Paths section.
      2. Add the $(PROJECT_DIR) path in the User Header Search Paths section with Recursive selected.
      3. Add the -ObjC flag in the Other Linker Flags section.
  4. Optional: If your third-party library requires any system libraries, add them in the Link Binary With Libraries section of the Build Phases tab.

To add a dynamically linked framework that contains the Objective-C or Swift code:

  1. Drag the framework into Xcode. Click Copy items if needed to add it to your project directory.
  2. In the Project Navigator pane, select your custom module project to access its settings.
  3. Select your target, and:
    1. In the Build Phases tab, link the framework against your targets in the Link Binary With Libraries section.
    2. In the Build Settings tab, add the $(PROJECT_DIR) path in the Framework Search Paths section.
  4. In your target's Build Phases tab, add a new Copy Files phase:
    1. In the Destination field, select Products Directory.
    2. Add the third-party framework in the area below.

Extending the container with plug-in classes

All JavaScript communication must pass through the main GCD queue (UI classes can be called only on the main thread). To conserve performance of the UI and JavaScript bridge, all JavaScript code that does not explicitly require using the main queue must be run on private GCD queues. This is why the dispatchQueue method has been implemented in the PMJavaScriptPlugin class used by the JavaScript bridge on iOS to ask plug-in instances for their queue when passing JavaScript calls.

To create the main plug-in class:

  1. In the Project Navigator pane, go to the MyModule folder.

  2. Click File > New File.

  3. Select Cocoa Touch Class.

  4. On the next screen, in the Class field, enter the name of the class. In the Subclass field, enter PMJavaScriptPlugin.

  5. In the Language field, select Objective-C or Swift.

  6. Click Next, and then click Create.

  7. In the newly created class, import the PMBase module by using the @import PMBase; declaration.

    If you selected Objective-C in the Language field, place the import statement in the header file. Otherwise, place it at the top of the Swift file.

To implement the JavaScript side of the plug-in, do the following steps:

  1. In the Project Navigator pane, go to the Resources folder.

  2. Click File > New File to create a JavaScript file, for example, proximitySensor.js.

  3. Click Next, and then click Create.

  4. Edit the resource to create code similar to the following example:

    (function() {
    window.example = window.example || {};
    window.example.ProximitySensor = {
      // The function makes a bridge call to native code and passes an object with a single function reference.
      // The callbacks is temporary and will be invalidated after first response is received.
      isCloseToTheUser: function (callback) {
        bridge.call('ProximitySensorExample', 'isCloseToTheUser', null, { 'onSuccess': callback });
        }
      };
    })();
    The name of the plug-in class used in the bridge.call method must be the same as the one that was defined when the class was created.

To return the JS code, implement the provideJavaScriptWithCompletionHandler: method (for Objective-C) or provideJavaScript(completion:) (for Swift) in your JavaScript code. Refer to the following example of the Objective-C implementation.

  • The header file should contain code similar to the following example:

    @import PMBase;
    @import UIKit;
    NS_ASSUME_NONNULL_BEGIN
      
    @interface ProximitySensorJSPlugin : PMJavaScriptPlugin
      
    #pragma mark - Called from JavaScript
    - (void) isCloseToTheUser:(nullable id)data withCallback:(nullable PMJavaScriptCallback *)callback;
      
    @end
  • The implementation file should contain code similar to the following example:
    #import "ProximitySensorJSPlugin.h"
      
    @interface ProximitySensorJSPlugin ()
      
    @end
      
    @implementation ProximitySensorJSPlugin
    + (NSString *)name {
      return @"ProximitySensorExample";
    }
    - (instancetype)initWithContext:(PMJavaScriptPluginContext *)context {
      if (self = [super initWithContext:context]) {
        // Do initialization here...
      }
      return self;
    }
      
    - (void)provideJavaScriptWithCompletionHandler:(void (^)(NSString *_Nonnull))completionHandler {
      NSString *scriptPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"proximitySensor" ofType:@"js"]
      NSString *scriptCode = [NSString stringWithContentsOfFile: encoding:NSUTF8StringEncoding error:NULL];
      completionHandler(scriptCode);
    }
      
    #pragma mark - Called from JavaScript
      
    - (void)isCloseToTheUser:(nullable id)data withCallback:(nullable PMJavaScriptCallback *)callback {
      dispatch_async(dispatch_get_main_queue(), ^{
        BOOL proximityState = [[UIDevice currentDevice] proximityState];
        [callback call:"onSuccess" passingData:@(proximityState)];
      });
    }

To add any other resources, including configuration files, audio files, graphics, and videos, drag them to your project in Xcode, select Copy items if needed, and click Finish.

Extending the container with extension classes

To create the main extension class, you must create a subclass of the PMApplicationPlugin class. Pega Mobile Client instantiates all subclasses of the PMApplicationPlugin class by calling the init selector in the Pega Mobile Client's UIApplicationDelegate init file. The extension class can react to the events from the UIApplicationDelegate protocol that are defined in the PMUIApplicationDelegate protocol.

To create the extension class:

  1. In the Project Navigator pane, go to the MyModule folder.

  2. Click File > New File.

  3. Select Cocoa Touch Class.

  4. On the next screen, enter the name of the class in the Class field. In the Subclass field, enter PMApplicationPlugin.

  5. Select Objective-C or Swift in the Language field.

  6. Click Next, and then click Create.

  7. In the newly created class, import the PMBase module by using the @import PMBase; declaration.

    If you selected Objective-C in the Language field, place the import statement in the header file. Otherwise, place it at the top of the Swift file.
  8. Import and implement the PMApplicationPlugin protocol. Refer to the following example of the Objective-C implementation.

    • The header file should contain code similar to the following example:

      @import PMBase;
      @import UIKit;
        
      NS_ASSUME_NONNULL_BEGIN
        
      @interface ProximitySensorJSPlugin : PMApplicationPlugin
        
      @end
        
      NS_ASSUME_NONNULL_END
    • The implementation file should contain code similar to the following example:
      #import "ProximitySensorExtension.h"
        
      @implementation ProximitySensorExtension
        
      - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [super application:application didFinishLaunchingWithOptions:launchOptions];
        [[UIDevice currentDevice] setProximityMonitoringEnabled:YES];
        return YES;
      }
        
      - (void)applicationDidEnterBackground:(UIApplication *)application {
        [super applicationDidEnterBackground:application];
        [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];
      }
        
      - (void)applicationWillEnterForeground:(UIApplication *)application {
        [super applicationWillEnterForeground:application];
        [[UIDevice currentDevice] setProximityMonitoringEnabled:YES];
      }
        
      @end

Testing the custom module

To test your custom module locally:

  1. Modify the app.properties file that is in the distribution package's main directory:
    1. Change a value of the bootstrap.config.dir parameter to resources/pega7OfflineBootstrap.

    2. Set a value of the bootstrap.string.applicationURL to your intended instance of the Pega Platform, for example, http://test.server.com:8243/prweb/.

    3. Set the other parameters as necessary (review the inline comments for details).

  2. Run the ant command at a command prompt to build a binary Pega Mobile Client package that includes your custom module.

  3. Open the HybridClient.xcworkspace project from the distribution package in Xcode.

  4. Drag the custom module project into the HybridClient.xcworkspace as a subproject of the HybridClient project.

  5. Open the Modules.xcconfig file and add -framework MyModule at the end of the line.

  6. Click Run. The Pega Mobile Client starts on a connected device.

If your custom module uses any third-party framework, you must also add it to the Modules.xcconfig file.

Enabling the logger API

Compared with an iOS original NSLog macro, the Logger API is more reliable. It allows:

  • Access to its logs
  • Setting a logging level
  • Speeding up log recording tenfold

To use the Logger API in the custom module's native code:

  1. Import the PMBase module into your class.

  2. Use the following macros in your code: PMLogError(), PMLogWarn(), PMLogInfo(), PMLogDebug() and PMLogVerbose(), which correspond to logging levels that are described in the Logger API reference.

Packaging the custom module

When your custom module is complete and has been tested, prepare it for packaging within a .zip file. To obtain a sample package, download the following file:

custom-module-assets.zip (0.83 MB)

  1. Set the Active Scheme in Xcode to Generic iOS Device.

  2. Click Product > Build to compile your custom module.

  3. Find the custom module binary by right-clicking the framework in the Products directory of the Project Navigator pane and clicking Show in Finder. The file name will be MyModule.framework.

  4. If you are using the sample custom-module-assets.zip file, remove the default contents of the modules-ios folder.

  5. Copy the module binary to the modules-ios folder in the custom-module-assets.zip file.

  6. Upload the custom-module-assets.zip file with your custom module to the Pega Platform. For more information, see Uploading custom modules.

Have a question? Get answers now.

Visit the Collaboration Center to ask questions, engage in discussions, share ideas, and help others.