This document includes the documentation for the Kandy iOS SDK
The code samples are shown in a developer-friendly window showing parsed code. The window also allows for easy copy-paste of the code by developers

See screenshot of one of the current iOS screens.

Sample Code

Quick Start with Kandy iOS SDK

This guide covers what you need to go through in order to be able to start using Kandy SDK for iOS, in five steps:

  1. Prerequisites
  2. Installing the Kandy SDK
  3. API Keys and Secret
  4. General Configuration
  5. Start coding!

Step 1 - Prerequisites

As you read through the documentation, let’s ensure you understand the definition of a few terms used which also helps you understand how Kandy is organized.

First off, Kandy is a hierarchical system and the top most level is ADMINISTRATOR. Think of the Administrator as a super user who can effect any changes. Below the Administrator, is the ACCOUNT. Accounts may be set up for specific applications or for individual customers as well (if you’re a developer working with multiple customers). You can name accounts according to your needs (for example customer_1, customer_2, etc).

From the Account Level moving down is DOMAINS. A domain could be used for an account which has multiple applications or perhaps to create a subsection of users. For example, you might create domains for a customer based upon regions (for example US, EMEA, APAC).

  • OS X is required for all iOS development
  • You need Xcode. If you don’t have it, you can get it from Apple.

The Kandy SDK for iOS versions supports iOS7 and higher.

Step 2 - Installing the Kandy SDK

Manual Install

Download the SDK zip file from:

Download SDK

Import the zip file contents into you existing iOS project, extracting it into your “frameworks” folder. If your project does not already have a frameworks folder, create one at the root of the project.

The zip contains:

  • Frameworks/KandySDK.framework
  • com.Genband.Kandy.docset
  • SampleApp.zip

For Kandy SDK documentation:

In order to include the SDK manual, copy KandySDK.docset to the following path: ~/Library/Developer/Shared/Documentation/DocSets

To view the manual click the “Window->Documentation And API Reference” button on xCode

In order to use the SDK, add the following frameworks to your project:

  • KandySDK.framework

You can do so by either dragging them to the Project Navigator, or by choosing “Add files” from the Project Navigator Context Menu.

Add required frameworks and libraries

In order for Kandy SDK to run, the following frameworks and libraries are required:

  • VideoToolbox.framework
  • CoreTelephony.framework
  • GLKit.framework
  • libstdc++.6.dylib
  • libicucore.dylib
  • CoreMedia.framework
  • AudioToolbox.framework
  • libsqlite3.dylib
  • SystemConfiguration.framework
  • AVFoundation.framework
  • AddressBook.framework
  • ImageIO.framework
  • MobileCoreServices.framework
  • Libc++.dylib
  • CoreLocation.framework
  • MediaPlayer.framework

Install the SDK using CocoaPods

Kandy Cocoapods

You can install the CocoaPods tool on OS X by running the following command from the terminal. Detailed information is available in the Getting Started guide.

$ sudo gem install cocoapods

Create a Podfile for the Kandy SDK for iOS and use it to install the API and its dependencies:

  • Create a file named Podfile in your project directory. This file defines your project’s dependencies, and is commonly referred to as a Podspec.
$ touch podfile
$ open -e podfile
  • Edit the Podfile and add your dependencies. Here is a simple Podspec, including the name of the pod you need for the Kandy SDK for iOS:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '7.0'
pod "Kandy"
  • Save the Podfile.
  • Open a terminal and go to the directory containing the Podfile:
$ cd <project-path>
  • Run the pod install command. This will install the APIs specified in the Podspec, along with any dependencies they may have.
$ pod install
  • Close Xcode, and then open (double-click) your project’s .xcworkspace file to launch Xcode. From this time onwards, you must use the .xcworkspace file to open the project.

Step 3 - API Keys and Secret

One thing to notice when viewing your developer dashboard is that you have multiple API keys. It’s important to know which one to use. You have a public and secret API key for your account which you can use for administrative actions like creating new domains. You also have public and secret API keys for each of your domains which you use for most of Kandy’s features. The only API key you will need to use for the Javascript SDK is your domain’s public API key. Here’s where you can find it:

Dashboard Example

Step 4 - General Configuration

Compilation

64-bit compilation is fully supported.

Enable Background Mode

Now configure the .plist for your project:

  • In Xcode right-click your application���s .plist file and choose ���Open As Source Code���.
  • Copy & Paste the XML snippet inot the body of your file (..).

For the application to work as expected in the background, enter into the application’s Info.plist the following configuration:

	<key>UIBackgroundModes</key>
	<array>
	  <string>audio</string>
	  <string>voip</string>
	</array>

Step 5 - Start coding!

The SDK is now installed and setup. You can use the SDK from any of your implementation files by adding the Kandy SDK header file:

Classes that use Kandy SDK must import <KandySDK/KandySDK>

#import <KandySDK/KandySDK>

Initialize

Initialize the SDK by messaging the Kandy class and use one of the following methods:

  • <initializeSDKWithDomainKey:domainSecret:>
    • Valid domain key and secret are used for making all SDK operations.
    • Passing nil for secret is used for all operations except provisioning.
    • Passing nil for both key and secret is only valid if you login with username@domain. It is used for all operations except provisioning.
  • <initializeSDKWithKandySession> Used for passing previous registered KandySession

The preferred way of doing this is in the AppDelegate’s application:didFinishLaunchingWithOptions.

	(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
	  [Kandy initializeWithDomainKey:@"YourDomainKey" domainSecret:@"YourDomainSecret"];
	  return YES;
	}

Subscriber Provisioning

The subscriber provisioning lets you add/remove users to/from the system.

You can add subscribers in two ways:

  • Admin - can create (multiple) subscribers using the web GUI.
  • Users - can create individual subscribers using the SDK. Currently, the SDK supports adding subscribers using their phone number.

Subscription flow using phone number:

  • Send validation code using the required phone number
  • The user copies the validation code manually from the SMS
  • Send the validation code with the related phone number to the SDK
  • Get response with the KandyUserInfo that contains the relevant data for connecting the system

Request Activation Code

This call will send a registration request to the server.

The verification method (currently supports SMS and IVR) sends a validation code to the user, which the user should enter on the “validate” method.

Note: You can receive the KandyAreaCode by MCC/IP using the location service using getCountryInfo API call.

	-(void)registerSubscriber{
		KandyAreaCode * areaCode = [[KandyAreaCode alloc] initWithISOCode:@"US" andCountryName:@"United States" andPhonePrefix:@"+1"];
		NSString * strPhoneNumber = @"<phone number>";
		[[Kandy sharedInstance].provisioning requestCode:areaCode phoneNumber:strPhoneNumber codeRetrivalMethod:EKandyValidationMethod_sms responseCallback:^(NSError *error, NSString *destinationToValidate) {
			if (error) {
				// Failure
			} else {
				// Success
			}
		}];
	}

There is an additional option for IVR method that allows sending validation code as a part of caller���s phone number. You can specify prefix for an incoming call. Incoming call number will be a combination of the validation code followed by the prefix specified.

Note: If user will not answer or decline the call in time, this method will return an error in the callback

	-(void)registerSubscriber{
		KandyAreaCode * areaCode = [[KandyAreaCode alloc] initWithISOCode:@"US" andCountryName:@"United States" andPhonePrefix:@"+1"];
		NSString * strPhoneNumber = @"<phone number>";
		[[Kandy sharedInstance].provisioning requestCode:areaCode phoneNumber:strPhoneNumber codeRetrivalMethod:EKandyValidationMethod_call callerPhonePrefix:@"<caller phone prefix>" responseCallback:^(NSError *error, NSString *destinationToValidate) {
			if (error) {
				// Failure 
			} else {
				// Success
			}
		}];
	}

Validation

Activation Code

This call sends the received code to the server for validation. On success, the system adds the user as a provisioned subscriber.

	-(void)validate{
	  NSString * validationCode = @"9876";
	  KandyAreaCode * areaCode = [[KandyAreaCode alloc] initWithISOCode:@"US" andCountryName:@"United States" andPhonePrefix:@"+1"];
	  NSString * strPhoneNumber = @"180000000000";
	  [[Kandy sharedInstance].provisioning validateAndProvision:validationCode destination:strPhoneNumber areaCode:areaCode responseCallback:^(NSError *error, KandyUserInfo *provisionedUserInfo) {
		if (error) {
			// Failure
		} else {
			// Success
		}
	  }];
	}

Domain and User Provisioning Status

You can validate your domain and your user provisioning status using the method validateDomain:

	-(IBAction)validateDomain:(id)sender
	{
		[[Kandy sharedInstance].provisioning validateDomain:_domainTokenTextField.text domainSecret:_domainSecretTextField.text responseCallback:^(NSError *error, NSString *domainName) {
			if (error) {
				// Fail
			}
			else {
				// Success
			}
		}];
	}

Note: You can get the KandyUserInfo object of the user you subscribed from:

  • The KandyUserInfo you received in the Validate method.
  • All provisioned users can be accessed using the following code:

    objectivec -[[Kandy sharedInstance].sessionManagement.provisionedUsers

Deactivation

This method deactivates a provisioned subscriber device:

	-(void)deactivate{
	  [[Kandy sharedInstance].provisioning deactivateWithResponseCallback:^(NSError *error) {
		 if (error) {
			  // Failure
		  } else {
			  // Success
		  }
	  }];
	}

Access

After you have provisioned a user, you should log the user into the system.

Login is required for access to the SDK capabilities, i.e. Calls, Chats, Presence, etc.

Login Deletegates

You can be notified when the user’s connection to the system is changed.

Implement the KandyAccessNotificationDelegate in the suitable class.

Then use the following code:

	-(void)registerForAccessEvents{
	  [[Kandy sharedInstance].access registerNotifications:self];
	}

Once registered to Access notification, you should implement the following:

	-(void) registrationStatusChanged:(EKandyRegistrationState)registrationStatus{
		// Handle registration change
	}

	-(void) connectionStatusChanged:(EKandyConnectionState)connectionStatus{
		// Handle connection change
	}

	-(void) gotInvalidUser:(NSError*)error{
	// Invalid user
	}

	-(void) sessionExpired:(NSError*)error{
	// Session expired, you can renew it by calling to following method
	[[Kandy sharedInstance].access renewExpiredSession:^(NSError *error) {
		}];
	}

	-(void) SDKNotSupported:(NSError*)error{
	// Failure
	}

Background Execution

Kandy iOS SDK works gracefully when your app is in the background.

Now configure your app:

  • Enable VOIP background mode for your app (because VOIP involve audio, it is recommended that you also enable the Audio and AirPlay background mode. Ref section 2.4.2)
  • Before moving to the background, call the setKeepAliveTimeout:handler: method to install a handler to be executed periodically. Your app can use this handler to maintain its service connection.
  • Copy & paste below code snippet into the ���applicationWillResignActive:��� appdelegate method
	BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{
			[[Kandy sharedInstance].access keepAliveTimeoutHandler];
		}];
	}

