iMessage + Apps + Messages app

iMessage + Apps with Pyze

iMessage service, Apple’s secure instant messaging service, accessible via the Messages apps on iOS and macOS, recently got an upgrade with iOS 10: third-party iMessage Apps and the Messages App store.  Currently, the Messages App on macOS can view messages created on iOS Messages App.

Let’s first take a brief look at terminology related to iMessage service.

Terminology

iMessage
Apple’s Instant Messaging service, which boasts end-to-end encryption.

Messages Apps
The messaging apps on iOS and macOS developed by Apple, or now the platform for stickers and 3rd party apps

iMessage Apps
Apps developed by 3rd-party app publishers / developers that reside in Messages App. 

Messages App store
An app store accessible within Messages App to browse, buy and install Messages Apps.

Messages Framework
Apple Messages Framework offers a number of classes that iMessage apps leverage  to interact with the Messages app.

App Extension
App Extension is a way to extend your app’s functionality to other apps on a user’s device.  E.g. Share a picture from within Photos app to Twitter.  The Twitter iOS App and Twitter App Extension have different life cycles and run independently of each other.  iMessage Apps are implemented as app extensions.

iMessage Application
The project that you use in Xcode to create an iMessage App.

 

Using the Messages Framework in iOS 10 app publishers can create an app extension that lets users interact with your app directly within the Messages app.

Messaging users get easy access your content and functionality, without having to leave and launch the Messages app. They can conveniently share content, edit photos, play games, send payments, and collaborate with friends within a custom interface that you design.

Messaging users can easily create and share content, add stickers, send payments, and collaborate expressively with friends without needing to switch to another app and leave the messaging conversations.

We have been writing extensively about bots and apps that use messaging as a platform in the article: Mobile Messaging: Yesterday vs. today & tomorrow.

Types of iMessage Apps

App publishers can use the Messages framework to create two types of app extensions: Sticker packs and iMessage apps. Both extension types can be created as standalone apps for Messages or as app extensions within a containing iOS app.

Standalone iMessages App

You write an app extension, using the iMessage extension point to extend the functionality and content of your app to the iOS Messages App.

Standalone Sticker pack app

App publishers can build a basic sticker pack made up of images or animated images, without writing code.

Sticker pack extension to iOS app

App publishers can bundle a sticker pack within an iOS app.

iMessage extension to iOS app

An iMessage extension can include the same features as a standalone iMessage app, and is distributed inside an iOS app.

To learn more about developing iMessage Apps, watch WWDC videos part 1 and part 2 and see the reference here.

Messages Framework

The classes, protocol and enumerations made available by Apple to app developers to create iMessage Apps, are collectively part of the Messages Framework.

Use the MSMessagesAppViewController as the base class for your view controller to manage your Messages app extension. It has life-cycle management methods and supports the compact and full screen presentation style.

Your app can insert text, stickers, or media into the Messages app’s input field.  The app users have to explicitly click on the arrow icon to send the inserted content to the remote participants.  The MSConversation class provides methods to insert content, and access to local participant (the sender) and array of remote participants (one or more receivers).

App specific data is carried in instance of MSMessage class. Recipient updatable messages (for instance collaborative messages) are initialized explicitly with MSSession.   The appearance of MSMessage objects is managed via the MSMessageLayout.

PyzeiMessageApps curated event class

Pyze has a large library of curated events for various verticals ranging from mobile commerce to app-controlled drones.  We just added another curated event class to allow iMessage App extensions to track creating an inserting stickers, iMessages, attachments and text messages into a user’s messages. The API also has lifecycle methods to manage message lifecycle.

Pyze API  Curated class PyzeiMessageApps

Sample code and assets

Creating an iMessage App extension

This section is about creating an iMessage App extension and posting events to https://growth.pyze.com about how your iMessage App is being used.

Prerequisites

Note, you need the latest Xcode version 8.0+ from App Store on OS X El Capitan or macOS Sierra (preferred).

Creating the iMessage Application

  • Start Xcode and create a new Xcode project of type: iMessage Application.
    screen-shot-2016-10-03-at-8-42-41-am
  • Compile and build the project
  • Close the Xcode Project (Important:  We will create a workspace and use it going forward, instead of a project created in Xcode)
  • Start Terminal and change directory to the folder where you created the above project.
  • Install the Pyze SDK using Cocoapods Pyze SDK using cocoapods.  If you are new to cocoapods see here.
  • cd to the folder where you created the project and create the pod file using pod init
cd path/to/your/iMessageApp/project/
pod init
  • Open the created podfile in your favorite code editor and add pod ‘pyze-sdk-iOS’ in the MessagesExtention target as shown.
...
target 'MessagesExtension' do
    ...
    # Pods for MessagesExtension
    pod 'pyze-sdk-iOS'
end
  • Run pod update to create a workspace which we will use instead of project file.
pod update
  • Open the created workspace instead of project. From terminal using open <workspacename> or double click the workspace in Finder
