├── .gitignore ├── Podfile ├── kaleo.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── README.md ├── kaleo ├── kaleo.entitlements ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── AppDelegate.swift └── ViewController.swift └── kaleo.xcworkspace └── contents.xcworkspacedata /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | Pods/ 4 | Podfile.lock 5 | SwiftVoiceCallKitQuickstart.xcworkspace 6 | 7 | xcuserdata/ 8 | *.xcuserstate 9 | .idea/ 10 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/twilio/cocoapod-specs' 2 | source 'https://github.com/CocoaPods/Specs.git' 3 | 4 | target 'kaleo' do 5 | use_frameworks! 6 | 7 | pod 'TwilioVoiceClient', '=2.0.0-beta6' 8 | end 9 | -------------------------------------------------------------------------------- /kaleo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kaleo iOS APP 2 | === 3 | 4 | Please follow this [instruction](https://www.twilio.com/docs/api/voice-sdk/ios/getting-started) to start the app 5 | 6 | Requirement 7 | --- 8 | 9 | - Cocoapods 1.0 or later. Run `pod install` to build the app. 10 | - Ngrok -------------------------------------------------------------------------------- /kaleo/kaleo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /kaleo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /kaleo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /kaleo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSMicrophoneUsageDescription 24 | $(EXECUTABLE_NAME) would like to access your microphone 25 | UIBackgroundModes 26 | 27 | audio 28 | fetch 29 | remote-notification 30 | voip 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /kaleo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /kaleo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Twilio Voice with CallKit Quickstart - Swift 4 | // 5 | // Copyright © 2016 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import TwilioVoiceClient 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | NSLog("Twilio Voice Version: %@", VoiceClient.sharedInstance().version()) 18 | 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /kaleo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 34 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /kaleo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5799913434DA347341225C4B /* Pods_kaleo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 971D7D3B5B104F934A9FA892 /* Pods_kaleo.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 11 | 8B14D7FD1E7DA48F00B83746 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B14D7FC1E7DA48F00B83746 /* AppDelegate.swift */; }; 12 | 8B14D8021E7DA48F00B83746 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B14D8001E7DA48F00B83746 /* Main.storyboard */; }; 13 | 8B14D8041E7DA48F00B83746 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B14D8031E7DA48F00B83746 /* Assets.xcassets */; }; 14 | 8B14D8071E7DA48F00B83746 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B14D8051E7DA48F00B83746 /* LaunchScreen.storyboard */; }; 15 | 8B14D8101E7DA5F400B83746 /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B14D80F1E7DA5F400B83746 /* PushKit.framework */; }; 16 | 8B14D8121E7DA5FA00B83746 /* CallKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B14D8111E7DA5FA00B83746 /* CallKit.framework */; }; 17 | 8B14D81A1E7F9BA400B83746 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B14D8191E7F9BA400B83746 /* ViewController.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 02132B4EC71358845E4B0E2E /* Pods-kaleo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-kaleo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-kaleo/Pods-kaleo.debug.xcconfig"; sourceTree = ""; }; 22 | 5A36FA55288F67CC6FABE375 /* Pods-kaleo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-kaleo.release.xcconfig"; path = "Pods/Target Support Files/Pods-kaleo/Pods-kaleo.release.xcconfig"; sourceTree = ""; }; 23 | 8B14D7F91E7DA48F00B83746 /* kaleo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = kaleo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 8B14D7FC1E7DA48F00B83746 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | 8B14D8011E7DA48F00B83746 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 8B14D8031E7DA48F00B83746 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 8B14D8061E7DA48F00B83746 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 8B14D8081E7DA48F00B83746 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 8B14D80E1E7DA5C900B83746 /* kaleo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = kaleo.entitlements; sourceTree = ""; }; 30 | 8B14D80F1E7DA5F400B83746 /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; 31 | 8B14D8111E7DA5FA00B83746 /* CallKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CallKit.framework; path = System/Library/Frameworks/CallKit.framework; sourceTree = SDKROOT; }; 32 | 8B14D8191E7F9BA400B83746 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 33 | 971D7D3B5B104F934A9FA892 /* Pods_kaleo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_kaleo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 8B14D7F61E7DA48F00B83746 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | 8B14D8121E7DA5FA00B83746 /* CallKit.framework in Frameworks */, 42 | 8B14D8101E7DA5F400B83746 /* PushKit.framework in Frameworks */, 43 | 5799913434DA347341225C4B /* Pods_kaleo.framework in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 0A5C8EE59937A51FC9533327 /* Pods */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 02132B4EC71358845E4B0E2E /* Pods-kaleo.debug.xcconfig */, 54 | 5A36FA55288F67CC6FABE375 /* Pods-kaleo.release.xcconfig */, 55 | ); 56 | name = Pods; 57 | sourceTree = ""; 58 | }; 59 | 8B14D7F01E7DA48E00B83746 = { 60 | isa = PBXGroup; 61 | children = ( 62 | 8B14D7FB1E7DA48F00B83746 /* kaleo */, 63 | 8B14D7FA1E7DA48F00B83746 /* Products */, 64 | 0A5C8EE59937A51FC9533327 /* Pods */, 65 | AC7A7014C658649182980FA8 /* Frameworks */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | 8B14D7FA1E7DA48F00B83746 /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 8B14D7F91E7DA48F00B83746 /* kaleo.app */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | 8B14D7FB1E7DA48F00B83746 /* kaleo */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 8B14D80E1E7DA5C900B83746 /* kaleo.entitlements */, 81 | 8B14D7FC1E7DA48F00B83746 /* AppDelegate.swift */, 82 | 8B14D8191E7F9BA400B83746 /* ViewController.swift */, 83 | 8B14D8001E7DA48F00B83746 /* Main.storyboard */, 84 | 8B14D8031E7DA48F00B83746 /* Assets.xcassets */, 85 | 8B14D8051E7DA48F00B83746 /* LaunchScreen.storyboard */, 86 | 8B14D8081E7DA48F00B83746 /* Info.plist */, 87 | ); 88 | path = kaleo; 89 | sourceTree = ""; 90 | }; 91 | AC7A7014C658649182980FA8 /* Frameworks */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 8B14D8111E7DA5FA00B83746 /* CallKit.framework */, 95 | 8B14D80F1E7DA5F400B83746 /* PushKit.framework */, 96 | 971D7D3B5B104F934A9FA892 /* Pods_kaleo.framework */, 97 | ); 98 | name = Frameworks; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 8B14D7F81E7DA48F00B83746 /* kaleo */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 8B14D80B1E7DA48F00B83746 /* Build configuration list for PBXNativeTarget "kaleo" */; 107 | buildPhases = ( 108 | 593B67043DF131AA431F9E04 /* [CP] Check Pods Manifest.lock */, 109 | 8B14D7F51E7DA48F00B83746 /* Sources */, 110 | 8B14D7F61E7DA48F00B83746 /* Frameworks */, 111 | 8B14D7F71E7DA48F00B83746 /* Resources */, 112 | 33865604667A2D8D3AD88B69 /* [CP] Embed Pods Frameworks */, 113 | 5DF8259664C387375E2DC096 /* [CP] Copy Pods Resources */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = kaleo; 120 | productName = kaleo; 121 | productReference = 8B14D7F91E7DA48F00B83746 /* kaleo.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 8B14D7F11E7DA48E00B83746 /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastSwiftUpdateCheck = 0820; 131 | LastUpgradeCheck = 0820; 132 | ORGANIZATIONNAME = "Poetic Systems"; 133 | TargetAttributes = { 134 | 8B14D7F81E7DA48F00B83746 = { 135 | CreatedOnToolsVersion = 8.2.1; 136 | DevelopmentTeam = M37WQS4429; 137 | ProvisioningStyle = Automatic; 138 | SystemCapabilities = { 139 | com.apple.BackgroundModes = { 140 | enabled = 1; 141 | }; 142 | com.apple.Push = { 143 | enabled = 1; 144 | }; 145 | }; 146 | }; 147 | }; 148 | }; 149 | buildConfigurationList = 8B14D7F41E7DA48E00B83746 /* Build configuration list for PBXProject "kaleo" */; 150 | compatibilityVersion = "Xcode 3.2"; 151 | developmentRegion = English; 152 | hasScannedForEncodings = 0; 153 | knownRegions = ( 154 | en, 155 | Base, 156 | ); 157 | mainGroup = 8B14D7F01E7DA48E00B83746; 158 | productRefGroup = 8B14D7FA1E7DA48F00B83746 /* Products */; 159 | projectDirPath = ""; 160 | projectRoot = ""; 161 | targets = ( 162 | 8B14D7F81E7DA48F00B83746 /* kaleo */, 163 | ); 164 | }; 165 | /* End PBXProject section */ 166 | 167 | /* Begin PBXResourcesBuildPhase section */ 168 | 8B14D7F71E7DA48F00B83746 /* Resources */ = { 169 | isa = PBXResourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 8B14D8071E7DA48F00B83746 /* LaunchScreen.storyboard in Resources */, 173 | 8B14D8041E7DA48F00B83746 /* Assets.xcassets in Resources */, 174 | 8B14D8021E7DA48F00B83746 /* Main.storyboard in Resources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXResourcesBuildPhase section */ 179 | 180 | /* Begin PBXShellScriptBuildPhase section */ 181 | 33865604667A2D8D3AD88B69 /* [CP] Embed Pods Frameworks */ = { 182 | isa = PBXShellScriptBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | ); 186 | inputPaths = ( 187 | ); 188 | name = "[CP] Embed Pods Frameworks"; 189 | outputPaths = ( 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | shellPath = /bin/sh; 193 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-kaleo/Pods-kaleo-frameworks.sh\"\n"; 194 | showEnvVarsInLog = 0; 195 | }; 196 | 593B67043DF131AA431F9E04 /* [CP] Check Pods Manifest.lock */ = { 197 | isa = PBXShellScriptBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | inputPaths = ( 202 | ); 203 | name = "[CP] Check Pods Manifest.lock"; 204 | outputPaths = ( 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | shellPath = /bin/sh; 208 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 209 | showEnvVarsInLog = 0; 210 | }; 211 | 5DF8259664C387375E2DC096 /* [CP] Copy Pods Resources */ = { 212 | isa = PBXShellScriptBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | inputPaths = ( 217 | ); 218 | name = "[CP] Copy Pods Resources"; 219 | outputPaths = ( 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | shellPath = /bin/sh; 223 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-kaleo/Pods-kaleo-resources.sh\"\n"; 224 | showEnvVarsInLog = 0; 225 | }; 226 | /* End PBXShellScriptBuildPhase section */ 227 | 228 | /* Begin PBXSourcesBuildPhase section */ 229 | 8B14D7F51E7DA48F00B83746 /* Sources */ = { 230 | isa = PBXSourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 8B14D81A1E7F9BA400B83746 /* ViewController.swift in Sources */, 234 | 8B14D7FD1E7DA48F00B83746 /* AppDelegate.swift in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXVariantGroup section */ 241 | 8B14D8001E7DA48F00B83746 /* Main.storyboard */ = { 242 | isa = PBXVariantGroup; 243 | children = ( 244 | 8B14D8011E7DA48F00B83746 /* Base */, 245 | ); 246 | name = Main.storyboard; 247 | sourceTree = ""; 248 | }; 249 | 8B14D8051E7DA48F00B83746 /* LaunchScreen.storyboard */ = { 250 | isa = PBXVariantGroup; 251 | children = ( 252 | 8B14D8061E7DA48F00B83746 /* Base */, 253 | ); 254 | name = LaunchScreen.storyboard; 255 | sourceTree = ""; 256 | }; 257 | /* End PBXVariantGroup section */ 258 | 259 | /* Begin XCBuildConfiguration section */ 260 | 8B14D8091E7DA48F00B83746 /* Debug */ = { 261 | isa = XCBuildConfiguration; 262 | buildSettings = { 263 | ALWAYS_SEARCH_USER_PATHS = NO; 264 | CLANG_ANALYZER_NONNULL = YES; 265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 266 | CLANG_CXX_LIBRARY = "libc++"; 267 | CLANG_ENABLE_MODULES = YES; 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 279 | CLANG_WARN_UNREACHABLE_CODE = YES; 280 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 281 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 282 | COPY_PHASE_STRIP = NO; 283 | DEBUG_INFORMATION_FORMAT = dwarf; 284 | ENABLE_STRICT_OBJC_MSGSEND = YES; 285 | ENABLE_TESTABILITY = YES; 286 | GCC_C_LANGUAGE_STANDARD = gnu99; 287 | GCC_DYNAMIC_NO_PIC = NO; 288 | GCC_NO_COMMON_BLOCKS = YES; 289 | GCC_OPTIMIZATION_LEVEL = 0; 290 | GCC_PREPROCESSOR_DEFINITIONS = ( 291 | "DEBUG=1", 292 | "$(inherited)", 293 | ); 294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 298 | GCC_WARN_UNUSED_FUNCTION = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 301 | MTL_ENABLE_DEBUG_INFO = YES; 302 | ONLY_ACTIVE_ARCH = YES; 303 | SDKROOT = iphoneos; 304 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 305 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 306 | }; 307 | name = Debug; 308 | }; 309 | 8B14D80A1E7DA48F00B83746 /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_WARN_BOOL_CONVERSION = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 347 | VALIDATE_PRODUCT = YES; 348 | }; 349 | name = Release; 350 | }; 351 | 8B14D80C1E7DA48F00B83746 /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | baseConfigurationReference = 02132B4EC71358845E4B0E2E /* Pods-kaleo.debug.xcconfig */; 354 | buildSettings = { 355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 356 | CODE_SIGN_ENTITLEMENTS = kaleo/kaleo.entitlements; 357 | DEVELOPMENT_TEAM = M37WQS4429; 358 | INFOPLIST_FILE = kaleo/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 360 | PRODUCT_BUNDLE_IDENTIFIER = com.poeticsystems.kaleo; 361 | PRODUCT_NAME = "$(TARGET_NAME)"; 362 | SWIFT_VERSION = 3.0; 363 | }; 364 | name = Debug; 365 | }; 366 | 8B14D80D1E7DA48F00B83746 /* Release */ = { 367 | isa = XCBuildConfiguration; 368 | baseConfigurationReference = 5A36FA55288F67CC6FABE375 /* Pods-kaleo.release.xcconfig */; 369 | buildSettings = { 370 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 371 | CODE_SIGN_ENTITLEMENTS = kaleo/kaleo.entitlements; 372 | DEVELOPMENT_TEAM = M37WQS4429; 373 | INFOPLIST_FILE = kaleo/Info.plist; 374 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 375 | PRODUCT_BUNDLE_IDENTIFIER = com.poeticsystems.kaleo; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | SWIFT_VERSION = 3.0; 378 | }; 379 | name = Release; 380 | }; 381 | /* End XCBuildConfiguration section */ 382 | 383 | /* Begin XCConfigurationList section */ 384 | 8B14D7F41E7DA48E00B83746 /* Build configuration list for PBXProject "kaleo" */ = { 385 | isa = XCConfigurationList; 386 | buildConfigurations = ( 387 | 8B14D8091E7DA48F00B83746 /* Debug */, 388 | 8B14D80A1E7DA48F00B83746 /* Release */, 389 | ); 390 | defaultConfigurationIsVisible = 0; 391 | defaultConfigurationName = Release; 392 | }; 393 | 8B14D80B1E7DA48F00B83746 /* Build configuration list for PBXNativeTarget "kaleo" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | 8B14D80C1E7DA48F00B83746 /* Debug */, 397 | 8B14D80D1E7DA48F00B83746 /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | /* End XCConfigurationList section */ 403 | }; 404 | rootObject = 8B14D7F11E7DA48E00B83746 /* Project object */; 405 | } 406 | -------------------------------------------------------------------------------- /kaleo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // 4 | // Created by Chuong Le on 3/20/17. 5 | // Copyright © 2017 Poetic Systems. All rights reserved. 6 | // 7 | 8 | 9 | import UIKit 10 | import AVFoundation 11 | import PushKit 12 | import CallKit 13 | import TwilioVoiceClient 14 | 15 | let baseURLString = "https://411db595.ngrok.io" 16 | let accessTokenEndpoint = "/access-token" 17 | 18 | class ViewController: UIViewController, PKPushRegistryDelegate, TVONotificationDelegate, TVOCallDelegate, CXProviderDelegate { 19 | 20 | @IBOutlet weak var placeCallButton: UIButton! 21 | @IBOutlet weak var hangupButton: UIButton! 22 | @IBOutlet weak var speakerButton: UIButton! 23 | @IBOutlet weak var iconView: UIImageView! 24 | 25 | // Get Device info 26 | var device_uuid:String? 27 | 28 | var deviceTokenString:String? 29 | 30 | var voipRegistry:PKPushRegistry 31 | 32 | var isSpinning: Bool 33 | var incomingAlertController: UIAlertController? 34 | 35 | var callInvite:TVOCallInvite? 36 | var call:TVOCall? 37 | 38 | let callKitProvider:CXProvider 39 | let callKitCallController:CXCallController 40 | 41 | required init?(coder aDecoder: NSCoder) { 42 | 43 | device_uuid = UIDevice.current.identifierForVendor?.uuidString 44 | 45 | isSpinning = false 46 | // register push notification using PushKit 47 | voipRegistry = PKPushRegistry.init(queue: DispatchQueue.main) 48 | 49 | // The VoiceClient is the entry point for interaction with the Twilio service. 50 | // sharedInstance return the shared instance of the VoiceClient 51 | // set logging level of SDK 52 | VoiceClient.sharedInstance().logLevel = .verbose 53 | 54 | // A CXProvider​Configuration object controls the native call UI for incoming and outgoing calls, 55 | // including a localized name for the provider, the ringtone to be played for incoming calls, 56 | // and the icon to be displayed during calls. 57 | // A provider configuration can also set the maximum number of call groups and number of calls in a single call group, 58 | // determine whether to use emails and/or phone numbers as handles, and specify whether video is supported. 59 | let configuration = CXProviderConfiguration(localizedName: "Kaleo") 60 | configuration.maximumCallGroups = 1 61 | configuration.maximumCallsPerCallGroup = 1 62 | if let callKitIcon = UIImage(named: "iconMask80") { 63 | configuration.iconTemplateImageData = UIImagePNGRepresentation(callKitIcon) 64 | } 65 | 66 | // A CXProvider object is responsible for reporting out-of-band notifications that occur to the system. 67 | // A VoIP app should create only one instance of CXProvider and store it for use globally. 68 | callKitProvider = CXProvider(configuration: configuration) 69 | 70 | // A CXCall​Controller object interacts with calls by performing actions, which are represented by instances of CXCall​Action subclasses. 71 | callKitCallController = CXCallController() 72 | 73 | // http://stackoverflow.com/questions/24036393/fatal-error-use-of-unimplemented-initializer-initcoder-for-class 74 | super.init(coder: aDecoder) 75 | callKitProvider.setDelegate(self, queue: nil) 76 | 77 | voipRegistry.delegate = self 78 | voipRegistry.desiredPushTypes = Set([PKPushType.voIP]) 79 | } 80 | 81 | // override func viewDidLoad() { 82 | // super.viewDidLoad() 83 | 84 | // // toggleUIState(isEnabled: true) 85 | // } 86 | 87 | override func didReceiveMemoryWarning() { 88 | super.didReceiveMemoryWarning() 89 | } 90 | 91 | // fetching access token via Twilio Rest API 92 | func fetchAccessToken() -> String? { 93 | // https://ericcerney.com/swift-guard-statement/ 94 | 95 | // guard let accessTokenURL = URL(string: baseURLString + accessTokenEndpoint + "?device_uuid=" + device_uuid!) else { 96 | // return nil 97 | // } 98 | guard let accessTokenURL = URL(string: baseURLString + accessTokenEndpoint) else { 99 | return nil 100 | } 101 | 102 | // http://stackoverflow.com/questions/32390611/try-try-try-what-s-the-difference-and-when-to-use-each 103 | return try? String.init(contentsOf: accessTokenURL, encoding: .utf8) 104 | } 105 | 106 | // func toggleUIState(isEnabled: Bool) { 107 | // placeCallButton.isEnabled = isEnabled 108 | // } 109 | 110 | // @IBAction func placeCall(_ sender: UIButton) { 111 | // if (self.call != nil && self.call?.state == .connected) { 112 | // self.call?.disconnect() 113 | // self.toggleUIState(isEnabled: false) 114 | // } else { 115 | // let uuid = UUID() 116 | // let handle = "Voice Bot" 117 | 118 | // performStartCallAction(uuid: uuid, handle: handle) 119 | // } 120 | // } 121 | 122 | @IBAction func hangupCall(_ sender: UIButton) { 123 | if (self.call != nil && self.call?.state == .connected) { 124 | self.call?.disconnect() 125 | } 126 | } 127 | 128 | @IBAction func speakerOn(_ sender: UIButton) { 129 | if (self.call != nil && self.call?.state == .connected) { 130 | routeAudioToSpeaker() 131 | } 132 | } 133 | 134 | 135 | // MARK: PKPushRegistryDelegate 136 | // A push registry delegate uses the methods of this protocol to react to token invalidation, push credential updates, and received remote pushes. 137 | // https://developer.apple.com/reference/pushkit/pkpushregistrydelegate 138 | 139 | // Notifies the delegate when the push credentials have been updated. 140 | func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, forType type: PKPushType) { 141 | NSLog("pushRegistry:didUpdatePushCredentials:forType:") 142 | 143 | if (type != .voIP) { 144 | return 145 | } 146 | 147 | guard let accessToken = fetchAccessToken() else { 148 | return 149 | } 150 | 151 | let deviceToken = (credentials.token as NSData).description 152 | 153 | VoiceClient.sharedInstance().register(withAccessToken: accessToken, deviceToken: deviceToken) { (error) in 154 | if (error != nil) { 155 | NSLog("An error occurred while registering: \(error?.localizedDescription)") 156 | } 157 | else { 158 | NSLog("Successfully registered for VoIP push notifications.") 159 | } 160 | } 161 | 162 | self.deviceTokenString = deviceToken 163 | } 164 | 165 | // Notifies the delegate that a push token has been invalidated. 166 | func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenForType type: PKPushType) { 167 | NSLog("pushRegistry:didInvalidatePushTokenForType:") 168 | 169 | if (type != .voIP) { 170 | return 171 | } 172 | 173 | guard let deviceToken = deviceTokenString, let accessToken = fetchAccessToken() else { 174 | return 175 | } 176 | 177 | VoiceClient.sharedInstance().unregister(withAccessToken: accessToken, deviceToken: deviceToken) { (error) in 178 | if (error != nil) { 179 | NSLog("An error occurred while unregistering: \(error?.localizedDescription)") 180 | } 181 | else { 182 | NSLog("Successfully unregistered from VoIP push notifications.") 183 | } 184 | } 185 | 186 | self.deviceTokenString = nil 187 | } 188 | 189 | // Notifies the delegate that a remote push has been received 190 | func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) { 191 | NSLog("pushRegistry:didReceiveIncomingPushWithPayload:forType:") 192 | 193 | if (type == PKPushType.voIP) { 194 | VoiceClient.sharedInstance().handleNotification(payload.dictionaryPayload, delegate: self) 195 | } 196 | } 197 | 198 | //http://stackoverflow.com/questions/5325226/what-is-the-difference-between-delegate-and-notification 199 | 200 | // MARK: TVONotificaitonDelegate 201 | 202 | // Notifies the delegate that an incoming call invite has been received. This comes from Twilio TVONotificaitonDelegate. 203 | func callInviteReceived(_ callInvite: TVOCallInvite) { 204 | NSLog("callInviteReceived:") 205 | 206 | if (self.callInvite != nil && self.callInvite?.state == .pending) { 207 | NSLog("Already a pending incoming call invite."); 208 | NSLog(" >> Ignoring call from %@", callInvite.from); 209 | return; 210 | } else if (self.call != nil) { 211 | NSLog("Already an active call."); 212 | NSLog(" >> Ignoring call from %@", callInvite.from); 213 | return; 214 | } 215 | 216 | self.callInvite = callInvite 217 | 218 | reportIncomingCall(from: callInvite.from, uuid: callInvite.uuid) 219 | } 220 | 221 | func callInviteCancelled(_ callInvite: TVOCallInvite?) { 222 | NSLog("callInviteCancelled:") 223 | 224 | if let callInvite = callInvite { 225 | performEndCallAction(uuid: callInvite.uuid) 226 | } 227 | 228 | self.callInvite = nil 229 | } 230 | 231 | func notificationError(_ error: Error) { 232 | NSLog("notificationError: \(error.localizedDescription)") 233 | } 234 | 235 | 236 | // MARK: TVOCallDelegate 237 | func callDidConnect(_ call: TVOCall) { 238 | NSLog("callDidConnect:") 239 | 240 | self.call = call 241 | 242 | // self.placeCallButton.setTitle("Hang Up", for: .normal) 243 | 244 | // toggleUIState(isEnabled: true) 245 | // stopSpin() 246 | // routeAudioToSpeaker() 247 | } 248 | 249 | func callDidDisconnect(_ call: TVOCall) { 250 | NSLog("callDidDisconnect:") 251 | 252 | performEndCallAction(uuid: call.uuid) 253 | 254 | self.call = nil 255 | 256 | // self.placeCallButton.setTitle("Place Outgoing Call", for: .normal) 257 | 258 | // toggleUIState(isEnabled: true) 259 | } 260 | 261 | func call(_ call: TVOCall, didFailWithError error: Error) { 262 | NSLog("call:didFailWithError: \(error.localizedDescription)") 263 | 264 | performEndCallAction(uuid: call.uuid) 265 | 266 | self.call = nil 267 | // toggleUIState(isEnabled: true) 268 | // stopSpin() 269 | } 270 | 271 | 272 | // MARK: AVAudioSession 273 | func routeAudioToSpeaker() { 274 | do { 275 | try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker) 276 | } catch { 277 | NSLog(error.localizedDescription) 278 | } 279 | } 280 | 281 | 282 | // MARK: Icon spinning 283 | // func startSpin() { 284 | // if (isSpinning != true) { 285 | // isSpinning = true 286 | // spin(options: UIViewAnimationOptions.curveEaseIn) 287 | // } 288 | // } 289 | 290 | // func stopSpin() { 291 | // isSpinning = false 292 | // } 293 | 294 | // func spin(options: UIViewAnimationOptions) { 295 | // UIView.animate(withDuration: 0.5, 296 | // delay: 0.0, 297 | // options: options, 298 | // animations: { [weak iconView] in 299 | // if let iconView = iconView { 300 | // iconView.transform = iconView.transform.rotated(by: CGFloat(M_PI/2)) 301 | // } 302 | // }) { [weak self] (finished: Bool) in 303 | // guard let strongSelf = self else { 304 | // return 305 | // } 306 | 307 | // if (finished) { 308 | // if (strongSelf.isSpinning) { 309 | // strongSelf.spin(options: UIViewAnimationOptions.curveLinear) 310 | // } else if (options != UIViewAnimationOptions.curveEaseOut) { 311 | // strongSelf.spin(options: UIViewAnimationOptions.curveEaseOut) 312 | // } 313 | // } 314 | // } 315 | // } 316 | 317 | 318 | // MARK: CXProviderDelegate 319 | func providerDidReset(_ provider: CXProvider) { 320 | NSLog("providerDidReset:") 321 | } 322 | 323 | func providerDidBegin(_ provider: CXProvider) { 324 | NSLog("providerDidBegin") 325 | } 326 | 327 | func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { 328 | NSLog("provider:didActivateAudioSession:") 329 | 330 | VoiceClient.sharedInstance().startAudioDevice() 331 | } 332 | 333 | func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { 334 | NSLog("provider:didDeactivateAudioSession:") 335 | } 336 | 337 | func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) { 338 | NSLog("provider:timedOutPerformingAction:") 339 | } 340 | 341 | // func provider(_ provider: CXProvider, perform action: CXStartCallAction) { 342 | // NSLog("provider:performStartCallAction:") 343 | 344 | // guard let accessToken = fetchAccessToken() else { 345 | // action.fail() 346 | // return 347 | // } 348 | 349 | // VoiceClient.sharedInstance().configureAudioSession() 350 | 351 | // call = VoiceClient.sharedInstance().call(accessToken, params: [:], delegate: self) 352 | 353 | // guard let call = call else { 354 | // NSLog("Failed to start outgoing call") 355 | // action.fail() 356 | // return 357 | // } 358 | 359 | // call.uuid = action.callUUID 360 | 361 | // toggleUIState(isEnabled: false) 362 | // startSpin() 363 | 364 | // action.fulfill(withDateStarted: Date()) 365 | // } 366 | 367 | func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { 368 | NSLog("provider:performAnswerCallAction:") 369 | 370 | // RCP: Workaround from https://forums.developer.apple.com/message/169511 suggests configuring audio in the 371 | // completion block of the `reportNewIncomingCallWithUUID:update:completion:` method instead of in 372 | // `provider:performAnswerCallAction:` per the WWDC examples. 373 | // VoiceClient.sharedInstance().configureAudioSession() 374 | 375 | guard let call = self.callInvite?.accept(with: self) else { 376 | action.fail() 377 | return 378 | } 379 | 380 | self.callInvite = nil 381 | 382 | call.uuid = action.callUUID 383 | action.fulfill() 384 | } 385 | 386 | func provider(_ provider: CXProvider, perform action: CXEndCallAction) { 387 | NSLog("provider:performEndCallAction:") 388 | 389 | VoiceClient.sharedInstance().stopAudioDevice() 390 | 391 | if (self.callInvite != nil && self.callInvite?.state == .pending) { 392 | self.callInvite?.reject() 393 | self.callInvite = nil 394 | } else if (self.call != nil) { 395 | self.call?.disconnect() 396 | } 397 | 398 | action.fulfill() 399 | } 400 | 401 | // // MARK: Call Kit Actions 402 | // func performStartCallAction(uuid: UUID, handle: String) { 403 | // let callHandle = CXHandle(type: .generic, value: handle) 404 | // let startCallAction = CXStartCallAction(call: uuid, handle: callHandle) 405 | // let transaction = CXTransaction(action: startCallAction) 406 | 407 | // callKitCallController.request(transaction) { error in 408 | // if let error = error { 409 | // NSLog("StartCallAction transaction request failed: \(error.localizedDescription)") 410 | // return 411 | // } 412 | 413 | // NSLog("StartCallAction transaction request successful") 414 | 415 | // let callUpdate = CXCallUpdate() 416 | // callUpdate.remoteHandle = callHandle 417 | // callUpdate.supportsDTMF = true 418 | // callUpdate.supportsHolding = false 419 | // callUpdate.supportsGrouping = false 420 | // callUpdate.supportsUngrouping = false 421 | // callUpdate.hasVideo = false 422 | 423 | // self.callKitProvider.reportCall(with: uuid, updated: callUpdate) 424 | // } 425 | // } 426 | 427 | func reportIncomingCall(from: String, uuid: UUID) { 428 | let callHandle = CXHandle(type: .generic, value: from) 429 | 430 | let callUpdate = CXCallUpdate() 431 | callUpdate.remoteHandle = callHandle 432 | callUpdate.supportsDTMF = true 433 | callUpdate.supportsHolding = false 434 | callUpdate.supportsGrouping = false 435 | callUpdate.supportsUngrouping = false 436 | callUpdate.hasVideo = false 437 | 438 | callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in 439 | if let error = error { 440 | NSLog("Failed to report incoming call successfully: \(error.localizedDescription).") 441 | return 442 | } 443 | 444 | NSLog("Incoming call successfully reported.") 445 | 446 | // RCP: Workaround per https://forums.developer.apple.com/message/169511 447 | VoiceClient.sharedInstance().configureAudioSession() 448 | } 449 | } 450 | 451 | func performEndCallAction(uuid: UUID) { 452 | 453 | let endCallAction = CXEndCallAction(call: uuid) 454 | let transaction = CXTransaction(action: endCallAction) 455 | 456 | callKitCallController.request(transaction) { error in 457 | if let error = error { 458 | NSLog("EndCallAction transaction request failed: \(error.localizedDescription).") 459 | return 460 | } 461 | 462 | NSLog("EndCallAction transaction request successful") 463 | } 464 | } 465 | } 466 | 467 | --------------------------------------------------------------------------------