Introduction

Bit6 is a real-time, cloud-based communications-as-a-service platform that allows mobile and web application developers to quickly and easily add voice/video calling, texting, and multimedia messaging capabilities into their apps.

GitHub version

Resources

Getting Started

Get Bit6 API Key

Go to Dashboard and get the API Key for your app.

Manual Configuration

Step 1. Download the Bit6 SDK.

Step 2. Add Bit6.framework and Bit6Resources.bundle to your project.

Step 3. Link the following frameworks and libraries: libicucore.tdb, libstdc++.tdb, GLKit and VideoToolbox.

Step 4. Set the Architectures value to be $(ARCHS_STANDARD_32_BIT) for iPhone Simulators and $(ARCHS_STANDARD) for devices.

Step 5. Set your Deployment Target to be iOS 7.0 or later.

Step 6. In your Build Settings add -ObjC to the 'Other Linker Flags'

Configuration using Cocoapods

Version License Platform

Installation

Bit6 starting from version v0.8.5 is available through CocoaPods. To install it, simply add the following lines to your Podfile:

# Uncomment this line if you're using Swift
# use_frameworks!
pod "Bit6"

Building for iOS9

Whitelist Bit6 for Network Requests

If you compile your app with iOS SDK 9.0, you will be affected by App Transport Security. You will need to add the following to your info.plist file:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>amazonaws.com</key>
            <dict>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSTemporaryExceptionMinimumTLSVersion</key>
                <string>TLSv1.1</string>
            </dict>
        </dict>
    </dict>

Bitcode Bit6 SDK doesn't include support for bitcode yet. You can disable bitcode in your target configuration:

Setup Application Delegate

Step 1. Import Bit6: @import Bit6; for ObjectiveC and import Bit6 for Swift.

Step 2. Launch Bit6 with your API Key.

//ObjectiveC
- (BOOL)application:(UIApplication *)application 
      didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // start of your application:didFinishLaunchingWithOptions: method
        // ...

        [Bit6 startWithApiKey:@"your_api_key"];

        NSDictionary *remoteNotificationPayload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
        if (remoteNotificationPayload) {
            [[Bit6 pushNotification] didReceiveRemoteNotification:remoteNotificationPayload];
        }

        // The rest of your application:didFinishLaunchingWithOptions: method
        // ...
    }
//Swift
func application(application: UIApplication, 
    didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {

        // start of your application:didFinishLaunchingWithOptions: method
        // ...

        Bit6.startWithApiKey("your_api_key")

        if let remoteNotificationPayload = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
            Bit6.pushNotification().didReceiveRemoteNotification(remoteNotificationPayload as [NSObject : AnyObject])
        }

        // The rest of your application:didFinishLaunchingWithOptions: method
        // ...
    }

Step 3. Support for Push Notifications

NOTE. Remember to upload your development and production APNS Certificates to Dashboard.

Implement the following methods in your AppDelegate

//ObjectiveC
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
    [application registerForRemoteNotifications];
}

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler
{
    [[Bit6 pushNotification] handleActionWithIdentifier:identifier forRemoteNotification:userInfo completionHandler:completionHandler];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    [[Bit6 pushNotification] didReceiveRemoteNotification:userInfo];
}

- (void) application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    [[Bit6 pushNotification] didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void) application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    [[Bit6 pushNotification] didFailToRegisterForRemoteNotificationsWithError:error];
}
//Swift
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
   application.registerForRemoteNotifications()
}

func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [NSObject : AnyObject], completionHandler: () -> Void) {
   Bit6.pushNotification().handleActionWithIdentifier(identifier, forRemoteNotification: userInfo, completionHandler: completionHandler)
}

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
   Bit6.pushNotification().didReceiveRemoteNotification(userInfo)
}

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
   Bit6.pushNotification().didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler)
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
   Bit6.pushNotification().didRegisterForRemoteNotificationsWithDeviceToken(deviceToken)
}

func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
   Bit6.pushNotification().didFailToRegisterForRemoteNotificationsWithError(error)
}

authenticationSession

Each user in the system has one or more identities - user id, username, email, facebook id, google account, phone number etc. Identities are required for user authentication, managing contacts, identifying user's network. An identity is represented by a URI.

An username is case-insensitive and must consist of alphanumeric characters, e.g. usr:john or usr:test123.

Bit6 supports various authentication mechanisms described in the following sections.

Create user account

Create a new user account with a username identity and a password.

//ObjectiveC
Bit6Address *address = [Bit6Address addressWithUsername:@"john"];

[[Bit6 session] signUpWithUserIdentity:address
                              password:@"secret" 
                     completionHandler:^(NSDictionary *response, NSError *error) {
    if (!error) {
        //Sign Up Completed
    }
    else {
        //Sign Up Failed
    }
}];
//Swift
let address = Bit6Address(username:"john")

Bit6.session().signUpWithUserIdentity(address, 
                             password:"secret") { (response,error) in
    if error == nil {
        //Sign Up Completed
    }
    else {
        //Sign Up Failed
    }
}

Login

Login into an existing account using an Identity and a password.

//ObjectiveC
Bit6Address *address = [Bit6Address addressWithUsername:@"john"];

[[Bit6 session] loginWithUserIdentity:address 
                             password:@"secret" 
                    completionHandler:^(NSDictionary *response, NSError *error) {
    if (!error) {
        //Login Completed
    }
    else {
        //Login Failed
    }
}];
//Swift
let address = Bit6Address(username:"john")

Bit6.session().loginWithUserIdentity(address, 
                            password:@"secret"){ (response,error) in
    if error == nil {
        //Login Completed
    }
    else {
        //Login Failed
    }
}

Check if the user is authenticated

//ObjectiveC
if ([Bit6 session].authenticated) {
    NSLog(@"Active Session");
}
else {
    NSLog(@"Not Active Session");
}
//Swift
if Bit6.session().authenticated {
    NSLog("Active Session")
}
else {
    NSLog("Not Active Session")
}

Logout

//ObjectiveC
[[Bit6 session] logoutWithCompletionHandler:^(NSDictionary *response, NSError *error) {
    if (! error) {
        NSLog(@"Logout Completed");
    }
}];
//Swift
Bit6.session().logoutWithCompletionHandler({(response,error) in 
    if error == nil {
        NSLog("Logout Completed")
    }
})

Listening to Login and Logout Notifications

//ObjectiveC

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(loginCompletedNotification:) 
                                             name:Bit6LoginCompletedNotification
                                           object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(logoutCompletedNotification:) 
                                             Bit6LogoutCompletedNotification
                                           object:nil];

- (void) loginCompletedNotification:(NSNotification*)notification
{

}

- (void) logoutCompletedNotification:(NSNotification*)notification
{

}

//Swift

NSNotificationCenter.defaultCenter().addObserver(self, 
                                        selector: "loginCompletedNotification:", 
                                            name: Bit6LoginCompletedNotification, 
                                          object: nil)

NSNotificationCenter.defaultCenter().addObserver(self, 
                                        selector: "logoutStartedNotification:", 
                                            name: Bit6LogoutStartedNotification, 
                                          object: nil)

func loginCompletedNotification(notification:NSNotification)
{

}

func logoutStartedNotification(notification:NSNotification)
{

}

authenticationOAuth

Bit6 integrates with various OAuth1 and OAuth2 providers for simplified user authentication.

See the FBVideoCallsDemo sample project included with the sdk.

Signin with an OAuth provider

Create a new Bit6 account or login into an existing one. In this example we use Facebook Login.

//ObjectiveC
[[Bit6 session] getAuthInfoCompletionHandler:^(NSDictionary *response, NSError *error) {
    if (response[@"facebook"][@"client_id"]){
        FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
        [login logInWithReadPermissions:@[@"public_profile",@"email",@"user_friends"] fromViewController:self.view.window.rootViewController handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
            if (error || result.isCancelled) {
                //An Error Ocurred
            }
            else {
                [Bit6.session oauthForProvider:@"facebook" params:@{@"client_id":response[@"facebook"][@"client_id"], @"access_token":[FBSDKAccessToken currentAccessToken].tokenString} completion:^(NSDictionary *response, NSError *error) {
                    if (!error) {
                        //Login Completed
                    }
                    else {
                        //Login Failed
                    }
                }];
            }
        }];
    }
}];
//Swift
Bit6.session().getAuthInfoCompletionHandler(){ (response, error) in
    if let facebook = response["facebook"] as? NSDictionary {
        let client_id = facebook["client_id"] as! NSString
        let login = FBSDKLoginManager()
        login.logInWithReadPermissions(["public_profile","email","user_friends"], fromViewController:self.view.window!.rootViewController) { (result, error) in
            if error != nil || result.isCancelled {
                //An Error Ocurred
            }
            else {
                Bit6.session().oauthForProvider("facebook", params:["client_id":client_id, "access_token":FBSDKAccessToken.currentAccessToken().tokenString]){ (response, error) in
                    if error == nil {
                        //Login Completed
                    }
                    else {
                        //Login Failed
                    }
                }
            }
        }
    }
}