open path/to/your/xcode/workspacefile.xcworkspace
  • Locate MessagesViewController.m under MessagesExtention folder
  • Add a reference to Pyze module
@import Pyze;
  • Goto growth.pyze.com, Add a new app and get a Pyze App Key.
  • Add the following in MessagesViewController.m,  using the Pyze App Key you obtained from growth.pyze.com
+(void) load {
    [Pyze initialize:@"replace with pyze app key obtained above" 
   withLogThrottling:PyzelogLevelMinimal];
}
  • Create three buttons in your storyboard to send a sticker, iMessage and PDF.  You will later create button handlers for creating a sticker, message and attachment.

screen-shot-2016-10-03-at-4-01-13-pm

  • Insert the following code in MessagesViewController.m
// Initialize Pyze in the load method
+(void) load {
    [Pyze initialize:@"QF3zSg9SRwWdV6rsncEMBw" withLogThrottling:PyzelogLevelMinimal];
}
// Helper Method to create a dynamic image we will use when sending a message
-(UIImage *) snapShot {
    CGRect frameRect = CGRectMake(self.view.frame.size.width, self.view.frame.size.height, 300, 300);
    UIView * frameView = [[UIView alloc] initWithFrame:frameRect];
    frameView.backgroundColor = [UIColor whiteColor];
    
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(75, 75, 150, 150)];
    [label setFont:[UIFont systemFontOfSize:40.0f]];
    label.backgroundColor = [UIColor blackColor];
    label.textColor = [UIColor colorWithRed:69 / 255.0f green:165 / 255.0f blue:238 / 255.0f alpha:1.0f];
    label.text = [NSString stringWithFormat:@"%d", (int)++self.tokenCounter];
    label.layer.cornerRadius = CGRectGetWidth(frameRect) / 2.0f;
    label.clipsToBounds = YES;
    label.textAlignment = NSTextAlignmentCenter;
    
    [frameView addSubview:label];
    [self.view addSubview:frameView];
    
    UIGraphicsBeginImageContextWithOptions(frameRect.size, NO, [[UIScreen mainScreen] scale]);
    [frameView drawViewHierarchyInRect:frameView.bounds afterScreenUpdates:YES];
    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    [frameView removeFromSuperview];
    
    return image;
}
// Create and Insert an iMessage upon button click
- (IBAction)createiMessage {
    
    UIImage * image = [self snapShot];
    MSConversation * conversation = self.activeConversation;
    if (image && conversation) {
        MSMessageTemplateLayout * activeLayout = [[MSMessageTemplateLayout alloc] init];
        activeLayout.image = image;
        activeLayout.caption = @"Message Counter";
        activeLayout.subcaption = @"Message subcaption";
        activeLayout.trailingCaption = @"Trailing caption";
        activeLayout.trailingSubcaption = @"Trailing Subcaption";
        activeLayout.mediaFileURL = [NSURL URLWithString:@"Path to media URL"];
        activeLayout.imageTitle = @"Image counter";
        activeLayout.imageSubtitle = @"Image subtitle";
        
        MSMessage * message = [[MSMessage alloc] init];
        message.layout = activeLayout;
        message.URL = [NSURL URLWithString:@"Empty URL"];
        message.summaryText = @"This is Summary";
        
        [conversation insertMessage:message completionHandler:^(NSError * error) {
            NSLog(@"error %@",error);
            NSMutableDictionary * dictionary = [self fillMessagingAttributes:message conversation:conversation];
            [PyzeiMessageApps postInsertMessageWithAttributes: dictionary];
        }];
    }
}
//Create and Insert Sticker upon button click
- (IBAction)createSticker
{
    NSURL * urlPath = [[NSBundle mainBundle] URLForResource:@"pyzeLogo" withExtension:@"png"];
    if (urlPath) {
        MSSticker * sticker = [[MSSticker alloc] initWithContentsOfFileURL:urlPath
                                                      localizedDescription:@"Pyze logo" error:nil];
        MSConversation * conversation = self.activeConversation;
        if (conversation && sticker) {
            [conversation insertSticker:sticker completionHandler:^(NSError * error) {
                NSLog(@"error %@",error);
                NSMutableDictionary * attributes = [NSMutableDictionary dictionary];
                
                // conversation details
                attributes[@"localParticipantHash"] = [Pyze hash:[conversation.localParticipantIdentifier UUIDString]];
                if (conversation.remoteParticipantIdentifiers)
                    attributes[@"remoteParticipants"] = @(conversation.remoteParticipantIdentifiers.count);
                
                NSMutableString * remoteParticipantHashes = [NSMutableString string];
                for (NSUUID * uuid in conversation.remoteParticipantIdentifiers) {
                    [remoteParticipantHashes appendString:[Pyze hash:[uuid UUIDString]]];
                }
                
                if (remoteParticipantHashes && remoteParticipantHashes.length)
                    attributes[@"remoteParticipantsHashes"] = remoteParticipantHashes;
                
                
                [PyzeiMessageApps postInsertStickerWithLocalizedDescription:true
                                              withStickerImageFileURLString:true
                                                             withAttributes:attributes];
            }];
        }
    }
}
//Create and Insert an attachment upon button click
- (IBAction)createAttachment
{
    NSURL * urlPath = [[NSBundle mainBundle] URLForResource:@"PyzeGrowthIntelligence" withExtension:@"pdf"];
    if (urlPath) {
        MSConversation * conversation = self.activeConversation;
        
        [conversation insertAttachment:urlPath
                 withAlternateFilename:nil
                     completionHandler:^(NSError * error) {
                         NSLog(@"error %@",error);
                         NSMutableDictionary * attributes = [NSMutableDictionary dictionary];
                         
                         // conversation details
                         attributes[@"localParticipantHash"] = [Pyze hash:[conversation.localParticipantIdentifier UUIDString]];
                         if (conversation.remoteParticipantIdentifiers)
                             attributes[@"remoteParticipants"] = @(conversation.remoteParticipantIdentifiers.count);
                         
                         NSMutableString * remoteParticipantHashes = [NSMutableString string];
                         for (NSUUID * uuid in conversation.remoteParticipantIdentifiers) {
                             [remoteParticipantHashes appendString:[Pyze hash:[uuid UUIDString]]];
                         }
                         
                         if (remoteParticipantHashes && remoteParticipantHashes.length)
                             attributes[@"remoteParticipantsHashes"] = remoteParticipantHashes;
                         
                         [PyzeiMessageApps postInsertAttachmentWithURL:true
                                                 withAlternateFileName:true
                                                        withAttributes:attributes];
                         
                     }];
    }

}
-(NSMutableDictionary *) fillMessagingAttributes:(MSMessage *)message
                                    conversation:(MSConversation *)conversation
{
    NSMutableDictionary * attributes = [NSMutableDictionary dictionary];
    // Message details
    if (message) {
        if (message.URL) attributes[@"URL"] = @"1";
        if (message.summaryText) attributes[@"summaryText"] = @"1";
    }
    // message layout details
    MSMessageTemplateLayout * layout = (MSMessageTemplateLayout *)message.layout;
    if (layout) {
        if (layout.caption) attributes[@"caption"] = @"1";
        if (layout.subcaption) attributes [@"subcaption"] = @"1";
        if (layout.trailingCaption) attributes[@"trailingCaption"] = @"1";
        if (layout.trailingSubcaption) attributes[@"trailingSubcaption"] = @"1";
        if (layout.image) attributes[@"image"] = @"1";
        if (layout.mediaFileURL) attributes[@"mediaFileURL"] =  @"1";
        if (layout.imageTitle) attributes[@"imageTitle"] = @"1";
        if (layout.imageSubtitle) attributes[@"imageSubtitle"] = @"1";
    }
    // conversation details
    if (conversation) {
        attributes[@"localParticipantHash"] = [Pyze hash:[conversation.localParticipantIdentifier UUIDString]];
        if (conversation.remoteParticipantIdentifiers)
            attributes[@"remoteParticipants"] = @(conversation.remoteParticipantIdentifiers.count);
        
        NSMutableString * remoteParticipantHashes = [NSMutableString string];
        for (NSUUID * uuid in conversation.remoteParticipantIdentifiers) {
            [remoteParticipantHashes appendString:[Pyze hash:[uuid UUIDString]]];
        }
        
        if (remoteParticipantHashes && remoteParticipantHashes.length)
            attributes[@"remoteParticipantsHashes"] = remoteParticipantHashes;
    }
    return attributes;
}
  • Finally, add the Pyze methods to didReceiveMessage,  didStartSendingMessage, and didCancelSendingMessage
