iOS: Extensions Framework

Description of the Extensions Framework and how to integrate it.

The iOS Extensions Framework is a new addition to the Marigold iOS SDK that allows apps to save events in app extensions - https://developer.apple.com/app-extensions/.

Extensions only have access to a limited set of the iOS APIs, so it's not possible to use the main Marigold iOS SDK Framework in an extension. To solve this we have developed the Extensions Framework specifically to operate in app extensions in order to allow you to properly integrate them with the Marigold platform.

App Groups

In order for the Extension Framework to communicate with the main Marigold iOS SDK your app needs to set up an App Group (https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups?language=objc).

App groups allow apps and extensions to share an NSUserDefaults instance. You can set up an App Group in the Apple Developer site (https://developer.apple.com/) in the Certificates, Identities and Profiles section.

You can then add the App Group in the Capabilities tab in your app settings.

πŸ“˜

Note

Both the main app and the extension will need to have the App Group Capability added and the same App Group must be active on both targets.

Adding the Extensions Framework

Swift Package Manager

If you are using Swift Package Manager you will see the extension SDK as an option to add to your project when you add Marigold as a dependency. This will add it automatically to your main app target. You should then manually add it to the list of linked binaries in your extensions targets.

Cocoapods

If you are using Cocoapods you should add the Extension Framework to your main app target and to any extension targets like so:

target 'YourAppExtension' do
    pod 'Marigold-Extension', '14.2.0'
end

The extension is a subspec of the main SDK pod so it will be automatically added to your main app target.

Carthage

If you import the SDK as normal using Carthage you will notice that the Marigold.framework file contains both the main Marigold.xcframework and the MarigoldExtension.xcframework. You should add both frameworks to your main app and the extensions framework to any app extensions that you plan to use it in.

Manually

If you add the Frameworks manually then you will find the MarigoldExtension.framework bundled next to the main Marigold.framework. You should ensure this is added to your Extension target and to your app target.

πŸ“˜

Note

The Extensions Framework needs to be embedded in your main app target as well as added to the Extension Target. If you are not setting up the SDK with Cocoapods you must ensure the Extension Framework is added to both targets.

Setting up the Framework

Once the Extensions Framework has been added you will need to initialize it both in the main app and in the extension.

Main App

In the main app you simply need to register any shared App Groups that you have created:

[[Marigold new] registerExtensionGroups:@[@"group.your.group.name"]];
Marigold().registerExtensionGroups(["group.your.group.name"])

If you want to use different app groups for different extensions you will need to register all the app groups in the array provided here.

Extensions

The Extensions Framework is primarily designed to work with the UNNotificationServiceExtension and UNNotificationContentExtension extensions.

UNNotificationServiceExtension

In order to simplify the UNNotificationServiceExtension integration we have created our own implementation that you can use to quickly and easily set up both the Extensions Framework and Rich Push Notification handling:

#import "NotificationService.h"
#import <MarigoldExtension/MARNotificationServiceExtension.h>

@interface NotificationService ()

@property (nonatomic, strong) MARNotificationServiceExtension *marServiceExtension;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
  
    // setup marigold service extension
    self.marServiceExtension = [[MARNotificationServiceExtension alloc] initWithAppKey:@"YOUR_APP_KEY" andGroupName:@"group.your.group.name"];
  	
  	// handle notification request
    [self.marServiceExtension didReceiveNotificationRequest:request withContentHandler:contentHandler];
}

- (void)serviceExtensionTimeWillExpire {
  	// handle extension timeout
    [self.marServiceExtension serviceExtensionTimeWillExpire];
}

@end
import UserNotifications
import SailthruMobileExtension

class NotificationService: UNNotificationServiceExtension {
    var marigoldNotificationServiceExtension : MARNotificationServiceExtension?
    
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler:@escaping (UNNotificationContent) -> Void) {
    
        // setup sailthru service extension
        self.marigoldNotificationServiceExtension = MARNotificationServiceExtension(appKey: "YOUR_APP_KEY", andGroupName: "group.your.group.name")
        
        // handle notification request
        self.marigoldNotificationServiceExtension?.didReceive(request, withContentHandler: contentHandler)
    }
    
    override func serviceExtensionTimeWillExpire() {
    		// handle extension timeout
      self.marigoldNotificationServiceExtension?.serviceExtensionTimeWillExpire()
    }
}

However, if you would prefer to set up your service extension manually then you can include and use the MarigoldExtension class directly:

@property (nullable, nonatomic, strong) MarigoldExtension *extension;
@property (nullable, nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
  	// retrieve instance for app group
    self.extension = [[MarigoldExtension alloc] initWithAppKey:@"YOUR_APP_KEY" groupName:@"group.your.group.name"];
  
  	// handle notification request
    [self.extension handleNotificationRequest:request];
    
    // Register any custom events
		[self.extension logEvent:@"my event"];
  
  	// Continue as normal
}
var extension : MarigoldExtension?
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var downloadTask: URLSessionDownloadTask?

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler:@escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
    self.extension = MarigoldExtension(appKey: "YOUR_APP_KEY", groupName: "group.your.group.name")
  
  	// handle notification request
    self.extension?.handleNotificationRequest(request)
    
    // Register any custom events
		self.extension?.logEvent("my event")
  
  	// Continue as normal
}

UNNotificationContentExtension

In the UNNotificationContentExtension you should start the engine and retrieve the instance in the viewDidLoad method and the use the extension to handle the notification when it is received:

@property (strong, nonatomic) MarigoldExtension *extension;

@implementation NotificationViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    self.extension = [[MarigoldExtension alloc] initWithAppKey:@"YOUR_APP_KEY" groupName:@"group.your.group.name"];
}

- (void)didReceiveNotification:(UNNotification *)notification {
  	// handle notification
    [self.extension handleNotification:notification];
  
  	// Register any custom events
		[self.extension logEvent:@"my event"];
    
    // Continue as normal
}
var extension : MarigoldExtension?

@implementation NotificationViewController

override func viewDidLoad() {
    super.viewDidLoad()
  
    self.extension = MarigoldExtension(appKey: "YOUR_APP_KEY", groupName: "group.your.group.name")
}

- (void)didReceiveNotification:(UNNotification *)notification {
  	// handle notification
    self.extension?.handleNotification(notification)
  
  	// Register any custom events
		self.extension?.logEvent("my event")
    
    // Continue as normal
}