To get a Bit6Address object you can do the following:

//ObjectiveC
Bit6Address *address = [Bit6Address addressWithFacebookId:friendId];
//Swift
let address = Bit6Address(facebookId:friendId)

callingVoice & Video Calls

See the Bit6CallDemo and Bit6CallDemo-Swift sample projects included with the sdk.

Make a Call

//ObjectiveC
Bit6Address *address = ...
Bit6CallController *callController = [Bit6 createCallTo:address streams:Bit6CallStreams_Audio];

if (callController){
    //trying to reuse the previous viewController, or create a default one
    Bit6CallViewController *callViewController = [Bit6 callViewController] ?: [Bit6CallViewController createDefaultCallViewController];

    //set the call to the viewController
    [callViewController addCallController:callController];

    //start the call
    [callController start];

    //present the viewController
    [Bit6 presentCallViewController:callViewController];
}
else {
    //cannot make call to specified address
}
//Swift
let address : Bit6Address = ...
let callController = Bit6.createCallTo(address, streams:.Audio)

if callController != nil {
    //trying to reuse the previous viewController, or create a default one
    let callViewController = Bit6.callViewController() ?? Bit6CallViewController.createDefaultCallViewController()

    //set the call to the viewController
    callViewController.addCallController(callController)

    //start the call
    callController.start()

    //present the viewController
    Bit6.presentCallViewController(callViewController)
}
else {
    NSLog("Call Failed")
}

Listen to call state changes

//ObjectiveC

//register the observer
[callController addObserver:self forKeyPath:@"callState" options:NSKeyValueObservingOptionOld context:NULL];

//deregister observer
//[callController removeObserver:self forKeyPath:@"callState"];

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([object isKindOfClass:[Bit6CallController class]]) {
        if ([keyPath isEqualToString:@"callState"]) {
            [self callStateChangedNotification:object];
        }
    }
}

- (void) callStateChangedNotification:(Bit6CallController*)callController
{
    switch (callController.callState) {

    }
}
//Swift

//register the observer
callController.addObserver(self, forKeyPath:"callState", options: .Old, context:nil)

//deregister observer
//callController.removeObserver(self, forKeyPath:"callState")

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    guard let object = object,
          let callController = object as? Bit6CallController else { return }

   if keyPath == "callState" {
       self.callStateChangedNotification(callController)
   }
}

func callStateChangedNotification(callController:Bit6CallController) {
    switch (callController.callState) {

    }
}

Receive Calls

By default the incoming calls are handled by a singleton instance of Bit6IncomingCallHandler.

You can customize the incoming in-call screen, the incoming call window prompt or even handle the entire flow by implementing the Bit6IncomingCallHandlerDelegate protocol:

//ObjectiveC
- (BOOL)application:(UIApplication *)application 
      didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   [Bit6 incomingCallHandler].delegate = self;
   //the rest of your code
}

- (Bit6CallViewController*)inCallViewController
{
    //return your custom view controller
}

- (UIView*)incomingCallPromptContentViewForCallController:(Bit6CallController*)callController
{
    //return your custom in-call prompt view
}

//implement this method if you want to override the default incoming call flow.
- (void)receivedIncomingCall:(Bit6CallController*)callController
{

}
//Swift
func application(application: UIApplication, 
    didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        Bit6.incomingCallHandler().delegate = self
        //the rest of your code
    }

func inCallViewController() -> Bit6CallViewController
{
    //return your custom view controller
}