Log In

This method logs in a subscriber to the Kandy system using his user name and password (taken from the KandyUserInfo object):

	-(void)login {
		KandyUserInfo * userInfo = [[KandyUserInfo alloc] initWithUserId:@"user1@domain" password:@"UserPassword"];
		[[Kandy sharedInstance].access login:userInfo responseCallback:^(NSError *error) {
		   if (error) {
				// Failure
			} else {
				// Success
			}
		}];
	}

Note: You can create KandyUserInfo either by the initializing it with user & password, or by taking the one you received from the Validate method.
After logging in to the system, you can also get the KandyUserInfo by the following code:

	[[Kandy sharedInstance].sessionManagement.currentUser

Renew Expired Session

Your session may expire over time. If so, you will be notified about it via Access notifications.

You can renew your expired session using:

	[[Kandy sharedInstance].access renewExpiredSession: :^(NSError *error) {
	  if (error) {
		// Failure
	  } else {
		// Success
	  }
	}];

Log Out

This method logs the subscriber out of the Kandy server.

	-(void)logout{
	  [[Kandy sharedInstance].access logoutWithResponseCallback:^(NSError *error) {
		if (error) {
		  // Failure
		} else {
		  // Success
		}
	  }];
	}

Connection and Registration State

To receive the current connection state to the server, use the following code:

	[[Kandy sharedInstance].access.connectionState
	[[Kandy sharedInstance].access.registrationState

Registration state describes current signup status of the user.

Connection state describes current login status of the user.

Push Notifications

Notifications services provides the ability to receive and handle notifications.

Preconditions

Before you can utilize Kandy Notifications you must first complete the following steps (code examples provided are for iOS 8).

For more information, visit Apple web site: Local and Remote Notification Programming Guide

  • Create Push notification certificate via iOS developer portal. Please contact Kandy Support in order to publish the *.pem file. For more information see: How to renew your Apple push notification push SSL certificate

  • In your AppDelegate, register your user notification settings using the following code:

        -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
          //���
          //initialize the KandySDK
          if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
              //Register UserNotificationSettings
              [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil]];
          } else {
              [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationType)(UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeAlert)];
          }
        }
    
  • Implement the user notification settings callback and register for remote notifications using the following code:

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

  • You can store the received deviceToken for future use using the following code:

    objectivec -(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { //��� [[NSUserDefaults standardUserDefaults]setObject:deviceToken forKey:@"deviceToken"]; NSString* bundleId = [[NSBundle mainBundle]bundleIdentifier]; //��� }

VOIP Push

Starting from iOS 8.1 there is another push type available - VoIP Push. VoIP Push provides additional functionality like running app in background and provide CPU resources so the app can prepare itself to handle push event and notify the user. You can find more information about VoIP Push and PushKit framework on Apple Developer Portal: Introduction to PushKit. Before registering VoIP Push token with KandySDK you should follow the steps as described in 7.1 but create VoIP Push certificate instead of standard one.

Following steps will show you sample usage of PushKit:

  • Create class implementing PKPushRegistryDelegate protocol.
  • When your app is ready to receive VoIP Push notifications, initialize PKPushRegistry object:

    objectivec PKPushRegistry * pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; pushRegistry.delegate = self; pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];

  • Implement PKPushRegistryDelegate methods:

    objectivec -(void)pushRegistry:(PKPushRegistry \*)registry didUpdatePushCredentials:(PKPushCredentials \*)credentials forType:(NSString \*)type { // Registered for VoIP push notifications. Store push token for future use [[NSUserDefaults standardUserDefaults]setObject:credentials.token forKey:@���voipToken���]; } -(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { // Received VoIP Push Notification, handle it } -(void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(NSString *)type { // VoIP Push credentials invalidated NSString* bundleId = [[NSBundle mainBundle]bundleIdentifier] [[Kandy sharedInstance].services.push disableRemoteNotificationsWithBundleId: bundleId responseCallback:^(NSErro *error) { if (error) { // Handle error } }]; }

Enabling Notifications

Once you have completed the preconditions above, you can handle the notifications on app launch.

To enable Kandy Remote Notifications.

Once the user is logged in, you can enable Kandy push notifications using the following code:

	[[Kandy sharedInstance].access login:kandyUserInfo andResponseCallback:^(NSError *error) {
		if (!error)
		{
			NSData* deviceToken = [[NSUserDefaults standardUserDefaults]objectForKey: isVoip ? @"voipToken" : @"deviceToken"];
			NSString* bundleId = [[NSBundle mainBundle]bundleIdentifier];
			[[Kandy sharedInstance].services.push enableRemoteNotificationsWithToken:deviceToken bundleId:bundleId isSandbox:isSandbox isVoipPush:isVoip responseCallback:^(NSError *error) {
			 if (error)
				{
				//handle error e.g no Internet connection
				}
			}];
		}
	}];

enableRemoteNotificationsWithToken: will pass the deviceToken to Kandy cloud and will later be used by it to refer push notification to this device.

Handling Notifications

After the users are logged in, they can handle Kandy notifications.

To handle Kandy SDK remote notification on the application launch, use the following code:

	-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
	  //���
	  //initialize the KandySDK
	  //register user notification settings
	  if (launchOptions && [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey] != nil) {
		NSDictionary* remoteNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
		id<KandyEventProtocol> event = [[Kandy sharedInstance].notifications getRemoteNotificationEvent:remoteNotification];
		[self_handleEventUI:event]; // event is nil if not handled by Kandy
		[[Kandy sharedInstance].notifications handleRemoteNotification:remoteNotification
		responseCallback: ^(NSError *error){
		  if (error &&
			error.domain isEqualToString:kandyNotificationServiceErrorDomian] &&
			error.code == EKandyNotificationServiceError_pushFormatNotSupported) {
			// Could not handle remote notification
		  } else {
			// Success
		  }
		}];
	  }
	}

To handle Kandy SDK remote notification while the application is running, use the following code:

	-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
	  [[Kandy sharedInstance].services.push handleRemoteNotification:userInfo responseCallback:^(NSError *error) {
		if (error &&
		   [error.domain isEqualToString:KandyNotificationServiceErrorDomain] &&
		   error.code == EKandyNotificationServiceError_pushFormatNotSupporte) {
		  //Push format not supported by Kandy, handle the notification my self
		}
	  }];
	}

If you wish to get the event prior to connecting (perhaps for UI changes), you can also use the call:

	id<KandyEventProtocol> event = [[Kandy sharedInstance].services.push getRemoteNotificationEvent:remoteNotification];

Location

Country Info

You can receive country codes based on your location (SIM and IP Address).

To receive a location-based country code use the following code:

	-(void)getCountryInfo {
	  [[Kandy sharedInstance].services.location getCountryInfoWithResponseCallback:^(NSError *error, KandyAreaCode *areaCode) {
		if (error) {
		  // Failure
		} else {
		  // Success
		}
	  }];
	}

Call

In this section, we will cover call settings; make a VOIP & PSTN and handling a call in general. Sample app code snippet is available at the end of each subsection.

Call Settings

Before application use any call features, you can adjust your call settings. Call settings represented by KandyCallServiceSettings class.

The following explains how you can use each call settings.

* `defaultCameraPosition:` The default camera position when initiating video (on call start or during the call). Default value is EKandyCameraPosition_front
* `cameraOrientationSupport:` The camera orientation support (while on a video call). Default value is EKandyCameraOrientationSupport_statusBar
* `shouldRejectIncomingCallsWhileInGSMCall:` Should the incoming call be automatically rejected incase already in GSM call. Default value is YES
* `isCallTerminationPushEnabled` Should the call termination events be sent by push
* `enableCallKitMode` KandySDK in CallKit mode will not manage VoIP calls. You'll be reponsibile to decline/hold or make any other actions on VoIP calls depending the state of incoming or active GSM calls. This will allow you to implement any logic needed to support your own behaviour. When you set `enableCallKitMode` to YES, this will also force useManualAudio set to YES. 
* `audioSessionCategory` Default category of AudioSession. This property takes effect only if `enableCallKitMode` is NO. When CallKit mode is enabled, you are responsible to setup and propogate audio session properties
* `audioSessionCategoryOptions` Default category options of AudioSession. This property takes effect only if `enableCallKitMode` is NO. When CallKit mode is enabled, you are responsible to setup and propogate audio session properties
* `audioSessionMode` Default mode of AudioSession. This property takes effect only if `enableCallKitMode` is NO. When CallKit mode is enabled, you are responsible to setup and propogate audio session properties
* `useManualAudio` Enable manual audio mode. This property is forced to YES when `enableCallKitMode` is YES. When useManualAudio is YES, KandySDK will not activate audio when the call starts, and will not deactivate audio when the call ends. Use manual audio when you need to stop audio recording so you can play video or audio etc. 
* `audioEnabled` Srart or stop audio engine. This property takes effect only if `useManualAudio` is YES. When CallKit mode is enabled, you must manually activate audio when the call starts, to enable audio recording, and deactivate audio when the call ends.

Make a VoIP Call

To make an outgoing call, use ` createVoipCall: callee: options:` method which takes below parameters

