ios pushkit end last call before reportnewIncommingCall - swift

im stuck in how to ending all the last call before report a new incoming call, my function work for 2 call, it mean i can end the first call before report new call
But the problem is after next report, the end call function throw error
Here is my code:
// Handle incoming pushes
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: #escaping () -> Void) {
// print("Handle incoming pushes: \(payload.dictionaryPayload)")
endCall(thenReportNewCallForUuid: UUID.init())
}
func reportNewCall(uuid : UUID){
let config = CXProviderConfiguration(localizedName: "CallKitExample")
config.includesCallsInRecents = true
config.supportsVideo = true
config.supportedHandleTypes = [.generic]
config.iconTemplateImageData = UIImage(named: "logo_square")!.pngData()
config.maximumCallGroups = 1
config.maximumCallsPerCallGroup = 1
let provider = CXProvider(configuration: config)
provider.setDelegate(self, queue: nil)
let update = CXCallUpdate()
update.supportsHolding = false
update.supportsGrouping = false
update.supportsUngrouping = false
update.remoteHandle = CXHandle(type: .generic, value: uuid.uuidString)
update.hasVideo = true
provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in
print("reportNewIncomingCall \(uuid) error: \(error)")
UserDefaults.standard.set(uuid.uuidString, forKey: "CallUUID")
UserDefaults.standard.synchronize()
})
}
func endCall(thenReportNewCallForUuid : UUID) {
guard let lastCallUUIDString = UserDefaults.standard.string(forKey: "CallUUID"), !lastCallUUIDString.isEmpty else{
return
}
print("end uuid: \(lastCallUUIDString)")
let call = UUID.init(uuidString: lastCallUUIDString)!
let controller = CXCallController()
let endTransaction = CXEndCallAction(call: call)
let transaction = CXTransaction(action: endTransaction)
controller.request(transaction, completion: { error in
if let error = error {
print("endcall Error: \(error)")
self.reportNewCall(uuid: thenReportNewCallForUuid)
} else {
print("endcall Success")
self.reportNewCall(uuid: thenReportNewCallForUuid)
}
})
}
Here is log + error i got
end uuid: CB91CCC6-7FCD-49D3-BE93-7A6581295B57
endcall Error: Error Domain=com.apple.CallKit.error.requesttransaction Code=2 "(null)"
-> OK first time endcall error because no call
reportNewIncomingCall 202DB031-23AE-46B6-91E9-3FBA708E07A7 error: nil
end uuid: 202DB031-23AE-46B6-91E9-3FBA708E07A7
endcall Success -> Matched call to end -> success
reportNewIncomingCall C45FEC0B-1320-4357-ADEF-7B7CA28D96C8 error: nil
end uuid: C45FEC0B-1320-4357-ADEF-7B7CA28D96C8
endcall Error: Error Domain=com.apple.CallKit.error.requesttransaction Code=4 "(null)"
-> Matched call to end -> FAILED
reportNewIncomingCall CBDBA75A-B263-49E5-9138-8D5CCA28ED9E error: nil
Some one who mark duplicate please show the right answer? Thanks
Does someone facing same problem? Please help

I see at least a couple of issues in your code. But, before that, why are you ending the ongoing call whenever you receive a new incoming call? I'm just curious, because it doesn't seem to be a great user experience.
Anyway, the issues I've found are the following:
At every new incoming call you instantiate a new CXProvider. As stated in the documentation:
A VoIP app should create only one instance of CXProvider and store it for use globally.
You don't invoke the completion handler of the pushRegistry(_:didReceiveIncomingPushWith:type:completion) method. You should invoke it inside the completion handler of the reportNewIncomingCall(with:update:completion:) method.
I think that the errors you're facing are caused by the CXProvider issue. But if you don't fix also the second issue you could incur in another problem: the system will suppose that you haven't reported a new incoming call and so, after a few calls, it will stop to send you new VoIP pushes (this limitation was first introduced in iOS 13).

i've had the same issue and fixed it by moving the instantiation of CXProvider, CXProfiderConfiguration and CXCallController outside of pushRegistry function and inside didFinishLaunchingWithOptions part of AppDelegate, something like this:
private func voipRegistration() {
// Create a push registry object
let mainQueue = DispatchQueue.main
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [PKPushType.voIP]
config.iconTemplateImageData = #imageLiteral(resourceName: "cometchat_white").pngData()
config.includesCallsInRecents = false
config.ringtoneSound = "ringtone.caf"
config.supportsVideo = false
provider = CXProvider.init(configuration: config)
provider!.setDelegate(self, queue: nil)
}
You have to have a single instance of CXProvider.