-(void)didReceiveMessage:(MSMessage *)message conversation:(MSConversation *)conversation {
    [PyzeiMessageApps postReceiveMessageWithAttributes:
       [self fillMessagingAttributes:message 
         conversation:conversation]];
}
-(void)didStartSendingMessage:(MSMessage *)message conversation:(MSConversation *)conversation {
    [PyzeiMessageApps postMessageStartSendingWithAttributes:
      [self fillMessagingAttributes:message 
        conversation:conversation]];
}
-(void)didCancelSendingMessage:(MSMessage *)message conversation:(MSConversation *)conversation {
    [PyzeiMessageApps postCancelSendingMessageWithAttributes: 
        [self fillMessagingAttributes:message 
          conversation:conversation]];
}
  • Compile and run

simulator-screen-shot-oct-3-2016-5-21-55-pmsimulator-screen-shot-oct-3-2016-5-22-40-pmsimulator-screen-shot-oct-3-2016-5-26-17-pm

  • You can track message on growth.pyze.com.

screen-shot-2016-10-03-at-5-11-54-pm

It is important to note that you did not send any message content, or sender or recipients IDs to Pyze using the helper method fillMessagingAttributes above.

Ram Nagurand and Dickey Singh contributed to this blog.

 

One thought on “iMessage + Apps + Messages app

Comments are closed.