* callee: the callee is the full username of the call recipient, eg: user@example.kandy.io
* caller: the caller is an optional parameter of the caller���s full username.
* isStartCallWithVideo: It is a Boolean parameter, whether the caller���s video should be sent to the callee. 

Now know about containers for streaming element:

* localView - the localView is an IBOutlet UIView object, which is required by Kandy for media streaming, whether audio or video that will be streamed. 
* remoteView - It is an IBOutlet UIView object, which is required for remote video that will be streamed.
	-(void) makeVoipCall
	{
		KandyRecord * kandyRecord = [[KandyRecord alloc] initWithURI:<callee���s username> type:EKandyRecordType_contact];

		KandyRecord *initiator = [[KandyRecord alloc] initWithURI:<Caller���s full username> type:EKandyRecordType_contact];

		EKandyOutgingVoIPCallOptions outgoingCallOption = EKandyOutgingVoIPCallOptions_startCallWithVideo; 
		[[Kandy sharedInstance].services.call createVoipCall:initiator callee:kandyRecord options:outgoingCallOption responseCallback:^(NSError * _Nullable error, id<KandyOutgoingCallProtocol>  _Nullable kandyOutgoingCall) {
			__block CallViewController * vcCall = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([CallViewController class])];
		
			typeof(self) __weak weakSelf = self;
			[kandyOutgoingCall establishWithResponseBlock:^(NSError *error) {
				dispatch_async(dispatch_get_main_queue(), ^{
					if (!error)
					{                    
						[weakSelf.navigationController presentViewController:vcCall animated:YES completion:^{}];
					}
					else
					{
						UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																		message:error.localizedDescription
																	   delegate:weakSelf
															  cancelButtonTitle:@"OK"
															  otherButtonTitles:nil];
						[alert show];
					}
				});
		}];
	}];

Make a VoIP to PSTN Call

The following method initiates a VoIP to PSTN call, where:

* International phone number represents the destination phone number

Note: The destination phone number must be an E164 formatted number without the ‘+’ prefix, e.g: for US number (213) 456-7890 use 12134567890.

-(void) makePSTNCall {
	[[Kandy sharedInstance].services.call createPSTNCall:<> destination:self.txtPstnDestination.text options:self.blockedCallerIDSwitch.isOn ? EKandyOutgingPSTNCallOptions_blockedCallerID : EKandyOutgingPSTNCallOptions_none responseCallback:^(NSError * _Nullable error, id<KandyOutgoingCallProtocol>  _Nullable kandyOutgoingCall) {
		__block CallViewController * vcCall = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([CallViewController class])];
		typeof(self) __weak weakSelf = self;
		[kandyOutgoingCall establishWithResponseBlock:^(NSError *error) {
		dispatch_async(dispatch_get_main_queue(), ^{
			if (!error)
			{
				[weakSelf saveGuiValues];
				[weakSelf.navigationController presentViewController:vcCall animated:YES completion:^{}];
			}
			else
			{
				UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																message:error.localizedDescription
															   delegate:weakSelf
													  cancelButtonTitle:@"OK"
													  otherButtonTitles:nil];
				[alert show];
			}
		});
	}];

Notifications

Register KandyCallServiceNotificationDelegate to get notified on Incoming call, CallStateChanged, Missed call..etc

Now copy & paste below code snippet to register KandyCallServiceNotificationDelegate into your class

	[[Kandy sharedInstance].services.call registerServiceNotificationsDelegate:self];

Unregister your notification by adding below code snippet into ViewDidDisappear:

	[[Kandy sharedInstance].services.call unregisterNotifications:self];

Call delegates:

* gotIncomingCall :(id<KandyIncomingCallProtocol>)call

We know that we are receiving a call when the gotIncoming delegate is triggered. Our gotIncoming method should let the user know about the call and give them the option to accept or decline it. It will receive a call object that we can use to get information about the call.

```objectivec
	-(void) gotIncomingCall:(id<KandyIncomingCallProtocol>)call
	{
		BOOL isAnswerWithVideo = YES;
		[call accept:isAnswerWithVideo withResponseBlock:^(NSError *error) {
			if(error){
				//Failure
			}
			else
			{
				//Success
			}
		}];
	}
```

* gotMissedCall :(KandyMissedCall*)missedCall 

We know that we got a missed call  when the gotMissedCall is triggered.

```objectivec
	-(void) gotMissedCall:(KandyMissedCall*)missedCall{
	}
```

* stateChanged:forCall:

You will be notified when the call status change. Eg: Call initiated, Call connected, talking���etc

```objectivec
	-(void) stateChanged:(EKandyCallState)callState forCall:(id<KandyCallProtocol>)call{
		
		if (callState == EKandyCallState_unknown) {
			// code goes here
		}
		if (callState == EKandyCallState_initialized) {
			// code goes here
		}
		if (callState == EKandyCallState_dialing) {
			// code goes here
		}
		if (callState == EKandyCallState_sessionProgress) {
			// code goes here
		}
		if (callState == EKandyCallState_ringing) {
			// code goes here
		}
		if (callState == EKandyCallState_answering) {
			// code goes here
		}
		if (callState == EKandyCallState_talking) {
			// code goes here
		}
		if (callState == EKandyCallState_terminated) {
			// code goes here
		}
		if (callState == EKandyCallState_notificationWaiting) {
			// code goes here
		}
	}
```
* availableAudioOutputChanged:

You will be notified when audio route change. Eg: Speaker, Microphone

```objectivec
	-(void) availableAudioOutputChanged:(NSArray*) updatedAvailableAudioOutputs {
  
	}
```

In-Call Operations

Kandy SDK provides you the option to perform several operations over the active call: mute, switch camera, hold,…

In order to do so, use the call object relevant methods.

Holding a Call

When on a call, our user will be able to hold or unhold the call

	- (IBAction)didTapHold:(id)sender {
		if (self.kandyCall.isOnHold) {
			[self.kandyCall unHoldWithResponseCallback:^(NSError *error) {
				[self updateButtonStates];
				if (error) {
					UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																	message:error.localizedDescription
																   delegate:self
														  cancelButtonTitle:@"OK"
														  otherButtonTitles:nil];
					[alert show];
				}
			}];
		} else {
			[self.kandyCall holdWithResponseCallback:^(NSError *error) {
				[self updateButtonStates];
				if (error) {
					UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																	message:error.localizedDescription
																   delegate:self
														  cancelButtonTitle:@"OK"
														  otherButtonTitles:nil];
					[alert show];
				}
			}];
		}
	}

Ending a Call

To end a voice call, Kandy���s hangupWithResponseCallback: needs to be called by active call object

	- (void) hangupCall:(NSString *)callURI
	{
		id<KandyCallProtocol>call = [[Kandy sharedInstance].services.call getCallByRemoteRecord:[[KandyRecord alloc] initWithURI:callURI]];
	
		typeof(self) __weak weakSelf = self;
	
		[call hangupWithResponseCallback:^(NSError *error){
			dispatch_async(dispatch_get_main_queue(), ^{
				if (error)
				{
					UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																	message:error.localizedDescription
																   delegate:weakSelf
														  cancelButtonTitle:@"OK"
														  otherButtonTitles:nil];
					[alert show];
				}
				else if ([weakSelf.activeCall.remoteRecord.uri isEqualToString:callURI])
				{
					weakSelf.activeCall = nil;
					[weakSelf setupGui];
				}
			});
		}];
	
	}

Muting a Call

To mute a call, use muteWithResponseCallback: and unmuteWithResponseCallback: functions. Muting a call will stop audio from being transmitted.

	- (IBAction)didTapMute:(id)sender {
		if (self.kandyCall.isMute) {
			[self.kandyCall unmuteWithResponseCallback:^(NSError *error) {
				[self updateButtonStates];
				if (error) {
					UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																	message:error.localizedDescription
																   delegate:self
														  cancelButtonTitle:@"OK"
														  otherButtonTitles:nil];

					[alert show];
				}
			}];
		} else {
			[self.kandyCall muteWithResponseCallback:^(NSError *error) {
				[self updateButtonStates];
				if (error) {
					UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																	message:error.localizedDescription
																   delegate:self
														  cancelButtonTitle:@"OK"
														  otherButtonTitles:nil];
					[alert show];
				}
			}];
		}
	}

Changing Video Status

To start or stop sending video to other party during a call, use startVideoSharingWithResponseCallback: and stopVideoSharingWithResponseCallback: functions.

	- (IBAction)didTapVideoOnOff:(id)sender {
		if (self.kandyCall.isSendingVideo) {
			[self.kandyCall stopVideoSharingWithResponseCallback:^(NSError *error) {
				[self updateButtonStates];
				if (error) {
					UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																	message:error.localizedDescription
																   delegate:self
														  cancelButtonTitle:@"OK"
														  otherButtonTitles:nil];
					[alert show];
				}
			}];
		} else {
			[self.kandyCall startVideoSharingWithResponseCallback:^(NSError *error) {
				[self updateButtonStates];
				if (error) {
					UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
																	message:error.localizedDescription
																   delegate:self
														  cancelButtonTitle:@"OK"
														  otherButtonTitles:nil];
					[alert show];
				}
			}];
		}
	}

Call Statistics

KandyCallProtocol lets you retrieve information about quality of the connection.

To get statistics for specific call use following method:

	id<KandyCallProtocol> call = <Call object>;
	[call getRTPStatisticsWithCompletion:^(id<KandyCallRTPStatisticsProtocol> statistics, NSError *error) {
		// Handle statistics information
	}]

KandySDK and CallKit

Here is the sample implementation of CallKitManager that shows how to configure AudioSession and Start/Stop audio engine properly when using CallKit. In addition, this example shows how to use callUUID to manage calls via CXProvider

//
//  CallKitManager sample implementation
//

@interface CallKitManager () <CXProviderDelegate, CXCallObserverDelegate>

@property (nonatomic, strong) dispatch_queue_t processingQueue;

@property (nonatomic, strong) CXProvider * callProvider;
@property (nonatomic, strong) CXCallController * callController;

@property (nonatomic, strong) NSMapTable * callByUUID;
@property (nonatomic, strong) NSMapTable * uuidByCall;

