├── .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 CXProviderConfiguration 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 CXCallController object interacts with calls by performing actions, which are represented by instances of CXCallAction 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 |
--------------------------------------------------------------------------------