func incomingCallPromptContentViewForCallController(callController: Bit6CallController) -> UIView? {
{
    //return your custom in-call prompt view
}

//implement this method if you want to override the default incoming call flow.
func receivedIncomingCall(callController: Bit6CallController) {

}

Setting the Ringtone

  1. Add the ringtone file to your Xcode project. Apple Docs

  2. Open the Bit6 Dashboard and sign in. Then select your app and open the Settings tab. Here you need to set the ringtone file name for both the APNS development and the APNS production configurations.

Continue Calls in the Background

You can continue the calls in the background by enable "Audio and Airplay" and "Voice over IP" Background Modes in your target configuration.

callingPhone/PSTN Calls

Start a Phone Call

Bit6 interconnects with the phone networks (PSTN) and allows making outgoing phone calls.

Phone numbers must be in E164 format, prefixed with +. So a US (country code 1) number (555) 123-1234 must be presented as +15551231234.

For the test purposes we allow short 1 minute calls to destinations in US and Canada. In later releases we will also add billing, which will in turn allow to connect calls of any length to any destination number.

//ObjectiveC
NSString *destination = @"+14445556666";
Bit6CallController *callController = [Bit6 createCallToPhoneNumber:destination];

if (callController){
    //trying to reuse the previous viewController, or create a default one
    Bit6CallViewController *callViewController = [Bit6 callViewController] ?: [Bit6CallViewController createDefaultCallViewController];

    //set the call to the viewController
    [callViewController addCallController:callController];

    //start the call
    [callController start];

    //present the viewController
    [Bit6 presentCallViewController:callViewController];
}
else {
    //cannot make call to specified address
}
//Swift
let destination = "+14445556666";
let callController = Bit6.createCallToPhoneNumber(destination)

if callController != nil {
    //trying to reuse the previous viewController, or create a default one
    let callViewController = Bit6.callViewController() ?? Bit6CallViewController.createDefaultCallViewController()

    //set the call to the viewController
    callViewController.addCallController(callController)

    //start the call
    callController.start()

    //present the viewController
    Bit6.presentCallViewController(callViewController)
}
else {
    //call failed
}

callingIn-Call UI

See the Bit6CallDemo and Bit6CallDemo-Swift sample projects included with the sdk.

Step 1. Create an UIViewController class that extends from Bit6CallViewController. All the implemented methods are optional.

//ObjectiveC
@interface MyCallViewController : Bit6CallViewController

@end

@implementation MyCallViewController

- (Bit6CallController*) callController
{
    return [[Bit6 callControllers] firstObject];
}

//called in the Main Thread when the UI controls should be updated. For example when the speaker is activated or the audio is muted.
- (void) refreshControlsView
{
    NSLog(@"Audio Muted: %@",[Bit6CallController audioMuted]?@"YES":@"NO");
    NSLog(@"Speaker Enabled: %@",[Bit6CallController speakerEnabled]?@"YES":@"NO");
}

//called in the Main Thread when the status of the call changes.
- (void) callStateChangedNotificationForCallController:(Bit6CallController*)callController
{
    switch (self.callController.callState) {
        case Bit6CallState_NEW: NSLog(@"Call created"); break;
        case Bit6CallState_ACCEPTING_CALL: 
        case Bit6CallState_GATHERING_CANDIDATES: 
        case Bit6CallState_WAITING_SDP: 
        case Bit6CallState_SENDING_SDP: 
        case Bit6CallState_CONNECTING: NSLog(@"Call is connecting"); break;
        case Bit6CallState_CONNECTED: NSLog(@"Call connected"); break;
        case Bit6CallState_END: NSLog(@"Call ends"); break;
        case Bit6CallState_ERROR: NSLog(@"Call ends with an error"); break;
        case Bit6CallState_DISCONNECTED: NSLog(@"Call is disconnected for the moment. Will try to reconnect again."); break;
        case Bit6CallState_MISSED: NSLog(@"Missed Call"); break;
    }

    //refresh the timer label
    [self secondsChangedNotificationForCallController:callController];
}

//called in the Main Thread each second to allow the refresh of a timer UILabel.
- (void) secondsChangedNotificationForCallController:(Bit6CallController*)callController
{
    switch (self.callController.callState) {
        case Bit6CallState_NEW: case Bit6CallState_ACCEPTING_CALL: 
        case Bit6CallState_GATHERING_CANDIDATES: 
        case Bit6CallState_WAITING_SDP: case Bit6CallState_SENDING_SDP: 
        case Bit6CallState_CONNECTING:
            NSLog(@"Connecting..."); break;
        case Bit6CallState_CONNECTED:
            NSLog(@"Seconds: %@",[Bit6Utils clockFormatForSeconds:self.callController.seconds]); break;
        case Bit6CallState_END: case Bit6CallState_MISSED: 
        case Bit6CallState_ERROR: case Bit6CallState_DISCONNECTED:
            NSLog(@"Disconnected"); break;
    }
}

//called in the Main Thread to customize the frames for the video feeds. You can call [self setNeedsUpdateVideoViewLayout] at any time to force a refresh of the frames.
- (void)updateLayoutForVideoFeedViews:(NSArray<Bit6VideoFeedView*>*)videoFeedViews
{
    //Here you can do your own calculations to set the frames or just call super. You can easily keep the aspect ratio by using AVMakeRectWithAspectRatioInsideRect() (import AVFoundation/AVFoundation.h) to do an scaleToFit or Bit6MakeRectWithAspectRatioToFillRect to do an scaleToFill.
    [super updateLayoutForVideoFeedViews:videoFeedViews];
}

@end
//Swift
class MyCallViewController: Bit6CallViewController

var callController : Bit6CallController? {
   get {
       return Bit6.callControllers().first
   }
}

//called in the Main Thread when the UI controls should be updated. For example when the speaker is activated or the audio is muted.
override func refreshControlsView()
{
    if Bit6CallController.audioMuted() {
       NSLog("Audio Muted: true")
   }
   else {
       NSLog("Audio Muted: false")
   }

   if Bit6CallController.speakerEnabled() {
       NSLog("Speaker Enabled: true")
   }
   else {
       NSLog("Speaker Enabled: false")
   }
}

//called in the Main Thread when the status of the call changes.
override func override func callStateChangedNotificationForCallController(callController: Bit6CallController)
{
    switch (self.callController.callState) {
       case .NEW: NSLog("Call created")
       case .ACCEPTING_CALL: fallthrough
       case .GATHERING_CANDIDATES: fallthrough
       case .WAITING_SDP: fallthrough
       case .SENDING_SDP: fallthrough
       case .CONNECTING: NSLog("Call is connecting")
       case .CONNECTED: NSLog("Call connected")
       case .END: NSLog("Call ends")
       case .ERROR: NSLog("Call ends with an error")
       case .DISCONNECTED: NSLog("Call is disconnected for the moment. Will try to reconnect again.")
       case .MISSED: NSLog("Missed Call")
    }

    //refresh the timer label
    self.secondsChangedNotificationForCallController(callController)
}

//called in the Main Thread each second to allow the refresh of a timer UILabel.
override func secondsChangedNotificationForCallController(callController: Bit6CallController!) 
{
   switch (self.callController.callState) {
       case .NEW: fallthrough case .ACCEPTING_CALL: fallthrough
       case .GATHERING_CANDIDATES: fallthrough
       case .WAITING_SDP: fallthrough
       case .SENDING_SDP: fallthrough
       case .CONNECTING: NSLog("Call is starting")
       case .CONNECTED:
           self.timerLabel.text = Bit6Utils.clockFormatForSeconds(Double(self.callController.seconds))
       case .DISCONNECTED: fallthrough case .END: fallthrough 
       case .MISSED: fallthrough case .ERROR:
           self.timerLabel.text = "Disconnected"
   }
}

//called to customize the frames for the video feeds. You can call [self setNeedsUpdateVideoViewLayout] at any time to force a refresh of the frames.
override func updateLayoutForVideoFeedViews(videoFeedViews: [Bit6VideoFeedView])
{
    //Here you can do your own calculations to set the frames or just call super. You can easily keep the aspect ratio by using AVMakeRectWithAspectRatioInsideRect() (import AVFoundation/AVFoundation.h) to do an scaleToFit or Bit6MakeRectWithAspectRatioToFillRect to do an scaleToFill.
    super.updateLayoutForVideoFeedViews(videoFeedViews)
}

@end

Step 2. Implement the actions and do the connections in your nib/storyboard file.

//ObjectiveC
- (IBAction) switchCamera:(id)sender {
    [Bit6CallController switchCamera];
}

- (IBAction) muteCall:(id)sender {
    [Bit6CallController switchMuteAudio];
}

- (IBAction) hangup:(id)sender {
    [Bit6CallController hangupAll];
}

- (IBAction) speaker:(id)sender {
    [Bit6CallController switchSpeaker];
}
//Swift
@IBAction func switchCamera(sender : UIButton) {
   Bit6CallController.switchCamera()
}

@IBAction func muteCall(sender : UIButton) {
   Bit6CallController.switchMuteAudio()
}

@IBAction func hangup(sender : UIButton) {
   Bit6CallController.hangupAll()
}

@IBAction func speaker(sender : UIButton) {
   Bit6CallController.switchSpeaker()
}

Step 3. Do some additional configurations on your viewDidLoad method

//ObjectiveC
- (void)viewDidLoad {
    [super viewDidLoad];

    self.usernameLabel.text = self.callController.otherDisplayName;

    if (! self.callController.hasVideo) {
        //hide switch camera option from the UI
    }

    //On iPad and iPod the audio always goes through the speaker.
    NSString *deviceType = [UIDevice currentDevice].model;
    if(![deviceType isEqualToString:@"iPhone"]){
        //hide speaker option from the UI
    }
}
//Swift
override func viewDidLoad() {
   super.viewDidLoad()

   self.usernameLabel.text = self.callController!.otherDisplayName

   if !self.callController.hasVideo {
       //hide switch camera option from the UI
   }

   //On iPad and iPod the audio always goes through the speaker.
   var deviceType = UIDevice.currentDevice().model
   if deviceType != "iPhone" {
       //hide speaker option from the UI
   }
}

callingPeer to Peer Transfers

See the DataChannelDemo sample project included with the sdk.

The process works similar to a regular audio/video call.

Step1. Start the call.

//ObjectiveC
Bit6Address * address = ...
Bit6CallController *callController = [Bit6 createCallTo:address streams:Bit6CallStreams_Data];

if (callController){
    self.callController = callController;

    //we listen to call state changes
    [callController addObserver:self forKeyPath:@"callState" options:NSKeyValueObservingOptionOld context:NULL];

    //start the call
    [callController start];
}
else {
    //cannot make call to specified address
}
//Swift
let address : Bit6Address = ...
let callController = Bit6.createCallTo(address, streams:.Data)

if callController != nil {
    self.callController = callController

    //we listen to call state changes
    callController.addObserver(self, forKeyPath:"callState", options: .Old, context:nil)

    //start the call
    callController.start()
}
else {
    //cannot make call to specified address
}

Note. See it's not required to create a Bit6CallViewController to make calls.

Step2. Start a transfer.

//ObjectiveC
UIImage *image = ...
NSData *imageData = UIImagePNGRepresentation(image);
Bit6OutgoingTransfer *transfer = [[Bit6OutgoingTransfer alloc] initWithData:imageData name:@"my_image.png" mimeType:@"image/png"];
[self.callController startTransfer:transfer];

//Swift
let image = ...
let data = UIImagePNGRepresentation(image)
let transfer = Bit6OutgoingTransfer(data:imageData, name:"my_image.png", mimeType:"image/png")
self.callController.startTransfer(transfer)

Step3. Listen for incoming transfers and updates in outgoing transfers.

You need to handle the Bit6TransferUpdateNotification.

//ObjectiveC

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(transferUpdateNotification:)
                                             name:Bit6TransferUpdateNotification
                                           object:self.callController];

