iOS
Advanced
Topics

This is a developers' guide for setting up a LiveLike SDK configuration for native iOS apps. We will take you through the basic technical steps for configuration and show you how to send your first widgets and chat messages. We will also provide detailed samples and instructions for a complete LiveLike integration.

Sync

This feature enables you to publish spoiler-free content with the Engagement SDK. When enabled, all published content from the CMS and chat messages will be synced with the live video (we don't currently support VOD). To enable Sync you will need to provide a value that represents the date/time of your media player's current playback position.

To enable Sync you need to implement the ContentSessionDelegate protocol, implement the playheadTimeSource method, and then assign to the ContentSession.delegate property.

If you are using AVPlayer and streams that support the EXT-X-PROGRAM-DATE-TIMEtag, we provide an AVPlayer extension in the EngagementSDK to allow you to quickly enable the Sync feature. Simply, return the programDateTime property of your AVPlayer instance, which will extract the reference date from supported stream types. See the samples below.

Otherwise, if you're using a different player or need to calculate the reference time using a different strategy, you will need to return your custom date/time as a Date object.

extension ViewController: ContentSessionDelegate {
    func playheadTimeSource() -> Date {
        return player.programDateTime
    }
}

class ViewController: UIViewController{
    var session: ContentSession?
    
    override func viewDidLoad(){
        let config = SessionConfiguration(programID: "< program-id >")
        session = engagementSDK.contentSession(config: config, delegate: self)
        widgetViewController.session = session
        chatViewController.session = session
    }
}
@interface ViewController : UIViewController < LLContentSessionDelegate >
...
@end

@implementation ViewController
...
- (NSDate*)playheadTimeSource {
    return player.programDateTime;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LLSessionConfiguration *config = [[LLSessionConfiguration alloc] initWithProgramID:@"123"];
    id< LLContentSession > session = [engagementSDK contentSessionWithConfig:config delegate:self];

    self.session = session;
    chatViewController.session = session;
    widgetViewController.session = session;
}
...
@end

Pausing Content Sessions

Content Sessions can be paused to temporarily ignore all incoming Widgets and Chat messages until resumed. This can be useful if you need to display an advertisement without overlay from the Engagement SDK, as an example.

//Pause
session?.pause()

//Resume
session?.resume()
//Pause
[[self session] pause];

//Resume
[[self session] resume];

Customize Your Theme

Our Engagement SDK allows you to customize fonts, colors, corner radii, padding, margins and spacing to conform to your style guide. The snippets below show chat corner radii and chat background colors, as well as widget corner radii and widget background colors as sample customizations.

To get started, create a theme object, adjust the relevant properties, and apply that object to your WidgetViewController and ChatViewController. For the full list of customizable properties see the API Reference.

The same widget with different themes:

let customTheme = Theme()
customTheme.chatCornerRadius = 16
customTheme.chatBodyBackgroundColor = .red
customTheme.widgetCornerRadius = 8
customTheme.widgetBodyColor = .blue
if let font = UIFont(name: "< font-name >", size: 16) {
    customTheme.fontPrimary = font
}

widgetViewController.setTheme(customTheme)
chatViewController.setTheme(customTheme)
LLTheme *customTheme = [[LLTheme alloc] init];
[customTheme setChatCornerRadius:16];
[customTheme setChatBodyColor:[UIColor redColor]];
[customTheme setWidgetCornerRadius:8];
[customTheme setWidgetBodyColor:[UIColor blueColor]];
[customTheme setFontPrimary:[UIFont fontWithName:@"< font-name >" size:16]];

[widgetViewController setTheme:customTheme];
[chatViewController setTheme:customTheme];

Chat Show/Hide

The ChatViewController provides functionality to show and hide the chat view, by animating it off the screen. This can be useful if you'd like to ensure none of the Engagement SDK UI is covering sections of the screen, like when playing ads, showing video player controls for the user to interact with or using the space behind chat to show your own UI elements. Take a look at our provided UX/UI best-practices for more information and tips.

This can be achieved by calling the related functions as shown below:

let chatViewController = ChatViewController()
...
// Hide the chatViewController, by animating it off the screen.
chatViewController.hide()

// Show the chatViewController, by animating it onto the screen, to its original position.
chatViewController.show()
LLChatViewController *chatViewController = [[LLChatViewController alloc] init];

// Hide the chatViewController, by animating it off the screen.
[chatViewController hide];

// Show the chatViewController, by animating it onto the screen, to its original position.
[chatViewController show];

If the ChatViewController is hidden for longer than 3 seconds, chat functionality is paused. When it is shown again, it will automatically reconnect and reload the latest chat messages, using the existing session.

The animation direction can also be customized by setting the following property:

chatViewContoller.animationDirection = .left
chatViewController.animationDirection = Left;

Widget Show/Hide

The WidgetViewController provides functionality to toggle widget visibility. This can be useful if you want to give your users the power to see and not see Widgets at will. This is different from Pause/Resume in that widgets received while hidden will run in the background and will be interact-able from their current state if shown.

let widgetViewController = WidgetViewController()

// Toggle widget visibility off. The current widget will animate off-screen and run in background. 
// Future widgets will also run in background.
widgetViewController.hide()

// Toggle widget visibility on. A widget running in the background will animate on-screen.
// Future widgets will appear like normal.
widgetViewController.show()
LLWidgetViewController *widgetViewController = [[LLWidgetViewController alloc] init];

// Toggle widget visibility off. The current widget will animate off-screen and run in background. 
// Future widgets will also run in background.
[widgetViewController hide];

// Toggle widget visibility on. A widget running in the background will animate on-screen.
// Future widgets will appear like normal.
[widgetViewController show];

Widget Presentation APIs

A WidgetPresentationDelegate can set on a WidgetViewController, which will then be informed and consulted about widget presentation. It can respond before and after presentation and dismissal via the will/did present/dismiss methods, which can be used to perform custom animations. It can decide for each widget whether to present, discard, or defer the presentation of the widget until a later time. Deferred widgets can then be discarded or presented, in the same order they were received and deferred, via WidgetViewController‘s presentDeferredWidget and discardDeferredWidget methods. The nextDeferredWidget method allows you to see if there is a deferred widget waiting to be addressed. Through these APIs, custom behaviors are made possible, such as deferring a widget due to other user interactions on your custom interface, or deferring fullscreen video mode to present a less intrusive toast before showing the full widget.

This sample implementation of a delegate demonstrates animating a constraint in response to widget presentation and dismissal.

extension MyViewController: WidgetPresentationDelegate {
    func didPresent(widget: WidgetViewModel, in view: UIView) {
        UIView.animate(withDuration: 0.33) { [weak self] in
            guard let self = self else { return }
            self.widgetSlidingConstraint.constant = widget.height + 16
            self.view.layoutIfNeeded()
        }
    }

    func willDismiss(widget: WidgetViewModel, in view: UIView) {
        UIView.animate(withDuration: 0.33) { [weak self] in
            guard let self = self else { return }
            self.widgetSlidingConstraint.constant = 0
            self.view.layoutIfNeeded()
        }
    }
}

Analytics

Our Engagement SDK allows you to hook into our analytic events, making it possible for you to pass through the analytic data to your own provider.

This can be done by conforming to the EngagementAnalyticsDelegate. In the code snippet below, we've included potential implementations with the most common analytic frameworks:

extension VideoViewController: EngagementAnalyticsDelegate {
    func engagementAnalyticsEvent(name: String, withData data: [String: Any]) {
        print("EngagementAnalyticsDelegate: Name->\(name), data->\(data)")

        // Localytics
        Localytics.tagEvent(name, attributes: data)

        // Google Analytics - Firebase
        Analytics.logEvent(name, parameters: data)
    
        // Mixpanel
        Mixpanel.mainInstance().track(event: name, properties: data)
    }
}
- (void)engagementAnalyticsEventWithName:(NSString *)name withData:(NSDictionary *)data {
    NSLog(@"engagementAnalyticsEventWithName: Name->%@, Data->%@", name, data);

    // Localytics
    [Localytics tagEvent:name
                attributes:data];

    // Google Analytics - Firebase
    [FIRAnalytics logEventWithName:name
                  parameters:data];

    // Mixpanel
    [[Mixpanel sharedInstance]  track:name
                                properties:data];
}

And then setting the analyticsDelegate in the desired class:

engagementSDK.analyticsDelegate = self
engagementSDK.analyticsDelegate = self;

Available Events

  • App Focus Changed
  • Chat Scroll Completed
  • Chat Scroll Initiated
  • Chat View Controller Closed
  • Chat View Controller Started
  • Chatbox View Controller Status Changed
  • Content Session Closed
  • Content Session Started
  • Keyboard Hidden
  • Keyboard Selected
  • Message Sent
  • Orientation Changed
  • Widget Dismissed
  • Widget Displayed
  • Widget Interacted
  • Widget View Controller Closed
  • Widget View Controller Started
  • Widget View Controller Status Changed

Widget Toggle Plugin

An optional feature set to empower your users to toggle whether they will receive widgets.

After the user has dismissed 3 (configurable) widgets prematurely - they will be presented with a Widget Disable Dialog.

Selecting For Now will disable widgets for the duration of the Content Session.

Selecting Forever will disable widgets until the user explicitly toggles the widgets on. This setting is stored in UserDefaults.

Regardless of the choice, the toggle button will be added to your layout for the user to toggle widgets at will.

Installation

Pre-Requisites:

  1. A reference to a Content Session
  2. A UIView that will serve as the parent view for the toggle button (referred to as someParentView in snippet below)
let widgetTogglePlugin = WidgetToggle(toggleParentView: someParentView)
session.install(plugin: widgetTogglePlugin)
LLWidgetToggle *widgetTogglePlugin = [[LLWidgetToggle alloc] initWithToggleParentView:someParentView];
[session installWithPlugin:widgetTogglePlugin];

User Profiles

A user profile represents the preferences and activities of a user of the product. This is where activity history and profile data such as usernames are stored. In the future, gamification rewards such as points & badges will also be stored here.

When creating a session for a new user, you can request for a new access token. When creating a new session for an existing user or retrieving the user data, you can use the stored access token.

Apps that don’t require sign in or don’t have the concept of user accounts can simply store the access token locally. User data will be accessible for the lifetime of the application, but will be lost when the app is reinstalled and the access token is lost. Apps that require sign in or have the concept of a user account can store the access token in their CRM or database. That would allow the data to be retrieved across app installs and multiple platforms.

Generate Access Token

You should generate a new access token for each unique user and store it somewhere to let the EngagementSDK identify this unique user in the future.

EngagementSDK.generateAccessToken(clientID: "< client-id>") { accessToken in
    // handle access token here
}
[LLEngagementSDK generateAccessTokenWithClientID:@"< client-id>" completion:^(NSString * *_Nonnull* accessToken) {
    // handle access token here
}];

Using Access Token

To identify the user within the EngagementSDK you must initialize the SDK with the Access Token.

let engagementSDK = EngagementSDK(clientID: "< client-id>", livelikeAccessToken: "< access-token>")
LLEngagementSDK *engagementSDK = [[LLEngagementSDK alloc] initWithClientID:@"< client-id>" livelikeAccessToken:@"< access-token>"];

Contacting LiveLike Support

If you've been unable to solve your problem with the help of the sections above, please feel free to contact the LiveLike support team at support@livelike.com.