Articles iOS and macOS App Extensions with Delphi by Allen Drennan

ermax

Moderator
Staff member
iOS and macOS App Extensions with Delphi
[SHOWTOGROUPS=4,20]
November 15, 2018 Allen Drennan

In this article we are going to cover how to package iOS and macOS Application Extensions with your Delphi developed iOS and macOS application and interact with the Application Extension from Delphi using the Application Groups API.

Introduction
There are numerous features on iOS and the Mac that are only available using . App Extensions are essentially background processes or plugins that are bundled along with your main host application and installed simultaneously when your app is initially installed. Delphi doesn’t have any way to directly build App Extensions, but you can create them in XCode and you can consume them, bundle them and interact with them in your Delphi project.

Why would you need Application Extensions? Well there are many API capabilities on iOS and macOS that are only available through App Extensions. For example, you can use the ReplayKit API directly from your Delphi app to capture your own Delphi app UX experience and either record it or stream it over the web. However, as soon as you return to the home screen on iOS, your application is placed into the background and ReplayKit no longer provides the callback. If you want to capture the entire UX experience of the iOS device you need a background process. Apple created the concept of a Broadcast Upload App Extension which uses the ReplayKit APIs in the background to capture the entire device UX. This capability is only available from an Application Extension.

There are many other capabilities only available via App Extensions, such as Today widgets and Social sharing extensions.

Creating App Extensions
XCode provides templates to ease the process of creating an App Extension. You simply create a new Project in XCode and then add a new Target for the extension(s) you want to include. While the template provides no practical code, it is a working App Extension that you can manipulate and test the ideas presented in this article.

Apple expects that all App Extensions will use a Bundle Id that is an expansion of host application’s Bundle Id. So if your Delphi host application’s Bundle Id is com.company.myapp then your App Extension’s Bundle Id needs to be com.company.myapp.myextension.

Once you build the project in XCode, you only need to copy the App Extensions from the Mac over to your Windows computer where your Delphi project resides. Essentially it is as simple as locating your XCode bundle .app folder and copying the entire \PlugIns subfolder and all related files to a location in your existing Delphi project.

Once you have copied the extension(s), you only need to add these files to your Delphi Deployment Manager. They must target the .\PlugIns folder in your deployed project. When you Build and Run your Delphi application, your App Extensions will automatically be installed along with your main host application. It is really that simple.
The tool makes adding and removing files from the Deployment Manager settings for your Delphi project much easier.
Exchanging data with your App Extension
For security reasons, on iOS and macOS your application is essentially and it is prevented from interacting directly with other processes. This is also true of App Extensions and their host application, even if they share a Bundle Id and even if they are part of the same Bundle.

In order to initialize or interact between your Delphi created host application and the extension you need to use some form of IPC (inter-process communication). On iOS and macOS there is a mechanism known as that provides various IPC related capabilities. Apple actually uses XPC to launch and configure App Extensions internally. However, for developers on iOS, Apple provides a set of APIs known as which essentially uses the XPC APIs to provide a shared space for multiple App Extensions and the host application in the same bundle to interact and exchange information.

In order to use Application Groups you need to enable App Groups in the Apple Develop Portal and create a new App Id. First you create an App Group in the Apple Developer portal and provide a properly formatted name. This should be in the form of group.com.company.myapp. Then you create a new App Id for your main host application and for each of your App Extensions. They should each be configured to use App Groups and you should choose the Application Group name you created (ex: group.com.company.myapp). In Delphi you need to make sure your project’s Provisioning Profile (under Project Options) is configured correctly.

Delphi will automatically include the new Entitlements from the Provisioning Profile for the Application Group and Delphi will automatically add the Entitlement to your Bundle.
1
2
3
4
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.company.myapp</string>
</array>
You should not add this Entitlement manually or your application will not function because of the duplicate key.
Using App Groups from your Delphi application
In order to share settings and data between your main host application and the App Extension, you need to use the NSUserDefaults APIs and initialize it with the initWithSuiteName() API. This is done in every host application and App Extension that wants to exchange data.

Delphi doesn’t expose this specific API, so for our purposes we will create a small wrapper to expose it:
1
2
3
4
5
6
7
8
9
10
11
12
uses
iOSapi.Foundation,
Macapi.Helpers,
Macapi.ObjectiveC;

type
MissingNSUserDefaults = interface(NSUserDefaults)
['{57042F99-BD77-4E25-9AE1-B8F0C6776A18}']
function initWithSuiteName(suitename: NSString): Pointer; cdecl;
end;

TMissingNSUserDefaults = class(TOCGenericImport<NSUserDefaultsClass, MissingNSUserDefaults>) end;