- (void) transferUpdateNotification:(NSNotification*)notification
{
    Bit6Transfer *transfer = notification.userInfo[Bit6ObjectKey];
    NSString *change = notification.userInfo[Bit6ChangeKey];
    Bit6CallController *callController = notification.object;
    Bit6TransferType transferType = transfer.type;

    if (callController == self.callController) {
        //the transfer started
        if ([change isEqualToString:Bit6TransferStartedKey]) { 

        }
        //transfer updated
        else if ([change isEqualToString:Bit6TransferProgressKey]) {
            CGFloat progressAtTheMoment = [notification.userInfo[Bit6ProgressKey] floatValue]; 
        }
        //the transfer ended
        else if ([change isEqualToString:Bit6TransferEndedKey]) {
            //transfer received
            if (transferType == Bit6TransferType_INCOMING) {
                if ([transfer.mimeType hasPrefix:@"image/"]) {
                    UIImage *image = [UIImage imageWithData:transfer.data];
                }
            }
            else {
                //transfer sent
            }
        }
        //the transfer failed
        else if ([change isEqualToString:Bit6TransferEndedWithErrorKey]) {
            NSLog(@"%@",transfer.error.localizedDescription);
        }
    }
}


//Swift

NSNotificationCenter.defaultCenter().addObserver(self
                                        selector:"transferUpdateNotification:",
                                             name:Bit6TransferUpdateNotification,
                                           object:self.callController)

func transferUpdateNotification(notification:NSNotification)
{
    let transfer = notification.userInfo![Bit6ObjectKey] as! Bit6Transfer
    let change = notification.userInfo![Bit6ChangeKey] as! String
    let callController = notification.object as! Bit6CallController
    let transferType = transfer.type

    if callController == self.callController {
        switch change {
            //the transfer started
            case Bit6TransferStartedKey : break

            //transfer updated
            case Bit6TransferProgressKey :
                let progressAtTheMoment = (notification.userInfo![Bit6ProgressKey] as! NSNumber).floatValue

            //the transfer ended
            case Bit6TransferEndedKey :
                //transfer received
                if transferType == .INCOMING {
                    if transfer.mimeType.hasPrefix("image/") {
                        let image = UIImage(data:transfer.data)!
                    }
                }
                else {
                    //transfer sent
                }

            //the transfer failed
            case Bit6TransferEndedWithErrorKey :
                NSLog("\(transfer.error.localizedDescription)")

            default: break
        }
    }
}

messagingText Messages

Send Text Message

//ObjectiveC
Bit6OutgoingMessage *message = [Bit6OutgoingMessage new];
message.content = @"This is a text message";
message.destination = [Bit6Address addressWithUsername:@"user2"];

[message sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
    if (!error) {
        //Message Sent
    }
    else {
        //Message Failed
    }
}];
//Swift
var message = Bit6OutgoingMessage()
message.content = "This is a text message"
message.destination = Bit6Address(username:"user2")

message.sendWithCompletionHandler { (response, error) in
    if error == nil {
        //Message Sent
    }
    else {
        //Message Failed
    }
}

Get Messages

//ObjectiveC
NSArray *messages = [Bit6 messagesWithOffset:0 length:NSIntegerMax asc:YES];
//Swift
var messages = Bit6.messagesWithOffset(0, length: NSIntegerMax, asc: true)

Listen to Changes in Messages

To know when a message has been added or a message status has been updated, register as an observer for updates on message level:

//ObjectiveC
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(messagesChangedNotification:) 
                                             name:Bit6MessagesChangedNotification 
                                           object:nil];
//Swift
var conversation : Bit6Conversation = ...
NSNotificationCenter.defaultCenter().addObserver(self,
                                        selector:"messagesChangedNotification:", 
                                            name:Bit6MessagesChangedNotification,
                                          object:nil)

Upon receiving a message change notification, update the messages array:

//ObjectiveC
- (void) messagesChangedNotification:(NSNotification*)notification
{
    Bit6Message *msg = notification.userInfo[Bit6ObjectKey];
    NSString *change = notification.userInfo[Bit6ChangeKey];

    if ([change isEqualToString:Bit6AddedKey]) {
        //add message to self.messages and refresh changes in UI
    }
    else if ([change isEqualToString:Bit6UpdatedKey]) {
        //find message in self.messages and refresh changes in UI
    }
    else if ([change isEqualToString:Bit6DeletedKey]) {
        //find message in self.messages, delete it and refresh changes in UI
    }
} 
//Swift
func messagesChangedNotification(notification:NSNotification)
{
   let msg = notification.userInfo[Bit6ObjectKey]
   let change = notification.userInfo[Bit6ChangeKey]

   if change == Bit6AddedKey {
       //add message to self.messages and refresh changes in UI
   }
   else if change == Bit6UpdatedKey {
       //find message in self.messages and refresh changes in UI
   }
   else if change == Bit6DeletedKey {
       //find message in self.messages, delete it and refresh changes in UI
   }
}

Delete a Message

//ObjectiveC
Bit6Message *messageToDelete = ...
[Bit6 deleteMessage:messageToDelete 
         completion:^(NSDictionary *response, NSError *error) {
            if (!error) {
                //message deleted
            }
}];
//Swift
var messageToDelete : Bit6Message = ...
Bit6.deleteMessage(messageToDelete) { (response, error) in
    if error == nil {
        //message deleted
    }
}

Enable Background Push Notifications for Messages

You can enable background remote notifications by checking the property in your target configuration.

Then in your UIApplicationDelegate implementation you need to add the following:

//ObjectiveC
- (void)application:(UIApplication *)application 
        didReceiveRemoteNotification:(NSDictionary *)userInfo 
              fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
    [[Bit6 pushNotification] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
//Swift
func application(application: UIApplication, 
        didReceiveRemoteNotification userInfo: [NSObject : AnyObject], 
     fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) 
{
    Bit6.pushNotification().didReceiveRemoteNotification(userInfo, fetchCompletionHandler:completionHandler)
}
Note. If you implement application:didReceiveRemoteNotification:fetchCompletionHandler: remember to remove this from your application:didFinishLaunchingWithOptions:
//ObjectiveC
//Remove
/*
    NSDictionary *remoteNotificationPayload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
    if (remoteNotificationPayload) {
        //[[Bit6 pushNotification] didReceiveRemoteNotification:remoteNotificationPayload];
    }
*/
//Swift
//Remove
/*
    if let remoteNotificationPayload = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
        Bit6.pushNotification().didReceiveRemoteNotification(remoteNotificationPayload as [NSObject : AnyObject])
    }
*/

messagingConversations

A conversation is interaction between two users, that includes all text messages and multimedia messages they exchange. There is one conversation per destination address (phone number, e-mail, Facebook account etc).

NOTE: arranging messages in conversations is optional, but can be very convenient depending on what you need.

Get Existing Conversations

//ObjectiveC
NSArray *conversations = [Bit6 conversations];
//Swift
var conversations = Bit6.conversations()

Add a Conversation

//ObjectiveC
Bit6Address *address = [Bit6Address addressWithUsername:@"user2"];
Bit6Conversation *conversation = [Bit6Conversation conversationWithAddress:address];
[Bit6 addConversation:conversation];
//Swift
var address = Bit6Address(username:"user2")
var conversation = Bit6Conversation(address: address)
Bit6.addConversation(conversation)

Delete a Conversation

//ObjectiveC
Bit6Conversation *conversationToDelete = ...
[Bit6 deleteConversation:conversationToDelete 
              completion:^(NSDictionary *response, NSError *error) {
            if (!error) {
                //conversation deleted
            }
}];
//Swift
var conversationToDelete : Bit6Conversation = ...
Bit6.deleteConversation(conversationToDelete) { (response, error) in
    if error == nil {
        //conversation deleted
    }
}

Listen to Changes in Conversations

You will need to know when a conversation has been created, deleted or changed (for example, a new message was received within a certain conversation, the conversation title has changed, etc).

Register as an observer for updates on conversation level:

//ObjectiveC
[[NSNotificationCenter defaultCenter] addObserver:self 
                       selector:@selector(conversationsChangedNotification:)
                           name:Bit6ConversationsChangedNotification
                         object:nil];
//Swift
NSNotificationCenter.defaultCenter().addObserver(self,
                                        selector:"conversationsChangedNotification:", 
                                            name:Bit6ConversationsChangedNotification, 
                                          object:nil)

Upon receiving a conversation change notification, update the conversations array:

//ObjectiveC
- (void) conversationsChangedNotification:(NSNotification*)notification
{
    Bit6Conversation *conversation = notification.userInfo[Bit6ObjectKey];
    NSString *change = notification.userInfo[Bit6ChangeKey];

    if ([change isEqualToString:Bit6AddedKey]) {
        //add conversation to self.conversations and refresh changes in UI
    }
    if ([change isEqualToString:Bit6UpdatedKey]) {
        //find conversation in self.conversations and refresh changes in UI
    }
    if ([change isEqualToString:Bit6DeletedKey]) {
        //find conversation in self.conversations, remove it and refresh changes in UI
    }
} 
//Swift
func conversationsChangedNotification(notification:NSNotification){
   let conversation = notification.userInfo[Bit6ObjectKey]
   let change = notification.userInfo[Bit6ChangeKey]

   if change == Bit6AddedKey {
       //add conversation to self.conversations and refresh changes in UI
   }
   else if change == Bit6UpdatedKey {
       //find conversation in self.conversations and refresh changes in UI
   }
   else if change == Bit6DeletedKey {
       //find conversation in self.conversations, remove it and refresh changes in UI
   }
}

Unread Messages Badge

Get the number of unread messages for a particular conversation:

//ObjectiveC
Bit6Conversation *conversation = ...
NSNumber *badge = conversation.badge;
//Swift
var conversation : Bit6Conversation = ...
var badge = conversation.badge

When showing a messages UIViewController the following code must be called to mark the messages as read and stop increasing the badge value while in the UIViewController:

//ObjectiveC
Bit6Conversation *conversation = ...
[Bit6 setCurrentConversation:conversation];
//Swift
var conversation : Bit6Conversation = ...
Bit6.setCurrentConversation(conversation)

When you are leaving the messages UIViewController the following code must be called:

//ObjectiveC
- (void)dealloc {
    [Bit6 setCurrentConversation:nil];
}
//Swift
deinit {
    Bit6.setCurrentConversation(nil)
}

To get the unread messages total:

//ObjectiveC
[Bit6 totalBadge]
//Swift
Bit6.totalBadge()

Get Messages in a Conversation

Although messages do not have to be arranged in conversations, it is frequently convenient to have the messages sorted by destination. More docs on handling conversation here.

//ObjectiveC
Bit6Conversation *conversation = ...
self.messages = conversation.messages;
//Swift
var conversation : Bit6Conversation = ...
self.messages = conversation.messages

Listen to Changes in Messages inside Conversations

To know when a message has been added to a particular conversation, or a message status has been updated, register as an observer for updates on message level:

//ObjectiveC
Bit6Conversation *conversation = ...
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(messagesChangedNotification:) 
                                             name:Bit6MessagesChangedNotification
                                           object:conversation];