/**
 Report incoming call. Invoke this method when you received call from KandySDK
 */
-(void)reportNewIncomingCall:(id<CallProtocol> _Nonnull)call completion:(void (^_Nullable)(NSError *_Nullable error))completion;

/**
 Report outgoing call started. Invoke this method right after you created your outgoing call
 */
-(void)reportNewOutgoingCall:(id<CallProtocol> _Nonnull)call completion:(void (^_Nullable)(NSError *_Nullable error))completion;

/**
 Report incoming/outgoing call connected. Invoke this method once your incoming call is connected or outgoing call is established
 */
-(void)reportCallConnected:(id<CallProtocol> _Nonnull)call completion:(void (^_Nullable)(NSError * _Nullable))completion;

/**
 Update call state
 */
-(void)reportCallUpdated:(id<CallProtocol> _Nonnull)call completion:(void (^_Nullable)(NSError *_Nullable error))completion;

/**
 Report call ended
 */
-(void)reportCallEnded:(id<CallProtocol> _Nonnull)call completion:(void (^_Nullable)(NSError *_Nullable error))completion;

@end

@implementation CallKitManager

+(instancetype)instance
{
    static dispatch_once_t onceToken;
    static CallKitManager * wrapper;
    dispatch_once(&onceToken, ^{
        wrapper = [[CallKitManager alloc] init];
    });
    return wrapper;
}

-(BOOL)isEnabled
{
    return YES;
}

-(instancetype)init
{
    if (self = [super init]) {
        
        if (self.isEnabled) {
            
            // Tell to KandySDK that we will control audioSession. This will set also 'useManualAudio' to YES
            [[Kandy sharedInstance].services.call.settings setEnableCallKitMode:YES];
            
            _processingQueue = dispatch_queue_create("com.kandy.CallKitManager.SerialQueue", DISPATCH_QUEUE_SERIAL);
            
            // We will store call IDs to manage our calls via CallKit
            _callByUUID = [NSMapTable strongToWeakObjectsMapTable];
            _uuidByCall = [NSMapTable weakToStrongObjectsMapTable];
            
            _callProvider = [[CXProvider alloc] initWithConfiguration:[self _createProviderConfigurationForCall:nil]];
            [_callProvider setDelegate:self queue:self.processingQueue];
            
            _callController = [[CXCallController alloc] initWithQueue:self.processingQueue];
            [_callController.callObserver setDelegate:self queue:self.processingQueue];
        }
    }
    
    return self;
}

-(void)dealloc
{
    [self.callProvider invalidate];
}

#pragma mark - Private

-(CXProviderConfiguration*) _createProviderConfigurationForCall:(id<KandyCallProtocol>)call
{
    BOOL isVideoSupportedForCall = YES;
    UIImage *callKitAppIcon = [UIImage imageNamed:@"CallKit_AppIcon"];
    CXProviderConfiguration * configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"Kandy App"];
    configuration.supportedHandleTypes = [NSSet setWithObjects:@(CXHandleTypePhoneNumber), nil];
    configuration.maximumCallsPerCallGroup = 1;
    configuration.maximumCallGroups = 1;
    configuration.supportsVideo = isVideoSupportedForCall;
    configuration.iconTemplateImageData = UIImagePNGRepresentation(callKitAppIcon);
    configuration.ringtoneSound = @"Ringtone.m4a";
    
    return configuration;
}

-(CXHandle*)_createHandleForCall:(id<KandyCallProtocol>)call
{
    NSString * value = [call remoteRecord].userName;
    CXHandle * handle = [[CXHandle  alloc] initWithType:CXHandleTypePhoneNumber value:value];
    
    return handle;
}

-(CXCallUpdate*)_createUpdateForCall:(id<KandyCallProtocol>)call
{
    CXCallUpdate *update = [[CXCallUpdate alloc] init];
    update.remoteHandle = [self _createHandleForCall:call];
    update.supportsHolding = NO;
    update.supportsGrouping = NO;
    update.supportsUngrouping = NO;
    update.supportsDTMF = YES;
    update.hasVideo = ([call isReceivingVideo] || [call isSendingVideo] || [call isCallStartedWithVideo]);
    update.localizedCallerName = [call remoteRecord].userName;
    
    return update;
}

-(NSUUID*)_addCall:(id<KandyCallProtocol>)call
{
    NSUUID * uuid = [NSUUID UUID];
    
    [self.callByUUID setObject:call forKey:uuid];
    [self.uuidByCall setObject:uuid forKey:call];
    
    return uuid;
}

-(void)_removeCall:(id<KandyCallProtocol>)call
{
    NSUUID * uuid = [self.uuidByCall objectForKey:call];
    
    if (uuid) {
        [self.callByUUID removeObjectForKey:uuid];
        [self.uuidByCall removeObjectForKey:call];
    }
}

-(void)_removeCallByUUID:(NSUUID*)uuid
{
    id<KandyCallProtocol> call = [self.callByUUID objectForKey:uuid];
    
    if (call) {
        [self.callByUUID removeObjectForKey:uuid];
        [self.uuidByCall removeObjectForKey:call];
    }
}

-(void)_removeAllCalls
{
    [self.callByUUID removeAllObjects];
    [self.uuidByCall removeAllObjects];
}

-(id<KandyCallProtocol>)_callByUUID:(NSUUID*)uuid
{
    id call = [self.callByUUID objectForKey:uuid];
    
    if (!call) {
        NSLog("CallKit - Call not found for UUID: %@", uuid);
    }
    
    return call;
}

-(NSUUID*)_uuidByCall:(id<KandyCallProtocol>)call
{
    NSUUID * uuid = [self.uuidByCall objectForKey:call];
    
    if (!uuid) {
        NSLog("CallKit - UUID not found for call: %@", call);
    }
    
    return uuid;
}

-(void)_configureAudioSession:(AVAudioSession *)audioSession
{
    NSLog("Configuring audio session");
    
    if (!audioSession) {
        audioSession = [AVAudioSession sharedInstance];
    }
    NSError * error = nil;
    
    
    NSString * audioSessionCategory = [Kandy sharedInstance].services.call.settings.audioSessionCategory;
    AVAudioSessionCategoryOptions audioSessionCategoryOptions = [Kandy sharedInstance].services.call.settings.audioSessionCategoryOptions;
    NSString * audioSessionMode = [Kandy sharedInstance].services.call.settings.audioSessionMode;
    
    
    [audioSession setCategory:audioSessionCategory withOptions:audioSessionCategoryOptions error:&error];
    if (error) {
        NSLog("Failed to set audio session category %@ with options %lx, error: %@", audioSessionCategory, (unsigned long)audioSessionCategoryOptions, error);
    }
    
    [audioSession setMode:audioSessionMode error:&error];
    if (error) {
        NSLog("Failed to set audio session mode %@, error: %@", audioSessionMode, error);
    }
}

-(void)_startAudioRecordingAndPalyback
{
    NSLog("Start audio recording and playback");
    [[Kandy sharedInstance].services.call.settings setAudioEnabled:YES];
}

-(void)_stopAudioRecordingAndPlayback
{
    NSLog("Stop audio recording and playback");
    [[Kandy sharedInstance].services.call.settings setAudioEnabled:NO];
}

#pragma mark - Public Methods Implementation

-(void)reportNewIncomingCall:(id<KandyCallProtocol>)call completion:(void (^)(NSError * _Nullable))completion
{
    NSLog("CallKit - report icoming call: %@", call);
    
    [self.callProvider setConfiguration:[self _createProviderConfigurationForCall:call]];
    
    NSUUID * uuid = [self _addCall:call];
    
    [self.callProvider reportNewIncomingCallWithUUID:uuid update:[self _createUpdateForCall:call] completion:^(NSError * _Nullable error) {
        
        if (error) {
            [self _removeCallByUUID:uuid];
        }
        else {
            [self _configureAudioSession:nil];
        }
        if (completion) {
            completion(error);
        }
    }];
}

-(void)reportNewOutgoingCall:(id<KandyCallProtocol>)call completion:(void (^)(NSError * _Nullable))completion
{
    NSLog("CallKit - report outgoing call started: %@", call);
    
    [self.callProvider setConfiguration:[self _createProviderConfigurationForCall:call]];
    
    NSUUID * uuid = [self _addCall:call];
    
    CXStartCallAction * action = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:[self _createHandleForCall:call]];
    CXTransaction * transaction = [[CXTransaction alloc] initWithAction:action];
    
    [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
        if (error) {
            [self _removeCallByUUID:uuid];
        }
        
        [self.callProvider reportOutgoingCallWithUUID:uuid startedConnectingAtDate:nil];
        
        [self reportCallUpdated:call completion:^(NSError * _Nullable reportError) {
            if (completion) {
                completion(error);
            }
        }];
    }];
}

-(void)reportCallConnected:(id<KandyCallProtocol>)call completion:(void (^)(NSError * _Nullable))completion
{
    NSLog("CallKit - report call connected: %@", call);
    
    NSUUID * uuid = [self _uuidByCall:call];
    
    if (uuid) {
        
        if ([call isIncomingCall]) {
            // Incoming call
            [self.callProvider reportCallWithUUID:uuid updated:[self _createUpdateForCall:call]];
        }
        else {
            // Outgoing call
            [self.callProvider reportOutgoingCallWithUUID:uuid connectedAtDate:nil];
        }
        
        if (completion) {
            completion(nil);
        }
    }
    else {
        NSLog("UUID not found for call %@", call);
    }
}

-(void)reportCallUpdated:(id<KandyCallProtocol>)call completion:(void (^)(NSError * _Nullable))completion
{
    NSLog("CallKit - report call updated: %@", call);
    
    NSUUID * uuid = [self _uuidByCall:call];
    
    if (uuid) {
        
        [self.callProvider reportCallWithUUID:uuid updated:[self _createUpdateForCall:call]];
        
        if (completion) {
            completion(nil);
        }
    }
    else {
        NSLog("UUID not found for call %@", call);
    }
}