Related

Unable to close CallKit UI after VOIP Call has ended

After I received an incoming voip push in the AppDelegate, I reported the incoming call using the method below:
func showIncomingCall(of session: String) {
let callUpdate = CXCallUpdate()
callUpdate.remoteHandle = CXHandle(type: .phoneNumber, value: session)
callUpdate.localizedCallerName = getMemberName(phone: session)
if voiceCallBool {
callUpdate.hasVideo = false
}
if videoCallBool {
callUpdate.hasVideo = true
}
callUpdate.supportsDTMF = false
currentCall = pairedUUID(of: session)
provider.reportNewIncomingCall(with: currentCall!, update: callUpdate, completion: { error in
if let error = error {
print("reportNewIncomingCall error: \(error.localizedDescription)")
}
})
}
After I answered the call, the call was connected. And both the callee and caller is able to have a conversation. After the call, the caller ended the call. This was detected at the callee's end and the following endCall method was called:
func endCall() {
let endCallAction = CXEndCallAction.init(call: currentCall!)
let transaction = CXTransaction.init()
transaction.addAction(endCallAction)
controller.request(transaction) { error in
if let error = error {
print("endSession failed: \(error.localizedDescription)")
}
}
}
The CallKit UI failed to dismiss with the error message: "The operation couldn’t be completed. (com.apple.CallKit.error.requesttransaction error 4.)". Why is this so, even though the same uuid (CurrentCall) is used for reporting the incoming call and for ending the same call? Please help. Thanks.

Why would the Swift Movesense-API Subscription Callback Operation Be Nil?

I'm building a Swift integration with Movesense's api as found here: https://bitbucket.org/suunto/movesense-mobile-lib/src/master/IOS/
Following the examples given in the Movesense Showcase application, I've subscribed to the heart rate notifications:
let request = MovesenseRequest(resourceType: .heartRate, method: .subscribe,
parameters: nil)
let operation = device.sendRequest(request, observer: self)
I'm positive that my device is connected and working because I can retrieve energy and temperature information using .get methods without any problem.
The problem is that I don't receive any heart rate responses from the Movesense API. Stepping through the api, my I get stuck in the send request method where both the onCompletion and onEvent callbacks have an operation that is nil, so it dies without sending a response to my app.
internal func sendRequest(_ request: MovesenseRequest, serial: String,
observer: Observer) -> MovesenseOperation? {
guard let mds = self.mdsWrapper,
let jsonDecoder = self.jsonDecoder else {
let error = MovesenseError.integrityError("MovesenseConnection::sendRequest error.")
delegate?.onConnectionError(error)
return nil
}
let resourcePath = "\(serial)/\(request.path)"
let onCancel = {
switch request.method {
case .subscribe: mds.doUnsubscribe(resourcePath)
default: return
}
}
let operation = MovesenseOperationFactory.create(request: request,
observer: observer,
jsonDecoder: jsonDecoder,
onCancel: onCancel)
// Decode response with the MovesenseOperation instance
let onCompletion = { [connectionQueue, weak operation] (_ response: MDSResponse) in
dump(response)
guard let operation = operation else { return }
connectionQueue.async {
operation.handleResponse(status: response.statusCode, header: response.header,
data: response.bodyData)
}
}
switch request.method {
case .get: mds.doGet(resourcePath, contract: request.contract, completion: onCompletion)
case .put: mds.doPut(resourcePath, contract: request.contract, completion: onCompletion)
case .post: mds.doPost(resourcePath, contract: request.contract, completion: onCompletion)
case .del: mds.doDelete(resourcePath, contract: request.contract, completion: onCompletion)
case .unsubscribe: mds.doUnsubscribe(resourcePath)
case .subscribe:
let onEvent = { [connectionQueue, weak operation] (_ event: MDSEvent) in
dump(event)
guard let operation = operation else {
mds.doUnsubscribe(resourcePath)
return
}
connectionQueue.async {
operation.handleEvent(header: event.header,
data: event.bodyData)
}
}
mds.doSubscribe(resourcePath, contract: request.contract,
response: onCompletion, onEvent: onEvent)
}
return operation
}
I know the API works because the Movesense Showcase code in the repository works just fine, but I don't know what I missed in setting up the request so the operation wouldn't be nil every single time. What might cause that error?
I eventually found my own answer. The problem was with the weak reference. I wasn’t storing that operation anywhere, so when the callback was called, the operation was always nil because the reference was garbage collected. The solution was to store the operation returned by the subscribe request somewhere and then remove the reference when you unsubscribe. I personally used a map to store my references, but any method to preserve the reference would be fine.