//Swift
var conversation : Bit6Conversation = ...
NSNotificationCenter.defaultCenter().addObserver(self,
                                        selector:"messagesChangedNotification:", 
                                            name:Bit6MessagesChangedNotification,
                                          object:conversation)

Upon receiving a message change notification, update the conversations array:

//ObjectiveC
- (void) messagesChangedNotification:(NSNotification*)notification
{
    Bit6Message *message = notification.userInfo[Bit6ObjectKey];
    NSString *change = notification.userInfo[Bit6ChangeKey];

    if ([change isEqualToString:Bit6AddedKey]) {
        //add message to self.messages and refresh changes in UI
    }
    else if ([change isEqualToString:Bit6UpdatedKey]) {
        //find message in self.messages and refresh changes in UI
    }
    else if ([change isEqualToString:Bit6DeletedKey]) {
        //find message in self.messages, remove it and refresh changes in UI
    }
} 
//Swift
func messagesChangedNotification(notification:NSNotification) 
{
   let message = notification.userInfo[Bit6ObjectKey]
   let change = notification.userInfo[Bit6ChangeKey]

   if change == Bit6AddedKey {
       //add message to self.messages and refresh changes in UI
   }
   else if change == Bit6UpdatedKey {
       //find message in self.messages and refresh changes in UI
   }
   else if change == Bit6DeletedKey {
       //find message in self.messages, remove it and refresh changes in UI
   }
}

messagingGroups

Get Existing Group

//ObjectiveC
Bit6Conversation *conv = ...
Bit6Group *group = [Bit6Group groupWithConversation:conv];
//Swift
var conv = ...
var group = Bit6Group(conversation:conv)

Create a Group

//ObjectiveC
[Bit6Group createGroupWithMetadata:@{@"key1":@"value1", ...} 
                     completion:^(Bit6Group *group, NSError *error) {
                        if (!error) {
                            //group created
                        }
}];
//Swift
Bit6Group.createGroupWithMetadata(["key1":"value1", ...]){ (group, error) in
    if error == nil {
        //group created
    }
}

A Bit6ConversationsChangedNotification and a Bit6GroupsChangedNotification with change key Bit6AddedKey will be sent. See "Listen to Changes" in Conversations.

Note. If you want to set the title for a group the use of Bit6GroupMetadataTitleKey is recomended, as in:

//ObjectiveC
[Bit6Group createGroupWithMetadata:@{Bit6GroupMetadataTitleKey:@"some title", ...} 
                     completion:^(Bit6Group *group, NSError *error) {
                        if (!error) {
                            //group created
                        }
}];

Leave a Group

//ObjectiveC
[group leaveGroupWithCompletion:^(NSError *error) {
    if (!error) {
        //you have left the group
    }
}];
//Swift
group.leaveGroupWithCompletion(){ (error) in
    if error == nil {
        //you have left the group
    }
}

A Bit6GroupsChangedNotification with change key Bit6ObjectDeleted will be sent. See "Listen to Changes" in Conversations.

Change Group Metadata

Note. only available if group.isAdmin returns YES;
//ObjectiveC
[group setMetadata:@{@"key1":@"value1", ...}
            completion:^(NSError *error) {
                if (!error) {
                    //metadata has changed
                }
}];
//Swift
group.setMetadata(["key1":"value1", ...]){ (error) in
    if error == nil {
      //metadata has changed
    }
}

A Bit6GroupsChangedNotification with change key Bit6UpdatedKey will be sent. See "Listen to Changes" in Conversations.

Note. If you want to set the title for a group the use of Bit6GroupMetadataTitleKey is recomended

Invite members to a Group

Note. only available if group.isAdmin returns YES;
//ObjectiveC
Bit6Address *friendToInvite = ...
NSString *role = Bit6GroupMemberRole_User;

[group inviteGroupMemberWithAddress:friendToInvite role:role
            completion:^(NSArray *members, NSError *error) {
                if (!error) {
                    //friend have been invited
                    //members is the updated list of group members
                }
}];
//Swift
let friendToInvite : Bit6Address = ...
let role = Bit6GroupMemberRole_User

group.inviteGroupMemberWithAddress(friendToInvite, role:role){ (members, error) in
    if error == nil {
        //friend have been invited
        //members is the updated list of group members
    }
}

A Bit6GroupsChangedNotification with change key Bit6UpdatedKey will be sent. See "Listen to Changes" in Conversations.

Remove members from a Group

Note. only available if group.isAdmin returns YES;
//ObjectiveC
Bit6GroupMember *friendToRemove = group.members[0];

[group kickGroupMember:friendToRemove 
            completion:^(NSArray *members, NSError *error) {
                if (!error) {
                    //friend has been removed 
                    //members is the updated list of group members
                }
}];
//Swift
let friendToRemove = group.members[0]

group.kickGroupMember(friendToRemove){ (members, error) in
    if error == nil {
        //friend has been removed
        //members is the updated list of group members
    }
}

A Bit6GroupsChangedNotification with change key Bit6UpdatedKey will be sent. See "Listen to Changes" in Conversations.

messagingNotifications

Notify the Recipient that the Sender is Typing

Call +[Bit6 typingBeginToAddress:].

If using UITextField to type, utilize its UITextFieldDelegate to send the notification:

//ObjectiveC
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range 
                                                       replacementString:(NSString *)string
{
    Bit6Address *otherUserAddress = ...
    [Bit6 typingBeginToAddress:otherUserAddress];
    return YES;
}
//Swift
func textField(textField: UITextField, 
shouldChangeCharactersInRange range: NSRange, 
           replacementString string: String) -> Bool
{
    var otherUserAddress : Bit6Address = ...
    Bit6.typingBeginToAddress(otherUserAddress)
    return true
}

"Typing" Notifications

You can check who is typing for a specific conversation:

//ObjectiveC
Bit6Conversation *conv = ...
Bit6Address *whoIsTyping = conv.typingAddress;
//Swift
let conv = ...
let whoIsTyping = conv.typingAddress

To detect when someone is typing register as an observer:

//ObjectiveC
[[NSNotificationCenter defaultCenter] addObserver:self 
                       selector:@selector(typingBeginNotification:)
                           name:Bit6TypingDidBeginRtNotification
                         object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self 
                       selector:@selector(typingEndNotification:)
                           name:Bit6TypingDidEndRtNotification
                         object:nil];

- (void)typingBeginNotification:(NSNotification*)notification
{
    Bit6Address* fromAddress = notification.userInfo[@"from"];
    Bit6Address* convervationAddress = notification.object;
}
- (void)typingEndNotification:(NSNotification*)notification
{
    Bit6Address* fromAddress = notification.userInfo[@"from"];
    Bit6Address* convervationAddress = notification.object;
}    
//Swift
NSNotificationCenter.defaultCenter().addObserver(self,
                            selector:"typingBeginNotification:", 
                                name:Bit6TypingDidBeginRtNotification, 
                              object:nil)
NSNotificationCenter.defaultCenter().addObserver(self,
                            selector:"typingEndNotification:", 
                                name:Bit6TypingDidEndRtNotification, 
                              object:nil)

func typingBeginNotification(notification:NSNotification){
    let fromAddress = notification.userInfo!["from"] as! Bit6Address
    let convervationAddress = notification.object as! Bit6Address
}
func typingEndNotification(notification:NSNotification){
    let fromAddress = notification.userInfo!["from"] as! Bit6Address
    let convervationAddress = notification.object as! Bit6Address
}

Receiving custom notifications

//ObjectiveC
[[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(receivedRTNotification:) 
          name:Bit6CustomRtNotification
        object:nil];

- (void) receivedRTNotification:(NSNotification*)notification
{
    Bit6Address *from = notification.userInfo[@"from"];
    Bit6Address *to = notification.userInfo[@"to"];
    NSString *type = notification.userInfo[@"type"];
}
//Swift
NSNotificationCenter.defaultCenter().addObserver(self, 
      selector: "receivedRTNotification:", 
          name: Bit6CustomRtNotification, 
        object: nil)

func receivedRTNotification(notification:NSNotification)
{
  let from : Bit6Address = notification.userInfo["from"]
  let to : Bit6Address = notification.userInfo["to"]
  let type : String = notification.userInfo["type"]
}

Sending custom notifications

//ObjectiveC
Bit6Address *address = ... ;
[Bit6 sendNotificationToAddress:address type:@"custom_type" data:nil];
//Swift
var address : Bit6Address = ...
Bit6.sendNotificationToAddress(address, type:"custom_type", data:nil)

multimedia messagingPhoto

Let your users exchange photos and pictures.

//ObjectiveC
UIImage *image = ...
Bit6OutgoingMessage *message = [Bit6OutgoingMessage new];
message.image = image;
message.destination = [Bit6Address addressWithUsername:@"user2"];
[message sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
    if (!error) {
        //Message Sent
    }
    else {
        //Message Failed
    }
}];
//Swift
var image : UIImage = ...
var message = Bit6OutgoingMessage()
message.image = image
message.destination = Bit6Address(username:"user2")
message.sendWithCompletionHandler{ (response, error) in
    if error == nil {
        //Message Sent
    }
    else {
        //Message Failed
    }
}

multimedia messagingVideo

If you are using UIImagePickerController to take/select a video you can do the following in the UIImagePickerControllerDelegate method:

//ObjectiveC
- (void)imagePickerController:(UIImagePickerController *)picker 
             didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    // start of your imagePickerController:didFinishPickingMediaWithInfo: method

    Bit6OutgoingMessage *message = [Bit6OutgoingMessage new];
    message.destination = [Bit6Address addressWithUsername:@"user2"];

    NSString *mediaType = info[UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:(NSString *) kUTTypeMovie]) {
        //here we add the video to the message
        message.videoURL = info[UIImagePickerControllerMediaURL];

        //if we used the camera to take the video we have to set the cropping times 
        message.videoCropStart = 
               [info objectForKey:@"_UIImagePickerControllerVideoEditingStart"];
        message.videoCropEnd = 
               [info objectForKey:@"_UIImagePickerControllerVideoEditingEnd"];
    }

    [message sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
        if (!error) {
            //Message Sent
        }
        else {
            //Message Failed
        }
    }];

    // The rest of your imagePickerController:didFinishPickingMediaWithInfo: method
}
//Swift
func imagePickerController(picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [NSObject : AnyObject])
{
    // start of your imagePickerController:didFinishPickingMediaWithInfo: method

    var message = Bit6OutgoingMessage()
    message.destination = Bit6Address(username:"user2")

    let mediaType = info[UIImagePickerControllerMediaType] as! String
    if mediaType == (kUTTypeMovie as String) {
        //here we add the video to the message
        message.videoURL = info[UIImagePickerControllerMediaURL] as! NSURL

        //if we used the camera to take the video we have to set the cropping times 
        message.videoCropStart = 
               info["_UIImagePickerControllerVideoEditingStart"] as! NSNumber
        message.videoCropEnd = 
               info["_UIImagePickerControllerVideoEditingEnd"] as! NSNumber
    }

    message.sendWithCompletionHandler { (response, error) in
        if error == nil {
            //Message Sent
        }
        else {
            //Message Failed
        }
    }

    // The rest of your imagePickerController:didFinishPickingMediaWithInfo: method
}

multimedia messagingLocation

In iOS 8 you must add the key NSLocationWhenInUseUsageDescription to your info.plist file. This string is used to specify the reason for accessing the user’s location information.

Step 1. Prepare the message:

//ObjectiveC
Bit6OutgoingMessage *message = [Bit6OutgoingMessage new];
message.destination = [Bit6Address addressWithUsername:@"user2"];
//Swift
var message = Bit6OutgoingMessage()
message.destination = Bit6Address(username:"user2")

Step 2. Start the location service:

//ObjectiveC
[[Bit6 locationController] startListeningToLocationForMessage:message delegate:self];
//Swift
Bit6.locationController().startListeningToLocationForMessage(message, delegate: self)

Step 3. Implement the Bit6CurrentLocationControllerDelegate and send the message when the location has been obtained

//ObjectiveC
@interface ChatsViewController <Bit6CurrentLocationControllerDelegate>

- (void) currentLocationController:(Bit6CurrentLocationController*)b6clc 
          didGetLocationForMessage:(Bit6OutgoingMessage*)message
{
    [message sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
        if (!error) {
            //Message Sent
        }
        else {
            //Message Failed
        }
    }];
}

- (void) currentLocationController:(Bit6CurrentLocationController*)b6clc 
                  didFailWithError:(NSError*)error 
                           message:(Bit6OutgoingMessage*)message
{
    //an error occurred
}
//Swift
class ChatsViewController : Bit6CurrentLocationControllerDelegate

func currentLocationController(b6clc: Bit6CurrentLocationController!, 
    didGetLocationForMessage message: Bit6OutgoingMessage!)
{
    message.sendWithCompletionHandler{ (response, error) in
        if error == nil {
            //Message Sent
        }
        else {
            //Message Failed
        }
    }
}

func currentLocationController(b6clc: Bit6CurrentLocationController!, 
              didFailWithError error: NSError!, 
                             message: Bit6OutgoingMessage!)
{
    //an error occurred
}

Send a Custom Location

Note. Remember to import the CoreLocation framework.

//ObjectiveC
Bit6OutgoingMessage *message = [Bit6OutgoingMessage new];
message.location = CLLocationCoordinate2DMake(latitude, longitude);
message.destination = [Bit6Address addressWithUsername:@"user2"];
[message sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
    if (!error) {
        //Message Sent
    }
    else {
        //Message Failed
    }
}];
//Swift
var message = Bit6OutgoingMessage()
message.location = CLLocationCoordinate2DMake(latitude, longitude)
message.destination = Bit6Address(username:"user2")
message.sendWithCompletionHandler{ (response, error) in
    if error == nil {
        //Message Sent
    }
    else {
        //Message Failed
    }
}

Open a Location

//ObjectiveC
Bit6Message *message = ...