-(void)reportCallEnded:(id<KandyCallProtocol>)call completion:(void (^)(NSError * _Nullable))completion
{
    NSLog("CallKit - report call ended: %@", call);
    
    NSUUID * uuid = [self.uuidByCall objectForKey:call];
    
    if (uuid) {
        
        // Terminate CallKit call - remove call after transaction will be executed
        CXAction * action = [[CXEndCallAction alloc] initWithCallUUID:uuid];
        CXTransaction * transaction = [[CXTransaction alloc] initWithAction:action];
        
        [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
            // What if transacton fails to despatch here?
            if (completion) {
                completion(error);
            }
        }];
    }
    else {
        NSLog("UUID not found for call %@", call);
    }
}

#pragma mark - CXObserverDelegate

-(void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call
{
    NSLog("CallKit - Call changed (from call observer): %@", call);
}

#pragma mark - CXProviderDelegate

-(void)providerDidBegin:(CXProvider *)provider
{
    NSLog("CallKit - Provided did begin");
}

-(void)providerDidReset:(CXProvider *)provider
{
    NSLog("CallKit - Provider did reset");
    
    for (id<KandyCallProtocol> call in self.callByUUID.objectEnumerator.allObjects) {
        [call hangupWithResponseCallback:^(NSError *error) {
            if (error) {
                NSLog("CallKit - Failed to hangup call on provider reset: %@", error);
            }
        }];
    }
    
    [self _removeAllCalls];
}
-(BOOL)provider:(CXProvider *)provider executeTransaction:(CXTransaction *)transaction
{
    NSLog("CallKit - Executing transaction: %@", transaction);
    return NO;
    
}
-(void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action
{
    NSLog("CallKit - Start call with UUID: %@", action.callUUID);
    
    [self _configureAudioSession:nil];
    
    [action fulfill];
}

-(void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action
{
    NSLog("CallKit - Answer call action with UUID: %@", action.callUUID);
    
    id<KandyIncomingCallProtocol> incomingCall = (id<KandyIncomingCallProtocol>)[self _callByUUID:action.callUUID];
    
    if (incomingCall) {
        
        [incomingCall accept:NO withResponseBlock:^(NSError *error) {
            if (error) {
                NSLog("Failed to accept call with error: %@", error);
                [action fail];
            }
            else {
                [action fulfill];
            }
        }];
    }
    else {
        [action fail];
    }
}

-(void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action
{
    NSLog("CallKit - Held call action with UUID: %@", action.callUUID);
    
    id<KandyCallProtocol> call = [self _callByUUID:action.callUUID];
    
    if (call) {
        
        if ([call isOnHold] && NO == action.isOnHold) {
            
            [call unHoldWithResponseCallback:^(NSError *error) {
                if (error) {
                    NSLog("Failed to unhold call with error: %@", error);
                    [action fail];
                }
                else {
                    [action fulfill];
                }
            }];
        }
        else if (NO == [call isOnHold] && action.isOnHold) {
            
            [call holdWithResponseCallback:^(NSError *error) {
                if (error) {
                    NSLog("Failed to hold call with error: %@", error);
                    [action fail];
                }
                else {
                    [action fulfill];
                }
            }];
        }
        else {
            [action fulfill]; // Already held/unheld
        }
    }
    else {
        [action fail];
    }
}

-(void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action
{
    NSLog("CallKit - Muted call action with UUID: %@", action.callUUID);
    
    id<KandyCallProtocol> call = [self _callByUUID:action.callUUID];
    
    if (call) {
        
        if ([call isMute] && NO == action.isMuted) {
            
            [call unmuteWithResponseCallback:^(NSError *error) {
                if (error) {
                    NSLog("CallKit - Failed to unmute call with error: %@", error);
                    [action fail];
                }
                else {
                    [action fulfill];
                }
            }];
        }
        else if (NO == [call isMute] && action.isMuted) {
            
            [call muteWithResponseCallback:^(NSError *error) {
                if (error) {
                    NSLog("CallKit - Failed to mute call with error: %@", error);
                    [action fail];
                }
                else {
                    [action fulfill];
                }
            }];
        }
        else {
            [action fulfill]; // Already muted/unmuted
        }
    }
    else {
        [action fail];
    }
}

-(void)provider:(CXProvider *)provider performPlayDTMFCallAction:(CXPlayDTMFCallAction *)action
{
    NSLog("CallKit - Sendind DTMF digits: %@", action.digits);
    
    id<KandyCallProtocol> call = [self _callByUUID:action.callUUID];
    
    if (call) {
        
        for (NSUInteger index = 0; index < action.digits.length; index++) {
            unichar tone = [action.digits characterAtIndex:index];
            NSLog("CallKit - Sendind tone: %c", tone);
            [call sendDTMF:tone];
        }
        
        [action fulfill];
    }
    else {
        [action fail];
    }
}

-(void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action
{
    NSLog("CallKit - End call action: %@", action);
    
    id<KandyCallProtocol> call = [self _callByUUID:action.callUUID];
    
    if (call) {
        
        if ([call callState] != ECallState_terminated) {
            
            if ([call isIncomingCall] && [call callState] != ECallState_talking) {
                
                id<KandyIncomingCallProtocol> incomingCall = (id<KandyIncomingCallProtocol>)call;
                
                NSLog("CallKit - reject incoming call: %@", incomingCall);
                
                [incomingCall rejectWithResponseBlock:^(NSError *error) {
                    
                    if (error) {
                        [action fail];
                    }
                    else {
                        [self _removeCallByUUID:action.callUUID];
                        
                        [self _stopAudioRecordingAndPlayback];
                        
                        [action fulfill];
                    }
                }];
                return;
            }
            
            NSLog("CallKit - hangup call: %@", call);
            
            [call hangupWithResponseCallback:^(NSError *error) {
                
                if (error) {
                    [action fail];
                }
                else {
                    [self _removeCallByUUID:action.callUUID];
                    
                    [self _stopAudioRecordingAndPlayback];
                    
                    [action fulfill];
                }
            }];
        }
        else {
            
            [self _stopAudioRecordingAndPlayback];
            
            [action fulfill];
        }
    }
    else {
        [action fail];
    }
}

-(void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession
{
    NSLog("CallKit - Audio session activated");
    
    [self _startAudioRecordingAndPalyback];
}

-(void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession
{
    NSLog("CallKit - Audio session deactivated");
    
    [self _stopAudioRecordingAndPlayback];
}

@end

Multi Party Conference (MPV)

Multi Party Conference Service allows you to create an audio/video conference room, and invite others to join your conference.

Join Room

After steps above were finished and you have called the room you should notify server about joining to the conference.

To join the conference you should use the following:

Method Parameters:

* (NSString *) conferenceID - The conference ID you received when you created the conference room
* (NSString *) nickname - The name that will be shown in room
	/**
	 *  Join a multi party conference
	 *
	 *  @param conferenceID     The conference ID
	 *  @param nickName         The name that will be shown in room
	 */
	-(void)join:(NSString *)conferenceID nickName:(NSString *)nickName
	{
		[[Kandy sharedInstance].services.multiPartyConference join:conferenceID nickName:nickName responseCallback:^(NSError *error) {
		
			if (nil != error) {
			
				// handle error
			}
			else {
			
				// handle response
			}
		}];
	}

Leave Room

Once disconnected from the conference, you must notify the server about leaving it. To leave the conference see the following example:

Method Parameter:

* (NSString *) conferenceID - The conference ID received when you created the conference room
	/**
	 *  Leave a multi party conference
	 *
	 *  @param conferenceID The   conference ID
	 */
	-(void)leave:(NSString *)conferenceID responseCallback:(void(^)(NSError * error))responseCallback
	{
		[[Kandy sharedInstance].services.multiPartyConference leave:_conferenceRoomDetails.conferenceID responseCallback:^(NSError *error) {
		
			if (nil != error) {
			
				// handle error
			}
			else {
			
				// handle response
			}
		}];
	}

Destroy Room

When you wish to finish the conversation and disconnect the participants you should call destroy room. To destroy the room see the following example:

Method Parameter:

* (NSString *) conferenceID - The conference ID received when you created the conference room
	/**
	 *  Destroy a multi party conference room
	 *
	 *  @param conferenceID The   conference ID
	 */
	-(void)destroyRoom:(NSString *)conferenceID
	{
		[[Kandy sharedInstance].services.multiPartyConference destroyRoom:(NSString *)conferenceID responseCallback:^(NSError * error) {
		
			if (nil != error) {
			
				// handle error
			}
			else {
			
				// handle response
			}
		}];
	}

Get Conference Status

Get info about an active conference call

Method Parameter:

* (NSString *) conferenceID - The conference ID received when you created the conference room. 

To retrieve conference status see the following example:

	/**
	 *  Get an active conference call info
	 *
	 *  @param conferenceID The conference ID
	 */
	- (void) getConferenceStatus:(NSString *)conferenceID
	{
		[[Kandy sharedInstance].services.multiPartyConference getConferenceCallDetailsWithConferenceID:conferenceID responseCallback:^(NSError *error, id<KandyMultiPartyConferenceCallDetailsProtocol> conferenceCallDetails) {
		
			if (nil != error) {
			
				// handle error
			}
			else {
			
				// handle response
			}
		}];
	}

Update Participant Name

Update a participant name while in an active conference call, currently this action is available only for the conference administrator.

To update a participant���s nickname see the following example:

Method Parameters:

* (NSString *) newNickName - The new nick name
* (NSString *) participantID - The ID of the participant you wish to update
* (NSString *) conferenceID - The conference ID received when you created the conference room
	/**
	 *  Update a participant name
	 *
	 *  @param newNickname   The new name
	 *  @param participantID The participant ID
	 *  @param conferenceID  The conference ID
	 */
	- (void) updateParticipantName:(NSString *)newNickname participantID:(NSString *)participantID conferenceID:(NSString *)conferenceID
	{
		[[Kandy sharedInstance].services.multiPartyConference updateParticipantName:newNickname participantID:participantID conferenceID:conferenceID responseCallback:^(NSError *error) {
		
			if (nil != error) {
			
				// handle error
			}
			else {
			
				// handle response
			}
		}];
	}

Update Control Actions

Update other participants control actions, currently this action is available only for the conference administrator.

Supported Actions:
* Mute
* Unmute
* Hold
* Unhold
* Enable Video
* Disable Video
* Remove from Conference

To update a participant control actions see the following example:

* (NSString *) participantID - The ID of the participant you wish to update
* (EKandyMultiPartyConferenceAction *) action - The control action you wish to apply on the participant
* (NSString *) conferenceID - The conference ID received when you created the conference room
	/**
	 *  Update participant available actions
	 *
	 *  @param participantID The participant ID
	 *  @param action        The control action
	 *  @param conferenceID  The conference ID
	 */
	- (void) updateContorlActionForParticipantWithID:(NSString *)participantID action:(EKandyMultiPartyConferenceAction)action conferenceID:(NSString *)conferenceID
	{
		KandyMultiPartyConferenceParticipantActionParams * participantActions = [[KandyMultiPartyConferenceParticipantActionParams alloc] initWithParticipantID:participantID action:action];                                                                                                                                                
	
		[[Kandy sharedInstance].services.multiPartyConference updateRoomParticipantActions:@[participantActions]
																			  conferenceID:conferenceID
			  responseCallback:^(NSError * error) {
			  
				  if (nil != error) {
				  
					  // handle error
				  }
				  else {
				  
					  // handle response
				  }
			  }];
	}

Chat

Chat service enables you to send/receive text and rich media to/from single or group recipient.

Make sure to enable the Push Notification to notify you of new messages or events, even when you are not actively using your application.

Notifications

You can receive Chat-related notifications (onChatReceived, onChatDelivered, onAutoDownloadProgress, onAutoDownloadFinished).

In order to receive Chat-related notifications, implement the KandyChatServiceNotificationDelegate in the suitable class.

You should acknowledge the received chat event unless, you wish to receive it again.

Then use the following code:

	-(void)registerForChatEvents {
	  [[Kandy sharedInstance].services.chat registerNotifications:self];
	}

To handle Chat events, use the following formats:

	-(void)onMessageReceived:(id<KandyMessageProtocol>)kandyMessage recipientType:(EKandyRecordType)recipientType{
	  switch (kandyMessage.mediaItem.mediaType) {
		case EKandyFileType_text:
		  //Your code here
		  break;
		case EKandyFileType_image:
		  //Your code here
		  break;
		case EKandyFileType_video:
		  //Your code here
		  break;
		case EKandyFileType_audio:
		  //Your code here
		  break;
		case EKandyFileType_location:
		  //Your code here
		  break;
		case EKandyFileType_contact:
		  //Your code here
		  break;
		case EKandyFileType_file:
		  //Your code here
		  break;
		case EKandyFileType_custom:
		  //Your code here
		  break;
		default:
		  break;
	  }
	}
* For Chat delivered:

```objectivec
	-(void)onMessageDelivered:(KandyDeliveryAck *)ackData {
	  //Your code here
	}
```

* Auto download progress:

```objectivec
	-(void) onAutoDownloadProgress:(KandyTransferProgress*)transferProgress kandyMessage:(id<KandyMessageProtocol>)kandyMessage {
	  //Progress
	}
```

* Auto download completion:

```objectivec
	-(void) onAutoDownloadFinished:(NSError*)error fileAbsolutePath:(NSString*)path kandyMessage:(id<KandyMessageProtocol>)kandyMessage {
	  if(error){
		//Failure
	  } else{
		//Success
	  }
	}
```

Send Text Message

To send an instant message, use the following code:

	-(void)sendMsg {
	  KandyRecord * kandyRecord = [[KandyRecord alloc] initWithURI:@"DestinationURI"];
	  KandyChatMessage *textMessage = [[KandyChatMessage alloc] initWithText:@"Message text" recipient:kandyRecord];
	  [[Kandy sharedInstance].services.chat sendChat:chatMessage progressCallback:^(KandyTransferProgress *transferProgress) {
		//progress
	  }
	  responseCallback:^(NSError *error) {
		if (error) {
		  //Failure
		} else {
		  //Success
		}
	  }];
	}

Send Group Message

To send a group IM, use the following code:

	-(void)sendGroupMsg{
	  // if you want to create a group record
	  // KandyRecord * kandyRecord = [[KandyRecord alloc] initWithUserName:@"groupUserName" domain:@"domain" type:EKandyRecordType_group];
	  // if you already have a groupID
	  KandyRecord * kandyRecord = self.kandyGroup.groupId;
	  KandyChatMessage *textMessage = [[KandyChatMessage alloc] initWithText:@���Message text��� recipient:kandyRecord];
	  [[Kandy sharedInstance].services.chat sendChat:chatMessage progressCallback:^(KandyTransferProgress *transferProgress){
		//progress
	  }
	  responseCallback:^(NSError *error) {
		if (error){
		  //Failure
		} else {
		  //Success
		}
	  }];
	}

Send Rich Media Message

To send an image message use the following code:

	-(void)sendImageMsg {
	  KandyRecord * kandyRecord = [[KandyRecord alloc] initWithURI:@"DestinationURI"];
	  id<KandyMediaItemProtocol> mediaItem = [KandyMessageBuilder createImageItem:@"absolutePathToImageFile text:@"Optional text"];
	  KandyChatMessage *message = [[KandyChatMessage alloc] initWithMediaItem:mediaItem recipient:kandyRecord];
	  [[Kandy sharedInstance].services.chat sendChat:chatMessage progressCallback:^(KandyTransferProgress *transferProgress){
		//progress
	  }
	  responseCallback:^(NSError *error) {
		if (error) {
		  //Failure
		} else {
		  //Success
		}
	  }];
	}

For other media messages the flow is similar, just create the appropriate media item:

  • Video:

    ```objectivec
    id<KandyMediaItemProtocol> mediaItem = [KandyMessageBuilder createVideoItem:@"absolutePathToImageFile" text:@"Optional text"];
    ```
    
  • Audio:

    ```objectivec
    id<KandyMediaItemProtocol> mediaItem = [KandyMessageBuilder createAudioItem:@"absolutePathToImageFile" text:@"Optional text"];
    ```
    
  • Location:

    ```objectivec
    CLLocation *location = [[CLLocation alloc] initWithLatitude:40.8283018 longitude:16.5500004];
    id<KandyMediaItemProtocol> mediaItem = [KandyMessageBuilder createLocationItem:location text:@"Optional text"];
    ```
    
  • Contact:

    ```objectivec
    [[Kandy sharedInstance].services.contacts createVCardDataByContact:kandyContact completionBlock:^(NSError *error, NSData *vCardData) {
      if (!error) {
        NSString *vcardPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"vcard.vcf"];
        [vCardData writeToFile:vcardPath atomically:YES];
        id<KandyMediaItemProtocol> contactMediaItem = [KandyMessageBuilder createContactItem:vcardPath text:@"Optional text];
      }
    }];
    ```
    

Send typing indication events

Chat service provides methods and classes allowing client to notify other users and/or groups that current provisioned user is typing. Chat service does not handle typing indication logic and provides only low level signaling functionality.

To send user typing indication event use following code:

	// Create typing indication object with start typing state
	KandyTypingIndication *typingIndication = [[KandyTypingIndication alloc] initWithState:EKandyUserTypingIndicationState_typing contentType:@"text"];

	// Send typing indication object to other user
	[[Kandy sharedInstance].services.chat sendUserTypingIndication:typingIndication destination:otherUserRecord responseCallback:^(NSError *error) {
				if (error) {
					LogError("Failed to send typing indication: %@ to user: %@", typingIndication, otherUserRecord);
				}
				else {
					LogInfo("Typing indication sent to %@: %@", otherUserRecord, typingIndication);
				}
	}];

Sample implementation for typing indication callback in Chat Service delegate:

	-(void)onUserTypingIndicationReceived:(KandyTypingIndication *)typingIndication sender:(KandyRecord *)sender destination:(KandyRecord *)destination
	{
			switch (typingIndication.state) {
				case EKandyUserTypingIndicationState_typing:
					LogInfo("User %@ started typing!", sender);
					break;
				
				case EKandyUserTypingIndicationState_idle:
					LogInfo("User %@ finished typing!", sender);
					break;
			}
		});
	}

Attaching custom data to message

Chat messages can be sending with the additional custom data. To attach your data to message create chat message and update additional data for its mediaItem:

	KandyRecord * kandyRecord = [[KandyRecord alloc] initWithURI:@" user1@kandy.io"];
	// Create chat message. Custom user���s data can be added to any type of chat messages
	KandyChatMessage *textMessage = [[KandyChatMessage alloc] initWithText:@���Message text��� recipient:kandyRecord];
	// Add custom data to message
	[textMessage.mediaItem updateAddtitionalData:@{@"timestamp": @( [NDate timeIntervalSinceReferenceDate])}];

*Note: recipient username is a full username of recipient. Eg: user1@domainname*

SMS Out

To send an SMS to a regular cellular phone use the following code (the SMS will be sent via Kandy service):

	- (void)sendSMS: {
	KandySMSMessage *smsMessage = [[KandySMSMessage alloc] initWithText:@���The SMS text��� recipient:@���18009871234��� displayName:@���SMS display name���];
	[[Kandy sharedInstance].services.chat sendSMS:smsMessage responseCallback:^(NSError *error) {
		if (error)
		{
			//Failure
		}
		else
		{
			//Success
		}
		}];
	}

*Note: Don���t forget to add country code as prefix to recipient phone number while trying to send SMS out. Eg: 18001234567*

Pull Events

To pull pending Chat events from the server, use the following code:

	-(void)pullEvents {
		[[Kandy sharedInstance].services.chat pullEventsWithResponseCallback:^(NSError *error) {
			if (error) {
			  // error
			}
		}];
	}

Acknowledging Events

You should respond to KandyChatServiceNotificationDelegate Chat notifications by acknowledging them; otherwise, as mentioned before, you will receive them again.

To do this, use the following code:

  • For Chat received:

    ```objectivec
        -(void)onMessageReceived:(KandyMessage*)kandyMessage {
          [kandyMessage markAsReceivedWithResponseCallback:^(NSError *error) {
              if (error) {
                //Failure
              } else {
                //Success
              }
          }];
        }
    ```
    
  • For Chat delivered:

    ```objectivec
        -(void)onMessageDelivered:(KandyDeliveryAck*)ackData{
          [[Kandy sharedInstance].services.chat markAsReceived:ackData withResponseCallback:^(NSError *error) {
            if (error) {
              //Failure
            } else {
              //Success
            }
          }
        }];
    ```
    

Downloading Media

To download media, use the following:

	-(void)downloadMediaForMessage:( id<KandyMessageProtocol>)kandyMessage {
	  [[Kandy sharedInstance].services.chat downloadMedia:kandyMessage progressCallback:^(KandyTransferProgress *transferProgress) {
		//Download progress
	  } responseCallback:^(NSError *error, NSString *fileAbsolutePath) {
		if (error) {
		  //Failure
		} else {
		  //Success
		}
	  }];
	}

Downloading Media Thumbnails

To download media thumbnails, use the following:

	-(void)downloadMediaThumbnail:( id<KandyMessageProtocol>)kandyMessage {
	  [[Kandy sharedInstance].services.chat downloadMediaThumbnail:kandyMessage thumbnailSize:EKandyThumbnailSize_medium progressCallback:^(KandyTransferProgress *transferProgress) {
		//Progress
	  } responseCallback:^(NSError *error, NSString *fileAbsolutePath) {
		if (error) {
		  //Failure
		} else {
		  //Success
		}
	  }];
	}

Cancel Operation

You can cancel an upload/download operation associated to a Kandy Message using:

	-(void)cancelOperation:( id<KandyMessageProtocol>)kandyMessage {
	  [[Kandy sharedInstance].services.chat cancelWithKandyMessage:kandyMessage responseCallback:^(NSError *error) {
		if (error) {
		  //Failure
		} else {
		  //Success
		}
	  }];
	}

Settings

The following are the Chat settings:

  • autoDownload_media_connectionType - when to allow the application to download the received media. (3G only, Wi-Fi only, Always, Never)
  • autoDownload_media_maxSizeKB - Maximum size of downloaded media
  • autoDownload_thumbnail - whether the thumbnail should be downloaded automatically
  • autoDownload_thumbnailSize - the size of the auto downloaded thumbnail
  • downloadPathBuilder - a protocol that you can conform to and override.
    The path builder allows you to set the download path for newly downloaded files and the file names according to your needs.
  • mediaMaxUploadSizeInKB - the maximum size allowed for upload

To get and set Kandy Chat settings, use:

	[[Kandy sharedInstance].services.chat.settings

Group

In this section, we will cover creating and managing a group, retrieving group information, sending instant messages (IMs) from one user to a group of users and receiving group messages.

Make sure to enable the Push Notification to notify you of new messages or events, even when you are not actively using your application.

Notifications

Register KandyGroupServiceNotificationDelegate to get notified on onGroupDestroyed, onGroupUpdated, onParticipantJoined..etc

Now copy & paste below code snippet to register KandyGroupServiceNotificationDelegate into your class

	[[Kandy sharedInstance].services.group registerServiceNotificationsDelegate:self];

Unregister your notification by adding below code snippet into ViewDidDisappear:

	[[Kandy sharedInstance].services.group unregisterNotifications:self];

Group delegates:

	-(void)onGroupDestroyed:(KandyGroupDestroyed*)groupDestroyedEvent{
	  //your code here
	}

	-(void)onGroupUpdated:(KandyGroupUpdated*)groupUpdatedEvent{
	  //your code here
	}

	-(void)onParticipantJoined:(KandyGroupParticipantJoined*) groupParticipantJoinedEvent{
	  //your code here
	}

	-(void)onParticipantKicked:(KandyGroupParticipantKicked*) groupParticipantKickedEvent{
	  //your code here
	}

	-(void)onParticipantLeft:(KandyGroupParticipantLeft*) groupParticipantLeftEvent{
	  //your code here
	}

Group Create

KandyGroup can be created with name (optional) and image (optional).

	-(void)createGroup {
		KandyGroupParams * kandyGroupParams = [[KandyGroupParams alloc] init];
		kandyGroupParams.groupName = @"groupName";
		kandyGroupParams.groupAbsoluteImagePath = @"pathToGroupImage"; //will also initialize the group.image
		[[Kandy sharedInstance].services.group createGroup:kandyGroupParams progressCallback:^(KandyTransferProgress *transferProgress) {
			NSLog(@"Group Image upload progress : %ld", (long)transferProgress.transferProgressPercentage);
		} responseCallback:^(NSError *error, KandyGroup *group) {
		if (error) {
		//error
		} else {
		//group created successfully
		}];
	}

Add/Remove Group Participants

You can add or remove participants from your group.

	-(void)addParticipant{
	  KandyRecord * participant = [[KandyRecord alloc] initWithURI: @"participantURI���];
	  KandyRecord * groupId = self.kandyGroup.groupId;
	  [[Kandy sharedInstance].services.group addGroupParticipants:@[participant] groupId:groupId responseCallback:^(NSError *error, KandyGroup *group) {
		if (error) {
		  //error
		} else {
		  //participants added
		 }
	   }];
	}

Group Update

You can update the group name and image. This method can also be used to remove the group image and name.

kRemoveGroupName - use to remove a group name

kRemoveGroupImage - use to remove a group image

-(void)createGroup{
	KandyRecord * groupId = self.kandyGroup.groupId;
	KandyGroupParams * params = [[KandyGroupParams alloc] init];
	kandyGroupParams.groupName = @"groupName";
	kandyGroupParams.groupAbsoluteImagePath = @"pathToGroupImage"; //will also initialize the group.image
	[[Kandy sharedInstance].services.group updateGroupParams:params groupId:groupId progressCallback:^(KandyTransferProgress *transferProgress) {
			NSLog(@"Update group image progress : %ld", (long)transferProgress.transferProgressPercentage);
		} responseCallback:^(NSError *error, KandyGroup *group) {
			if (error) {
				//error            
			} else {
				//group created successfully
		}
		}];
	}

Note:

  • Update group name or group image only by calling:
    updateGroupName, updateGroupImage
  • Remove group image is also possible by calling:
    removeGroupImage

Group Destroy

To delete a group, use following code

	-(void)destroyGroup{
	  [[Kandy sharedInstance].services.group destroyGroup:self.kandyGroup.groupId responseCallback:^(NSError *error) {
		if (error) {
		  //error
		} else {
		  //group destryed
		}
	  }];
	}

My Groups

To get all groups associated with a KandyRecord call this method:

	-(void)getMyGroups{
	  [[Kandy sharedInstance].services.group getMyGroupsWithResponseCallback:^(NSError *error, NSArray *groups) {
		if (error) {
		   //error
		} else {
		  /NSLog(@���My groups: %@���,groups);
		}
	  }];
	}

Group Details

To get group details, use following code

	-(void)getGroupDetails{
	  [[Kandy sharedInstance].services.group getGroupDetails:self.groupId responseCallback:^(NSError *error, KandyGroup *group) {
		if (error) {
		  //error
		} else {
		  //NSLog(@���Group details: %@���,group);
		}
	  }];
	}

Group Settings

The following are the Group settings:

  • downloadPathBuilder - a protocol that you can conform to and override.
    The path builder allows you to set the download path for newly downloaded files and the file names according to your needs.
  • groupImageMaxUploadSizeInKB - the maximum size allowed for upload

To get and set Kandy Group settings, use:

	[[Kandy sharedInstance].services.group.settings

Events

Events service allows you to pull current pending events or events history from server. Events could be the chat messages, group chats and group changes events, or message delivery notifications.

Notifications

You can receive history event notifications (onConversationsDeleted, onHistoryEventsDeleted).

In order to receive these notifications, implement the KandyEventServiceNotificationDelegate methods and properties in the suitable class.

You should acknowledge all received events, unless you wish to receive it again.

Sample code for events service delegate implementation:

Register Notification

Now copy & paste below code snippet to register event delegate into your class

	-(void)registerForHistoryEvents{
		[[Kandy sharedInstance].services.events registerNotifications:self];
	}

Unregister Notification

Unregister your notification by adding below code snippet into ViewDidDisappear:

	- (void) unRegisterForHistoryEvents
	{
		[[Kandy sharedInstance].services.events unregisterNotifications:self];
	}

Delegate methods:

	-(void)onConversationsDeleted:(KandyConversationsDeleted *)conversationsDeletedEvent
	{
		// Handle your logic when some conversations has been deleted from history
			
		// Remove from list of pending events
		[conversationsDeletedEvent markAsReceivedWithResponseCallback:nil];
	}

	-(void)onHistoryEventsDeleted:(KandyHistoryEventsDeleted *)hisotryEventsDeletedEvent
	{ 
		// Handle your logic when some events has been deleted from history

		// Remove from list of pending events
		[hisotryEventsDeletedEvent markAsReceivedWithResponseCallback:nil];
	}

Pull Pending Events

To pull pending events from the server, use the following code:

Events pulled will be dispatched to relevant service delegates, and will be available via Chat/Group/Call services.

-(void)pullEvents{
    [[Kandy sharedInstance].services.events pullPendingEventsWithResponseCallback:^(NSError *error, NSArray *kandyEvents) {
        if(error)
        {
            //Failure
        }
        else
        {
            //Success
        }
    }];
}

Acknowledging Pending Events

You should mark pulled pending events as received by acknowledging them, otherwise you will receive them again and again every time you pull pending events from server.

To flag event as received and stop getting it in the future, use markAsReceivedWithResponseCallback: method from KandyEventProtocol:

	-(void)onMessageReceived:(KandyMessage*)kandyMessage {
		[kandyMessage markAsReceivedWithResponseCallback:^(NSError *error) {
			if(error){
				//Failure
			}
			else
			{
				//Success
			}
		}];
	}

You can also use markEventsAsReceived:responseCallback: method of KandyEventService to mark single event or multiple events at once:

For chat message:

	-(void)onMessageReceived:(KandyMessage*)kandyMessage {
		[[Kandy sharedInstance].services.events markEventsAsReceived:@[kandyMessage] responseCallback:^(NSError *error) {
			if(error){
				//Failure
			}
			else
			{
				//Success
			}
		}];
	}

For delivery notification:

	-(void)onMessageDelivered:(KandyDeliveryAck*)ackData{
		[[Kandy sharedInstance].services.events markEventsAsReceived: @[ackData] responseCallback:^(NSError *error){
			if(error)
			{
				//Failure
			}
			else
			{
				//Success
			}

		}];
	}

Presence

Presence service enables you to retrieve the “last seen” date of contacts.

Last Seen

To retrieve the last seen date, call the method retrievePresenceForRecords:withResponseCallback

To retrieve <KandyPresenceProtocol> objects representing the presence data, including last seen date, of the requested array of contacts in the form of KandyRecords.

The response callback also contains missingPresenceKandyRecords - an array with the requested KandyRecords for which no presence data was found.

	- (void)getLastSeen{
	  KandyRecord* kandyRecord = [[KandyRecord alloc] initWithURI:@"Contact@DomainName.Com" type:EKandyRecordType_contact];

	  [[Kandy sharedInstance].services.presence getPresenceForRecords:[NSArray arrayWithObject:kandyRecord] responseCallback:^(NSError *error, NSArray *presenceObjects, NSArray *missingPresenceKandyRecords) {
		  if (error) {
			// Failure
		  } else {
			// Success
			id <KandyPresenceProtocol> presenceObject = [presenceObjects objectAtIndex:0];
			NSString *strDate = [NSDateFormatter localizedStringFromDate:presenceObject.lastSeen
			dateStyle:NSDateFormatterShortStyle
			timeStyle:NSDateFormatterShortStyle];
			self.lblLastSeen.text = strDate;
		 }
	  }];
	}

Contacts

The Contacts service enables you to access the device address book and the domain directory, retrieve and filter contacts and get notification on changes to the device address book.

Notifications

You can receive Contacts-related notifications (onDeviceContatcsChanged).

Implement KandyContactsServiceNotificationDelegate with its suitable classes.

Register Notification

	-(void)registerForContactsChanges{
	  [[Kandy sharedInstance].services.contacts registerNotifications:self];
	}

Unregister Notification

	-(void)registerForContactsChanges{
	  [[Kandy sharedInstance].services.contacts unregisterNotifications:self];
	}

Device AddressBook Contacts

The following method returns the device contacts as id<KandyContactProtocol> objects. If operation failed, returns an error.

	-(void)getDeviceContacts{
	  [[Kandy sharedInstance].services.contacts getDeviceContactsWithResponseCallback:^(NSError *error, NSArray *kandyContacts) {
		if (error) {
		  // Failure
		} else {
		  // Success
		}
	  }];
	}

Filtering Device AddressBook Contacts

The following method returns the filtered device contacts as id<KandyContactProtocol> objects. If operation fails, it returns an error.

You can filter contacts in two ways:

  • According to the fields the contacts contains: phones/emails/all (bitwise operation)
  • By the display name, with a given “search” string. If filter is empty or nil, does not filter by text.

Filtering of the fields is prefix based.

	-(void)filterDeviceContacts{
	  [[Kandy sharedInstance].services.contacts getDeviceContactsWithFilter:EKandyDeviceContactFilter_phone | EKandyDeviceContactFilter_email textSearch:@"John" responseCallback:^(NSError *error, NSArray *kandyContacts) {
		if (error) {
		  // Failure
		} else {
		  // Success
		}
	  }];
	}

Retrieving Domain Directory Contacts

The following method returns the domain directory contacts as id<KandyContactProtocol>; objects. If the operation failed, it returns an error.

	-(void)getDomainDirectoryContacts {
	  [[Kandy sharedInstance].services.contacts getDomainDirectoryContactsWithResponseCallback:^(NSError *error, NSArray *kandyContacts) {
		if (error) {
		  // Failure
		} else {
		  // Success
		}
	  }];
	}

Filtering Domain Directory Contacts

The following method returns the filtered domain directory contacts as id<KandyContactProtocol> objects. If the operation fails, it returns an error.

Filtering is done by a given text string, a EKandyDomainContactFilter enum indicating which fields to filter and a boolean specifying whether the filtering is case sensitive. Filtering of the fields logic is achieved by using “contains”.

	-(void)filterDomainDirectoryContacts {
	  [[Kandy sharedInstance].services.contacts getFilteredDomainDirectoryContactsWithTextSearch:@"John" filterType:EKandyDomainContactFilter_firstAndLastName caseSensitive:NO responseCallback:^(NSError *error, NSArray *kandyContacts) {
		if (error) {
		  // Failure
		} else {
		  // Success
		}
	  }];
	}

VCard Helper Methods

KandySDK provides you with an easy way to create a VCard by KandyContact and vice versa.

VCard by KandyContact

	-(void)getVCardForKandyContact:(id<KandyContactProtocol>)kandyContact {
	  [[Kandy sharedInstance].services.contacts createVCardDataByContact:kandyContact completionBlock:^(NSError *error, NSData *vCardData) {
		if (error) {
		  // Failure
		} else {
		  // Success
		}
	  }];
	}

KandyContact by VCard

	-(id<KandyContactProtocol>)getKandyContactByVCard:( NSData*)vCardData {
	  NSError* error = nil;
	  [[Kandy sharedInstance].services.contacts createContactByVCard:vCardData error:&error];
	  if (error) {
		// Failure
	  } else {
		// Success
	  }
	}

Cloud Storage

Cloud storage service allows you to upload/download files to Kandy cloud storage.

Upload File

To upload file to the cloud storage use the following code:

	-(IBAction)uploadFile:(id)sender{
	// Get file name and path for selected file
	id<KandyFileItemProtocol> fileItem = [KandyMessageBuilder createFileItem:@���localPathFile��� text:nil];

	// Upload the file
	[[Kandy sharedInstance].services.cloudStorage uploadMedia: fileItem progressCallback:^(KandyTransferProgress *transferProgress) {
	  // Notify UI about progress change
	  } responseCallback:^(NSError *error) {
		if (error) {
			// Fail
		} else {
			// Success. Now you can get ContentUUID from updated fileItem to download your file later
		}
	  }];
	}

Download File

To download file from the cloud storage, use the following code:

	-(IBAction)downloadFile:(id)sender{
	  NSString * downloadFilename = <Path and file name>;
	  id<KandyFileItemProtocol> transferingFileItem = [KandyMessageBuilder createFileItem:nil text:nil];
	  NSString * contentUUID = <The file ContentUUID you wish to download>;
	  [transferingFileItem updateContentUUID:contentUUID];
		// Download the file
	  [[Kandy sharedInstance].services.cloudStorage downloadMedia:transferingFileItem fileName:[downloadFileName lastPathComponent] downloadPath:self.documentsDirectory progressCallback:^(KandyTransferProgress *transferProgress) {
		  // Notify UI about progress change
		} responseCallback:^(NSError *error, NSString *filePath) {
		if (error) {
		  // Fail
		} else {
		  // Success. transferingFileItem now filled with all relevant information
		}
	  }];
	}

Download thumbnail

To download media thumbnail from the cloud storage, use the following code (thumbnails only available for images and video files):

	-(IBAction)downloadFile:(id)sender{
	  NSString * thumbnailFilename = <Path and file name>;
	  id<KandyFileItemProtocol> fileItem = [KandyMessageBuilder createFileItem:thumbnailFileName text:nil];
	  NSString * contentUUID = < The file ContentUUID you wish to download its thumbnail>;
	  [transferingFileItem updateContentUUID:contentUUID];
	  // Download the file thumbnail
	  [[Kandy sharedInstance].services.cloudStorage downloadThumbnail:fileItem thumbnailName:[ thumbnailFilename lastPathComponent] downloadPath:self.documentsDirectory thumbnailSize:EKandyThumbnailSize_medium progressCallback:^(KandyTransferProgress *transferProgress) {
		// Notify UI about progress change
	  } responseCallback:^(NSError *error, NSString *filePath) {
		if (error) {
		  // Fail
		} else {
		  // Success
		}
	  }];
	}

Cancel Media Transfer

To cancel file upload/download process use the following code:

	-(IBAction)cancelTransfer:(id)sender{
	  [[Kandy sharedInstance].services.cloudStorage cancelMediaTransfer:self.transferingFileItem responseCallback:^(NSError *error) {
		if (error) {
		  // Fail
		} else {
		  // Success
		}
	  }];
	}

Error Handling

Each error the SDK returns contains two properties:

  • Domain
  • Code

In each scenario, the domain will probably be KandyGeneralServiceErrorDomain or a domain that is relevant for your operation (for example: KandyCallServiceErrorDomain).

In each domain, there are multiple error codes, that represent the error scenarios the SDK recognized.

You should handle errors by the domain category, and by switching its relevant code using the following code:

	-(void)errorHandling {
	  [[Kandy sharedInstance].provisioning signoutWithResponseCallback:^(NSError *error) {
		BOOL isHandledError = YES;
		if ([error.domain isEqualToString:KandyGeneralServiceErrorDomain]) {
		  switch (error.code) {
			case EKandyGeneralServiceError_NetworkError:
			  // Error handling code
			  break;
			case EKandyGeneralServiceError_RequestTimeout:
			  // Error handling code
			  break;
			default:
			  isHandledError = NO;
			  break;
		  }
		}
		else if ([error.domain isEqualToString:KandySignupServiceErrorDomain]) {
		  switch (error.code) {
			case EKandySignupErrors_userValidationFailed:
			  // Error handling code
			case EKandySignupErrors_validationCodeExpired:
			  // Error handling code
			  // .
			  // .
			  // .
			default:
			  isHandledError = NO;
			  break;
		  }
		}
		else {
		  isHandledError = NO;
		}
	  }];
	}

Reference Application

Kandy has made a sample iOS application, which implements the basic services and functions outlined below.

You can quickly get started by cutting and pasting code sections from this application into your own, and expanding from there.

You can also use it for troubleshooting, as a sanity test, when your system under development goes wrong or fails to operate as expected.

This application can be found in the Code section, here.