Can't get data returned from dataTask()

For one week I have been trying to get a string returned from dataTask().
I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.
So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:
func requestOGD(code gtin: String, completion: #escaping (_ result: String) -> String) {
// MARK: Properties
var answerList: [String.SubSequence] = []
var answerDic: [String:String] = [:]
var product_name = String()
var producer = String()
// Set up the URL request
let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")
guard let url = URL(string: ogdAPI) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result, which is String. It willbecome split and placed in a dictionary
do {
let answer = (String(decoding: responseData, as: UTF8.self))
answerList = answer.split(separator: "\n")
for entry in answerList {
let entry1 = entry.split(separator: "=")
if entry1.count > 1 {
let foo = String(entry1[0])
let bar = String(entry1[1])
answerDic[foo] = "\(bar)"
}
}
if answerDic["error"] == "0" {
product_name = answerDic["detailname"]!
producer = answerDic["vendor"]!
completion(product_name)
} else {
print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
return
}
}
}
task.resume()
Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:
// Configure the cell...
var foo:String = ""
requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
print(result)
foo = result
return result
}
print("Foo:", foo)
cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""
return cell
}
So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.
You used a completion handler in your call to requestOGD:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
// result comes back here
}
But then you tried to capture and return that result:
foo = result
return result
So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.
To put it in simple terms, this is the order of operations:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
foo = result // 2
}
print("Foo:", foo) // 1
You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.
In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.

Updating UI after retrieving device settings

I want to do something simple in Swift. I have to retrieve some setting from a device and then initialize some UI controls with those settings. It may take a few seconds to complete the retrieval so I don't want the code to continue until after the retrieval (async).
I have read countless posts on many websites including this one and read many tutorials. None seem to work for me.
Also, in the interest of encapsulation, I want to keep the details within the device object.
When I run the app I see the print from the initializing method before I see the print from the method.
// Initializing method
brightnessLevel = 100
device.WhatIsTheBrightnessLevel(level: &brightnessLevel)
print("The brightness level is \(brightnessLevel)")
// method with the data retrieval code
func WhatIsTheBrightnessLevel(level brightness: inout Int) -> CResults
{
var brightness: Int
var characteristic: HMCharacteristic
var name: String
var results: CResults
var timeout: DispatchTime
var timeoutResult: DispatchTimeoutResult
// Refresh the value by querying the lightbulb
name = m_lightBulbName
characteristic = m_brightnessCharacteristic!
brightness = 100
timeout = DispatchTime.now() + .seconds(CLightBulb.READ_VALUE_TIMEOUT)
timeoutResult = .success
results = CResults()
results.SetResult(code: CResults.code.success)
let dispatchGroup = DispatchGroup()
DispatchQueue.global(qos: .userInteractive).async
{
//let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
characteristic.readValue(completionHandler:
{ (error) in
if error != nil
{
results.SetResult(code: CResults.code.homeKitError)
results.SetHomeKitDescription(text: error!.localizedDescription)
print("Error in reading the brightness level for \(name): \(error!.localizedDescription)")
}
else
{
brightness = characteristic.value as! Int
print("CLightBulb: -->Read the brightness level. It is \(brightness) at " + Date().description(with: Locale.current))
}
dispatchGroup.leave()
})
timeoutResult = dispatchGroup.wait(timeout: timeout)
if (timeoutResult == .timedOut)
{
results.SetResult(code: CResults.code.timedOut)
}
else
{
print("CLightBulb: (After wait) The brightness level is \(brightness) at " + Date().description(with: Locale.current))
self.m_brightnessLevel = brightness
}
}
return(results)
}
Thank you!
If you're going to wrap an async function with your own function, it's generally best to give your wrapper function a completion handler as well. Notice the call to your completion handler. This is where you'd pass the resulting values (i.e. within the closure):
func getBrightness(characteristic: HMCharacteristic, completion: #escaping (Int?, Error?) -> Void) {
characteristic.readValue { (error) in
//Program flows here second
if error == nil {
completion(characteristic.value as? Int, nil)
} else {
completion(nil, error)
}
}
//Program flows here first
}
Then when you call your function, you just need to make sure that you're handling the results within the completion handler (i.e. closure):
getBrightness(characteristic: characteristic) { (value, error) in
//Program flows here second
if error == nil {
if let value = value {
print(value)
}
} else {
print("an error occurred: \(error.debugDescription)")
}
}
//Program flows here first
Always keep in mind that code will flow through before the async function completes. So you have to structure your code so that anything that's depending on the value or error returned, doesn't get executed before completion.