//Open in AppleMaps
[Bit6 openLocationOnMapsFromMessage:msg];

/*
    //Open in GoogleMaps app, if available
    CLLocationCoordinate2D location = msg.location;
    NSString *urlString = [NSString stringWithFormat:@"comgooglemaps://?center=%f,%f&zoom=14",
                                 location.latitude, location.longitude];
    NSURL *url = [NSURL URLWithString:urlString];
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url];
    }
*/

/*
    //Open in Waze app, if available
    CLLocationCoordinate2D location = msg.location;
    NSString *urlString = [NSString stringWithFormat:@"waze://?ll=%f,%f&navigate=yes", 
                                   location.latitude, location.longitude];
    NSURL *url = [NSURL URLWithString:urlString];
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url];
    }
*/
//Swift
var message : Bit6Message = ...

//Open in AppleMaps
Bit6.openLocationOnMapsFromMessage(message)

/*
    //Open in GoogleMaps app, if available
    let location = msg.location
    let urlString = "comgooglemaps://?center=\(location.latitude),\(location.longitude)&zoom=14"
    let url = NSURL(string: urlString)
    if UIApplication.sharedApplication().canOpenURL(url!) {
        UIApplication.sharedApplication().openURL(url!)
    }
*/

/*
    //Open in Waze app, if available
    let location = msg.location
    let urlString = "waze://?ll=\(location.latitude),\(location.longitude)&navigate=yes"
    let url = NSURL(string: urlString)
    if UIApplication.sharedApplication().canOpenURL(url!) {
            UIApplication.sharedApplication().openURL(url!)
    }
*/

multimedia messagingAudio

Record and Send an Audio File

Step 1. Start the audio recording:

//ObjectiveC
[[Bit6 audioRecorder] startRecordingAudioWithMaxDuration: 60 
                                                delegate: self 
                                           defaultPrompt: YES
                                            errorHandler:^(NSError *error) {
    //an error occurred
}];
//Swift
Bit6.audioRecorder().startRecordingAudioWithMaxDuration(60, 
                                               delegate: self, 
                                          defaultPrompt: true, 
                                           errorHandler: { (error) in
    //an error occurred
});

Note If defaultPrompt param is YES then a default UIAlertView will be shown to handle the recording. If NO then you need to provide a custom UI to handle the recording.

To get the length of the current recording: [Bit6 audioRecorder].duration. Then you can use [Bit6Utils clockFormatForSeconds:duration] to get a string like 01:05.

To cancel the recording: [[Bit6 audioRecorder] cancelRecording]

To finish the recording: [[Bit6 audioRecorder] stopRecording]

To know if there's a recording in process: [Bit6 audioRecorder].isRecording

Step 2. Implement the Bit6AudioRecorderControllerDelegate. Its methods are called in the main thread.

//ObjectiveC
@interface ChatsViewController <Bit6AudioRecorderControllerDelegate>

- (void) doneRecorderController:(Bit6AudioRecorderController*)b6rc 
                       filePath:(NSString*)filePath
{
    if ([Bit6Utils audioDurationForFileAtPath:filePath] > 1.0) {
        Bit6OutgoingMessage *message = [Bit6OutgoingMessage new];
        message.destination = [Bit6Address addressWithUsername:@"user2"];
        message.audioFilePath = filePath;
        [message sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
            if (!error) {
                //Message Sent
            }
            else {
                //Message Failed
            }
        }];
    }
}

- (void)isRecordingWithController:(Bit6AudioRecorderController*)b6rc 
                         filePath:(NSString*)filePath
{
    //called each 0.5 seconds while recording
    double durationValue = [Bit6 audioRecorder].duration;
    NSString *duration = [Bit6Utils clockFormatForSeconds:durationValue];
}

- (void)cancelRecorderController:(Bit6AudioRecorderController*)b6rc
{
    //the recording was cancelled
}
//Swift
class ChatsViewController : Bit6AudioRecorderControllerDelegate

func doneRecorderController(b6rc: Bit6AudioRecorderController, 
                         filePath: NSString) {
    if Bit6Utils.audioDurationForFileAtPath(filePath) > 1.0 {
        var message = Bit6OutgoingMessage()
        message.destination = Bit6Address(username:"user2")
        message.audioFilePath = filePath
        message.sendWithCompletionHandler{ (response, error) in
            if error == nil {
                //Message Sent
            }
            else {
                //Message Failed
            }
        }
    }
}

func isRecordingWithController(b6rc: Bit6AudioRecorderController, 
                           filePath: NSString) {
    //called each 0.5 seconds while recording
    if let durationValue = Bit6.audioRecorder().duration {
        let duration = Bit6Utils.clockFormatForSeconds(durationValue)
    }
}

func cancelRecorderController(b6rc: Bit6AudioRecorderController) {
    //the recording was cancelled
}

Play an Audio File

//ObjectiveC
NSString *filePath = message.pathForFullAttachment;
[[Bit6 audioPlayer] startPlayingAudioFileAtPath:filePath errorHandler:^(NSError *error) 
{
    //an error occurred
}];
//Swift
var message : Bit6OutgoingMessage() = ...
let filePath = message.pathForFullAttachment
Bit6.audioPlayer().startPlayingAudioFileAtPath(filePath,
                            errorHandler: { (error) in
    //an error occurred
})

To get the length of an audio file in a Bit6Message: message.audioDuration

To get the current audio file playing length: [Bit6 audioPlayer].duration

To get the audio file playing current time: [Bit6 audioPlayer].currentTime

To get the path to the last audio file played: [Bit6 audioPlayer].filePathPlaying

To know if there's an audio file playing at the moment: [Bit6 audioPlayer].isPlayingAudioFile

Note. You can listen to the Bit6AudioPlayingNotification to update your UI while playing an audio file.

multimedia messagingAttachment Actions

Let the user view the attachment preview, play audio files, view location on the map, view the picture fullscreen etc.

Get the Path for a Message Attachment

Check the attachment path for a given message.

//ObjectiveC
Bit6Message *msg = ...;
NSString *thumbnailAttachmentPath = msg.pathForThumbnailAttachment;
NSString *fullAttachmentPath = msg.pathForFullAttachment;
//Swift
var msg : Bit6Message = ...
var thumbnailAttachmentPath = msg.pathForThumbnailAttachment
var fullAttachmentPath = msg.pathForFullAttachment

Get the Status for a Message Attachment

Check the attachment state for a given message.

Properties:
message.statusForThumbnailAttachment - for thumbnails message.statusForFullAttachment - for full size

//ObjectiveC
Bit6Message *msg = ...;
    switch (msg.statusForFullAttachment) {
        case Bit6MessageAttachmentStatus_INVALID:
            //the message can't have an attachment, for example a text message
            break;
        case Bit6MessageAttachmentStatus_FAILED:
            //the attachment can't be downloaded, for example because of an 
            //error when uploading the attachment
            break;
        case Bit6MessageAttachmentStatus_NOT_FOUND:
            //the file is not in cache
            break;
        case Bit6MessageAttachmentStatus_DOWNLOADING:
            //the file is not in cache, but is being downloaded
            break;
        case Bit6MessageAttachmentStatus_FOUND:
            //the file is in cache, ready to be used
            break;
    }
//Swift
var msg : Bit6Message = ...
    switch msg.statusForFullAttachment {
        case .INVALID:
            //the message can't have an attachment, for example a text message
            break;
        case .FAILED:
            //the attachment can't be downloaded, for example because of an
            //error when uploading the attachment
            break;
        case .NOT_FOUND:
            //the file is not in cache
            break;
        case .DOWNLOADING:
            //the file is not in cache, but is being downloaded
            break;
        case .FOUND:
            //the file is in cache, ready to be used
            break;
    }

Preview the attachment

Step 1. Check if the message has a preview to show:

//ObjectiveC
Bit6Message *message = ...
if (message.type != Bit6MessageType_Text) {
    //can show preview
}
//Swift
var msg : Bit6Message = ...
if message.type != .Text {
    //can show preview
}

Step 2. To show the preview, use a Bit6ThumbnailImageView object. To add Bit6ThumbnailImageView to your nib/storyboard just add an UIImageView object and change its class to Bit6ThumbnailImageView:

Step 3. Set the message to be used with the Bit6ThumbnailImageView object:

//ObjectiveC
Bit6Message *message = ...
Bit6ThumbnailImageView *imageView = ...
imageView.message = message;
//Swift
var msg : Bit6Message = ...
var imageView : Bit6ThumbnailImageView = ...
imageView.message = message

Interact with the attachment

Step 1. Implement the Bit6ThumbnailImageViewDelegate

//ObjectiveC
@interface ChatsViewController <Bit6ThumbnailImageViewDelegate>
//Swift
class ChatsViewController : Bit6ThumbnailImageViewDelegate

Step 2. Depending on the attachment type, show the location on the map, play the audio or video, open the picture full screen:

//ObjectiveC
- (void) touchedThumbnailImageView:(Bit6ThumbnailImageView*)thumbnailImageView
{
    Bit6Message *msg = thumbnailImageView.message;

    Bit6MessageAttachmentStatus fullAttachStatus = msg.statusForFullAttachment;

    if (msg.type == Bit6MessageType_Location) {
        //Open in AppleMaps
        [Bit6 openLocationOnMapsFromMessage:msg];
    }
    else if (msg.type == Bit6MessageType_Attachments) {
        //play an audio file
        if (msg.attachFileType == Bit6MessageFileType_Audio) {
            [[Bit6 audioPlayer] startPlayingAudioFileInMessage:msg errorHandler:^(NSError *error) {
                //an error occurred
            }];
        }
        else if (msg.attachFileType == Bit6MessageFileType_Video) {
            if ([Bit6 shouldDownloadVideoBeforePlaying]) {
                if (fullAttachStatus==Bit6MessageAttachmentStatus_FOUND) {
                    [Bit6 playVideoFromMessage:msg viewController:self.navigationController];
                }
            }
            else {
                [Bit6 playVideoFromMessage:msg viewController:self.navigationController];
            }
        }
        else if (msg.attachFileType == Bit6MessageFileType_Image) {
        //code to navigate to another screen to show the image in full size
        }
    }
}
//Swift
func touchedThumbnailImageView(thumbnailImageView:Bit6ThumbnailImageView) {
        let msg = thumbnailImageView.message

        let fullAttachStatus = msg.statusForFullAttachment

        if msg.type == .Location{
            //Open in AppleMaps
            Bit6.openLocationOnMapsFromMessage(msg)
        }

        else if msg.type == .Attachments {
            //play an audio file
            if msg.attachFileType == .Audio {
                Bit6.audioPlayer().startPlayingAudioFileInMessage(msg,errorHandler: { (error) in
                    //an error occurred
                })
            }
            else if msg.attachFileType == .Video {
                if Bit6.shouldDownloadVideoBeforePlaying() {
                    if fullAttachStatus == .FOUND {
                        Bit6.playVideoFromMessage(msg, viewController:self.navigationController)
                    }
                }
                else {
                    Bit6.playVideoFromMessage(msg, viewController:self.navigationController)
                }
            }
            else if msg.attachFileType == .Image {
                //code to navigate to another screen to show the image in full size
            }
        }
    }

Step 3. Set the Bit6ThumbnailImageView delegate

//ObjectiveC
Bit6ThumbnailImageView *imageView = ...
imageView.thumbnailImageViewDelegate = self;
//Swift
var imageView : Bit6ThumbnailImageView  = ...
imageView.thumbnailImageViewDelegate = self

Load an image attachment

Step 1. Check if a message has an image to show:

//ObjectiveC
Bit6Message *msg = ...
if (msg.type == Bit6MessageType_Attachments && msg.attachFileType == Bit6MessageFileType_Image) {
    //can show full image
}
//Swift
var msg : Bit6Message = ...
if msg.type == .Attachments && msg.attachFileType == .Image {
    //can show full image
}

Step 2. Use a Bit6ImageView object to show the image.

Add Bit6ImageView to nib/storyboard: add an UIImageView object and change its class to Bit6ImageView.

Step 3. Set the message to be used with the Bit6ImageView object:

//ObjectiveC
Bit6Message *message = ...
Bit6ImageView *imageView = ...
imageView.message = message;
//Swift
var msg : Bit6Message = ...
var imageView : Bit6ImageView = ...
imageView.message = message

Download Video Attachments

By default when playing a video attachment ([Bit6 playVideoFromMessage:msg viewController:viewController]) the file will be streamed if not found in the device. If you want to change this behaviour by forcing the video file to be downloaded you can add the following to your target info.plist

multimedia messagingMenu Controller

Implement a menu popup to enable message forwarding, resending and text copying.

This menu controller is available in Bit6ThumbnailImageView for multimedia messages and in Bit6MessageLabel for text messages.

Step 1. Implement Bit6MenuControllerDelegate

//ObjectiveC
@interface ChatsTableViewController <Bit6MenuControllerDelegate>

@end

Bit6MessageLabel *textLabel = ...
textLabel.message = message;
textLabel.menuControllerDelegate = self;

Bit6ThumbnailImageView *imageView = ...
imageView.message = message;
imageView.menuControllerDelegate = self;
//Swift
class ChatsTableViewController :Bit6MenuControllerDelegate {

}

var textLabel : Bit6MessageLabel = ...
textLabel.message = message
textLabel.menuControllerDelegate = self

var imageView : Bit6ThumbnailImageView = ...
imageView.message = message;
imageView.menuControllerDelegate = self;

Step 2. Enable resending of failed messages

To show this action, implement resendFailedMessage:

//ObjectiveC
- (void) resendFailedMessage:(Bit6OutgoingMessage*)msg
{
    //try to send the message again
    [msg sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
        ...
    }];
}
//Swift
func resendFailedMessage(msg:Bit6OutgoingMessage){
    //try to send the message again
    msg.sendWithCompletionHandler{ (response, error) in
        ...
    }
}

Step 3. Enable message forwarding

To show this action, implement forwardMessage:

//ObjectiveC
- (void) forwardMessage:(Bit6Message*)msg
{
    //we create a copy of the message
    Bit6OutgoingMessage *messageToForward = [Bit6OutgoingMessage outgoingCopyOfMessage:msg];

    //set the destination and the channel
    Bit6Address *address = ...
    messageToForward.destination = address;

    //send a copy of the message to the new destination
    [messageToForward sendWithCompletionHandler:^(NSDictionary *response, NSError *error) {
        ...
    }];
}
//Swift
func forwardMessage(msg:Bit6Message)
{
    //we create a copy of the message
    var messageToForward = Bit6OutgoingMessage.outgoingCopyOfMessage(msg)

    //set the destination and the channel
    var address : Bit6Address = ...
    messageToForward.destination = address

    //send a copy of the message to the new destination
    messageToForward.sendWithCompletionHandler{ (response, error) in
        ...
    }
}

multimedia messagingDownload Attachments

Download Attachments

Bit6ThumbnailImageView and Bit6ImageView classes can downloads the attachments automatically.

You can also download the files manually using the Bit6FileDownloader class. For example to download the thumbnail for a message:

//ObjectiveC
NSURL *url = message.remoteURLForThumbnailAttachment;
NSString *filePath = message.pathForThumbnailAttachment;
NSData* (^preparationBlock)(NSData *data) = ^(NSData *imageData) {
    UIImage *image = [UIImage imageWithData:imageData];
    UIImage *finalImage = ...
    return UIImagePNGRepresentation(finalImage);
};

[Bit6FileDownloader downloadFileAtURL:url 
                           toFilePath:filePath 
                     preparationBlock:preparationBlock 
                             priority:NSOperationQueuePriorityNormal 
                    completionHandler:^(NSURL* location, NSURLResponse* response, NSError* error) {
                        //operation completed
                    }
];

//Swift
let url = message.remoteURLForThumbnailAttachment!
let filePath = message.pathForThumbnailAttachment!
let preparationBlock = ({ (data:NSData) -> NSData in
    let image = UIImage(data:data)
    let finalImage = ...
    return UIImagePNGRepresentation(finalImage)!
})

Bit6FileDownloader.downloadFileAtURL(url, 
                          toFilePath:filePath, 
                    preparationBlock:preparationBlock, 
                            priority:.Normal) { (location, response, error) in
                                //operation completed
                            }

Note. The preparationBlock can be used to make changes to the downloaded data before saving it locally. For example you could download the thumbnail and apply a filter using this block.