Then to setup our shared settings from Delphi we first initialize the NSUserDefaults using our Application Group name along with the initWithSuiteName() API. You can only use theinitWithSuiteName() API to create a shared NSUserDefaults and all other related APIs will instead initialize a sandboxed NSUserDefaults. We then call setObject() or one of the various APIs available within NSUserDefaults to update a specific key/value pair. You can pass as many individual settings as you want, but in order to update all other App Extensions about the changes you must call the synchronize() API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var
Defaults: NSUserDefaults;
SharedSettings: MissingNSUserDefaults;
begin
{ Init with the proper Application Group suite name }
Defaults := TNSUserDefaults.Alloc;
SharedSettings := TMissingNSUserDefaults.Wrap((Defaults as ILocalObject).GetObjectID);
SharedSettings.initWithSuiteName(StrToNSStr('group.com.company.myapp'));

{ Save SomeKey/SomeValue to the shared settings }
SharedSettings.setObject(
(StrToNSStr('SomeValue') as ILocalObject).GetObjectID,
StrToNSStr('SomeKey') );

{ Update the settings }
SharedSettings.synchronize;
end;


[/SHOWTOGROUPS]
 
[SHOWTOGROUPS=4,20]

Using App Groups from your Objective-C App Extension
From the App Extension in XCode, we can read these settings. The process in XCode is the same as Delphi in that need to use the NSUserDefaults APIs and initialize it with the initWithSuiteName() API.

1
2
3
4
NSString *const AppGroupName = @"group.com.company.myapp";

// Settings passed from the host application
NSString *someValue;
You need to supply your App Group name to the initWithSuiteName() API as shown below.
1
2
3
4
5
// Create and share access to an NSUserDefaults object
NSUserDefaults *sharedSettings = [[NSUserDefaults alloc] initWithSuiteName: AppGroupName];

// Get shared object key/value
someValue = [sharedSettings stringForKey:mad:"SomeKey"];

And finally you can extract the settings you created in your Delphi main host application from within your XCode authored App Extension.

Can App Extensions be created with Delphi?
It would be great if you could create App Extensions (.appex) with Delphi. Extensions are really just separate applications that are packaged with your main app and executed on demand. The big problem for Delphi produced apps and most other platforms outside of Objective-C with XCode is you are probably not going to get app store approval from Apple because you have to use private APIs to make it work, especially on iOS.

It is absolutely possible to create a bare-bones project in Delphi, make a few changes to the info.plist and the version info and build an application that acts like an app extension. This bare-bones app can be added to your Delphi Deployment manager and deployed along with your main app.
Those steps involve:
  1. Create a project group, consisting of a host app and an extension app.

  2. Create a FMX project for your “main” host app.
  3. Set your CFBundleIdentifier to something specific. (ex: com.myorg.MyApp )
  4. Create a FMX project for your app extension.
  5. Set your CFBundleIdentifier to an exactly match the bundle identifier of the host app and append something specific. (ex: com.myorg.MyApp.$(ModuleName) )
  6. Remove the Main form from the app extension project and all the units from the DPR.
  7. Modify the iOS setting, CFBundlePackageType from APPL to XPC! in the version information for the extension.
  8. Add the NSExtension settings to your info.plist:
    1
    2
    3
    4
    5
    6
    7
    8
    <key>NSExtension</key>
    <dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.???</string>
    <key>NSExtensionPrincipalClass</key>
    <string>???</string>
    etc...
    </dict>
  9. Build and Run your extension app project so it is deployed to the Mac.

  10. Copy the compiled .app for your extension from the Mac to a folder on your Windows build machine.
  11. Rename the extension folder name from an .app extension to .appex extension.
  12. Add the extension .appex folder and all subfolders and files to the Deployment Manager in Delphi with a subfolder target of .\PlugIns\ on the remote machine.
  13. Build and run the host app and your application extension will also be installed.
This looks like it works, but in reality the OS will not allow you to use your app extension. Let’s look at the issues.
  1. First off, although your extension is sand-boxed, communication still needs to happen between your host app and your extension. Apple does this using a form a IPC (inter-process communication) using a set of APIs internally built around the XPC foundation apis.

  2. XCode doesn’t use the ApplicationMain entry point to launch extensions, it uses a special entry point _NSExtensionMain and links to it using a special flag -e. Extensions need to implement NSExtension and handle NSXPCConnection which is considered a private API. You can manually create a header translation for the private APIs in the Foundation unit for NSExtension and implement this yourself.
  3. Extensions are sand-boxed and can only communicate with their host app using IPC, which you must implement in your extension or you will not be able to communicate with your extension.
  4. Extensions must be small and fast, so you can forget including any FireMonkey units because of size limitations or anything that causes your extension to perform poorly.
  5. You can’t debug your extensions in Delphi and you probably cannot build Debug builds because the size of your extension must not exceed 16MB.
Conclusion
I hope you enjoyed this discussion on the topics of iOS and macOS Application Extensions and using Application Groups for inter-process communications. We don’t believe these topics have ever been covered before in the Delphi community, so we hope it helps you with your projects.

Here at Grijjy we have used the techniques in this article to build Delphi applications that use App Extensions created in XCode with our projects. If you are willing to experiment a little in Objective-C you can make it all work. Maybe someday Delphi will have the ability to create App Extensions as well, much like Xamarin, which would be really great and make Delphi even that much more useful for cross-platform development.

[/SHOWTOGROUPS]
 
Back
Top