swift OSX: serially generating files using GCD

I am trying to generate .aiff files using NSSpeechSynthesizer.startSpeakingString() and am using GCd using a serial queue as NSSpeechSynthesizer takes in a string and creates an aiff file at a specified NSURL address. I used the standard for loop method for a list of strings in a [String:[String]] but this creates some files which have 0 bytes.
Here is the function to generate the speech:
func createSpeech(type: String, name: String) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
print("Attempting to save speech \(name).aiff")
self.synth.startSpeakingString(name, toURL: URL)
}catch{
print("error occured")
}
}
}
And here is the function that traverses the dictionary to create the files:
for key in self.nodeLibrary.keys{
dispatch_sync(GlobalBackgroundQueue){
let type = self.nodeLibrary[key]?.0
let name = key.componentsSeparatedByString("_")[0]
if !speechCheck.contains(name){
mixer.createSpeech(type!, name: name)
}
}
}
The globalBackgroundQueue is an alias to the GCD queue call _T for readability.
The routine runs fine, creates folders and subfolders as required by another external function then synthesizes the speech but in my case I always get one or some which don't load properly, giving 0 bytes or a too small number of bytes which makes the file unuseable.
I read the following post and have been using these GCD methods for a while but I'm not sure where I'm wrong here:
http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1
Any help greatly appreciated as usual
edit: Updated with completion closure and found possibly a bug
I have created a closure function as below and use it in another helper method which checks for any errors such as sourceFile.length being 0 once loaded. However, all files exhibit a 0 length which is not possible as I checked each file's audio properties using finder's property command+i.
func synthesise(type: String, name: String, completion: (success: Bool)->()) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
let success = self.synth.startSpeakingString(name, toURL: URL)
completion(success: success)
}catch{
print("error occured")
}
}
}
func loadSpeech(type: String, name: String){
synthesise(type, name: name, completion: {(success: Bool)->Void in
if success{
print("File \(name) created successfully with return \(self.synthSuccess), checking file integrity")
let URL = NSURL(fileURLWithPath: "\(self.dataPath)\(type)/\(name)/\(name).aiff")
do{
let source = try AVAudioFile(forReading: URL)
print("File has length: \(source.)")
}catch{
print("error loading file")
}
}else{
print("creation unsuccessful, trying again")
self.loadSpeech(type, name: name)
}
})
}
The files are generated with their folders and both the method startSpeakingString->Bool and the delegate function I have in my class which updates the synthSuccess property show true. So I load an AVAudioFile to check its length. All file lengths are 0. Which they are not except for one.
When I say bug, this is from another part of the app where I load an AVAudioEngine and start loading buffers with the frameCount argument set to sourceAudioFile.length which gives a diagnostic error but this is out of context right now.
startSpeakingString(_:toURL:) will start an asynchronous task in the background. Effectively, your code starts a number of asynchronous tasks that run concurrently. This may be the cause of the problem that you experience.
A solution would need to ensure that only one task is active at a time.
The problem with startSpeakingString(_:toURL:) is, that it starts an asynchronous task - but the function itself provides no means to get notified when this task is finished.
However, there's a delegate which you need to setup in order to be notified.
So, your solution will require to define a NSSpeechSynthesizerDelegate.
You may want to create your own helper class that exposes an asynchronous function which has a completion handler:
func exportSpeakingString(string: String, url: NSURL,
completion: (NSURL?, ErrorType?) -> ())
Internally, the class creates an instance of NSSpeechSynthesizer and NSSpeechSynthesizerDelegate and implements the delegate methods accordingly.
To complete the challenge, you need to search for an approach to run several asynchronous functions sequentially. There are already solutions on SO.
Edit:
I setup my own project to either confirm or neglect a possible issue in the NSSpeechSynthesizer system framework. So far, may own tests confirm that NSSpeechSynthesizer works as expected.
However, there are few subtleties worth mentioning:
Ensure you create a valid file URL which you pass as an argument to parameter URL in method startSpeakingString(:toURL:).
Ensure you choose an extension for the output file which is known by NSSpeechSynthesizer and the system frameworks playing this file, for example .aiff. Unfortunately, the documentation is quite lacking here - so I had to trial and error. The list of supported audio file formats by QuickTime may help here. Still, I have no idea how NSSpeechSynthesizer selects the output format.
The following two classes compose a simple easy to use library:
import Foundation
import AppKit
enum SpeechSynthesizerError: ErrorType {
case ErrorActive
case ErrorURL(message: String)
case ErrorUnknown
}
internal class InternalSpeechSynthesizer: NSObject, NSSpeechSynthesizerDelegate {
typealias CompletionFunc = (NSURL?, ErrorType?) -> ()
private let synthesizer = NSSpeechSynthesizer(voice: nil)!
private var _completion: CompletionFunc?
private var _url: NSURL?
override init() {
super.init()
synthesizer.delegate = self
}
// CAUTION: This call is not thread-safe! Ensure that multiple method invocations
// will be called from the same thread!
// Only _one_ task can be active at a time.
internal func synthesize(input: String, output: NSURL, completion: CompletionFunc) {
guard _completion == nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorActive)
}
return
}
guard output.path != nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorURL(message: "The URL must be a valid file URL."))
}
return
}
_completion = completion
_url = output
if !synthesizer.startSpeakingString(input, toURL: output) {
fatalError("Could not start speeaking")
}
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakWord characterRange: NSRange,
ofString string: String)
{
NSLog("willSpeakWord")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakPhoneme phonemeOpcode: Int16)
{
NSLog("willSpeakPhoneme")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didEncounterErrorAtIndex characterIndex: Int,
ofString string: String,
message: String)
{
NSLog("didEncounterErrorAtIndex")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didFinishSpeaking finishedSpeaking: Bool)
{
assert(self._url != nil)
assert(self._url!.path != nil)
assert(self._completion != nil)
var error: ErrorType?
if !finishedSpeaking {
do {
error = try self.synthesizer.objectForProperty(NSSpeechErrorsProperty) as? NSError
} catch let err {
error = err
}
}
let url: NSURL? = NSFileManager.defaultManager().fileExistsAtPath(self._url!.path!) ? self._url : nil
let completion = self._completion!
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
if url == nil && error == nil {
error = SpeechSynthesizerError.ErrorUnknown
}
completion(url, error)
}
_completion = nil
_url = nil
}
}
public struct SpeechSynthesizer {
public init() {}
private let _synthesizer = InternalSpeechSynthesizer()
public func synthesize(input: String, output: NSURL, completion: (NSURL?, ErrorType?) -> ()) {
_synthesizer.synthesize(input, output: output) { (url, error) in
completion(url, error)
}
}
}
You can use it as shown below:
func testExample() {
let expect = self.expectationWithDescription("future should be fulfilled")
let synth = SpeechSynthesizer()
let url = NSURL(fileURLWithPath: "/Users/me/Documents/speech.aiff")
synth.synthesize("Hello World!", output: url) { (url, error) in
if let url = url {
print("URL: \(url)")
}
if let error = error {
print("Error: \(error)")
}
expect.fulfill()
}
self.waitForExpectationsWithTimeout(1000, handler: nil)
// Test: output file should exist.
}
In the code above, check the result of the call to synth.startSpeakingString(name, toURL: URL), which can return false if the synthesiser could not start speaking. If it fails, find out why, or just retry it.
Plus, add [NSSpeechSynthesiserDelegate][1], and look for the speechSynthesizer:didFinishSpeaking: callbacks there. When the synthesiser thinks it has finished speaking, check the file size. If it is zero, retry the operation.