I'm working in a app that uses Apple Pay, and everything was working, but suddenly this crash start to happen
func startPaymentWith(output: PaymentHandlerResponseProtocol?) {
self.output = output
self.output?.startProcess()
// Create our payment request
let paymentRequest = PKPaymentRequest()
paymentRequest.paymentSummaryItems = paymentSummaryItems
paymentRequest.merchantIdentifier = "abcde.abcdef"
paymentRequest.merchantCapabilities = .capability3DS
paymentRequest.countryCode = "BR"
paymentRequest.currencyCode = "BRL"
paymentRequest.supportedNetworks = self.supportedNetworks
print("comoestaovalorsemreplace \(paymentRequest.paymentSummaryItems[0].amount)")
// Display our payment request
paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest) // Here happens the EXC_BAD_ACCESS
paymentController?.delegate = self
paymentController?.present(completion: { (presented: Bool) in
if presented {
debugPrint("Presented payment controller")
} else {
debugPrint("Failed to present payment controller")
self.output?.responseWith(nil)
}
})
}
This is shown in var paymentRequest when mouse pointer is hover above the the variable:
expression produced error: error: /var/folders/sp/8x6rp88s4nv6s3z1635pqdtm0000gq/T/expr139-c248cd..swift:1:65: error: cannot find type 'PassKit' in scope
Swift._DebuggerSupport.stringForPrintObject(Swift.UnsafePointer<PassKit.PKPaymentRequest>(bitPattern: 0x15e4e9da0)!.pointee)
Related
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.
I am testing the auto renewable In-app purchases in swift, I found out that there are some strange problems with my code.
I am testing these functions in sandbox environment
User can purchase either one month, one year auto renewable subscription or permanent permission
App should check if the subscription is still valid every time when user open app, if not, lock all premium functions
User is able to restore the purchased plan, app should get the previous purchased type ie. one month, one year, or permanent.
After long research on the tutorials, I am still confused about the validation
I see that there are two ways to validate receipt, one is locally the other is on the server.
But I don't have a server, does that mean I can only validate it locally
Every time the auto-renewal subscription expires, the local receipt is not updated, so when I reopen the app I got a subscription expiration alert (The method I defined by my self for validation check ), when I click the restore button, the app restored successfully and receipt was updated
After 6 times manually restored and refresh the receipt (the sandbox user can only renew 6 times), when I click the restore button, the part transaction == .purchased is till called, and my app unlocks premium function, however when I reopen my app, my app alerts that the subscription is expired, which is it should.
My core problem is how can I check the validation of subscriptions with Apple every time when I open the app, I don't have a server, and I don't know why the receipt is not refreshing automatically
Here are some parts of my code, I call checkUserSubsriptionStatus() when I open the app, I am using TPInAppReceipt Library
class InAppPurchaseManager {
static var shared = InAppPurchaseManager()
init() {
}
public func getUserPurchaseType() -> PurchaseType {
if let receipt = try? InAppReceipt.localReceipt() {
var purchaseType: PurchaseType = .none
if let purchase = receipt.lastAutoRenewableSubscriptionPurchase(ofProductIdentifier: PurchaseType.oneMonth.productID) {
purchaseType = .oneMonth
}
if let purchase = receipt.lastAutoRenewableSubscriptionPurchase(ofProductIdentifier: PurchaseType.oneYear.productID) {
purchaseType = .oneYear
}
if receipt.containsPurchase(ofProductIdentifier: PurchaseType.permanent.productID) {
purchaseType = .permanent
}
return purchaseType
} else {
print("Receipt not found")
return .none
}
}
public func restorePurchase(in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
if SKPaymentQueue.canMakePayments() {
SKPaymentQueue.default().restoreCompletedTransactions()
} else {
self.userIsNotAbleToPurchase()
}
}
public func checkUserSubsriptionStatus() {
DispatchQueue.main.async {
if let receipt = try? InAppReceipt.localReceipt() {
self.checkUserPermanentSubsriptionStatus(with: receipt)
}
}
}
private func checkUserPermanentSubsriptionStatus(with receipt: InAppReceipt) {
if let receipt = try? InAppReceipt.localReceipt() { //Check permsnent subscription
if receipt.containsPurchase(ofProductIdentifier: PurchaseType.permanent.productID) {
print("User has permament permission")
if !AppEngine.shared.currentUser.isVip {
self.updateAfterAppPurchased(withType: .permanent)
}
} else {
self.checkUserAutoRenewableSubsrption(with: receipt)
}
}
}
private func checkUserAutoRenewableSubsrption(with receipt: InAppReceipt) {
if receipt.hasActiveAutoRenewablePurchases {
print("Subsription still valid")
if !AppEngine.shared.currentUser.isVip {
let purchaseType = InAppPurchaseManager.shared.getUserPurchaseType()
updateAfterAppPurchased(withType: purchaseType)
}
} else {
print("Subsription expired")
if AppEngine.shared.currentUser.isVip {
self.subsrptionCheckFailed()
}
}
}
private func updateAfterAppPurchased(withType purchaseType: PurchaseType) {
AppEngine.shared.currentUser.purchasedType = purchaseType
AppEngine.shared.currentUser.energy += 5
AppEngine.shared.userSetting.hasViewedEnergyUpdate = false
AppEngine.shared.saveUser()
AppEngine.shared.notifyAllUIObservers()
}
public func updateAfterEnergyPurchased() {
AppEngine.shared.currentUser.energy += 3
AppEngine.shared.saveUser()
AppEngine.shared.notifyAllUIObservers()
}
public func purchaseApp(with purchaseType: PurchaseType, in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
if SKPaymentQueue.canMakePayments() {
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = purchaseType.productID
SKPaymentQueue.default().add(paymentRequest)
} else {
self.userIsNotAbleToPurchase()
}
}
public func purchaseEnergy(in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
let productID = "com.crazycat.Reborn.threePointOfEnergy"
if SKPaymentQueue.canMakePayments() {
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = productID
SKPaymentQueue.default().add(paymentRequest)
} else {
self.userIsNotAbleToPurchase()
}
}
}
If you do not have the possibility to use a server, you need to validate locally. Since you are already included TPInAppReceipt library, this is relatively easy.
To check if the user has an active premium product and what type it has, you can use the following code:
// Get all active purchases which are convertible to `PurchaseType`.
let premiumPurchases = receipt.activeAutoRenewableSubscriptionPurchases.filter({ PurchaseType(rawValue: $0.productIdentifier) != nil })
// It depends on how your premium access works, but if it doesn't matter what kind of premium the user has, it is enough to take one of the available active premium products.
// Note: with the possibility to share subscriptions via family sharing, the receipt can contain multiple active subscriptions.
guard let product = premiumPurchases.first else {
// User has no active premium product => lock all premium features
return
}
// To be safe you can use a "guard" or a "if let", but since we filtered for products conforming to PurchaseType, this shouldn't fail
let purchaseType = PurchaseType(rawValue: product.productIdentifier)!
// => Setup app corresponding to active premium product type
One point I notice in your code, which could lead to problems, is that you constantly add a new SKPaymentTransactionObserver. You should have one class conforming to SKPaymentTransactionObserver and add this only once on app start and not on every public call. Also, you need to remove it when you no longer need it (if you created it only once, you would do it in the deinit of your class, conforming to the observer protocol.
I assume this is the reason for point 2.
Technically, the behavior described in point 3 is correct because the method you are using asks the payment queue to restore all previously completed purchases (see here).
Apple states restoreCompletedTransactions() should only be used for the following scenarios (see here):
If you use Apple-hosted content, restoring completed transactions gives your app the transaction objects it uses to download the content.
If you need to support versions of iOS earlier than iOS 7, where the app receipt isn’t available, restore completed transactions instead.
If your app uses non-renewing subscriptions, your app is responsible for the restoration process.
For your case, it is recommended to use a SKReceiptRefreshRequest, which requests to update the current receipt.
Get the receipt every time when the app launches by calling the method in AppDelegate.
getAppReceipt(forTransaction: nil)
Now, below is the required method:
func getAppReceipt(forTransaction transaction: SKPaymentTransaction?) {
guard let receiptURL = receiptURL else { /* receiptURL is nil, it would be very weird to end up here */ return }
do {
let receipt = try Data(contentsOf: receiptURL)
receiptValidation(receiptData: receipt, transaction: transaction)
} catch {
// there is no app receipt, don't panic, ask apple to refresh it
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
// If all goes well control will land in the requestDidFinish() delegate method.
// If something bad happens control will land in didFailWithError.
}
}
Here is the method receiptValidation:
func receiptValidation(receiptData: Data?, transaction: SKPaymentTransaction?) {
guard let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) else { return }
verify_in_app_receipt(with_receipt_string: receiptString, transaction: transaction)
}
Next is the final method that verifies receipt and gets the expiry date of subscription:
func verify_in_app_receipt(with_receipt_string receiptString: String, transaction: SKPaymentTransaction?) {
let params: [String: Any] = ["receipt-data": receiptString,
"password": "USE YOUR PASSWORD GENERATED FROM ITUNES",
"exclude-old-transactions": true]
// Below are the url's used for in app receipt validation
//appIsInDevelopment ? "https://sandbox.itunes.apple.com/verifyReceipt" : "https://buy.itunes.apple.com/verifyReceipt"
super.startService(apiType: .verify_in_app_receipt, parameters: params, files: [], modelType: SubscriptionReceipt.self) { (result) in
switch result {
case .Success(let receipt):
if let receipt = receipt {
print("Receipt is: \(receipt)")
if let _ = receipt.latest_receipt, let receiptArr = receipt.latest_receipt_info {
var expiryDate: Date? = nil
for latestReceipt in receiptArr {
if let dateInMilliseconds = latestReceipt.expires_date_ms, let product_id = latestReceipt.product_id {
let date = Date(timeIntervalSince1970: dateInMilliseconds / 1000)
if date >= Date() {
// Premium is valid
}
}
}
if expiryDate == nil {
// Premium is not purchased or is expired
}
}
}
case .Error(let message):
print("Error in api is: \(message)")
}
}
}
I am using Twilio iOS framework to connect in the group room.
On the click on connect room button below is the code which I used
let recorder = RPScreenRecorder.shared()
recorder.isMicrophoneEnabled = false
recorder.isCameraEnabled = false
// The source produces either downscaled buffers with smoother motion, or an HD screen recording.
videoSource = ReplayKitVideoSource(isScreencast: true, telecineOptions: ReplayKitVideoSource.TelecineOptions.disabled)
screenTrack = LocalVideoTrack(source: videoSource!,
enabled: true,
name: "Screen")
recorder.startCapture(handler: { (sampleBuffer, type, error) in
if error != nil {
print("Capture error: ", error as Any)
return
}
switch type {
case RPSampleBufferType.video:
self.videoSource?.processFrame(sampleBuffer: sampleBuffer)
break
case RPSampleBufferType.audioApp:
break
case RPSampleBufferType.audioMic:
// We use `TVIDefaultAudioDevice` to capture and playback audio for conferencing.
break
}
}) { (error) in
if error != nil {
print("Screen capture error: ", error as Any)
} else {
print("Screen capture started.")
}
}
if (accessToken == "TWILIO_ACCESS_TOKEN") {
do {
accessToken = try TokenUtils.fetchToken(url: tokenUrl)
} catch {
let message = "Failed to fetch access token"
logMessage(messageText: message)
return
}
}
// Prepare local media which we will share with Room Participants.
self.prepareLocalMedia()
// Preparing the connect options with the access token that we fetched (or hardcoded).
let connectOptions = ConnectOptions(token: accessToken) { (builder) in
// Use the local media that we prepared earlier.
builder.audioTracks = self.localAudioTrack != nil ? [self.localAudioTrack!] : [LocalAudioTrack]()
builder.videoTracks = self.localVideoTrack != nil ? [self.localVideoTrack!, self.screenTrack!] : [LocalVideoTrack]()
// Use the preferred audio codec
if let preferredAudioCodec = Settings.shared.audioCodec {
builder.preferredAudioCodecs = [preferredAudioCodec]
}
// Use the preferred video codec
if let preferredVideoCodec = Settings.shared.videoCodec {
builder.preferredVideoCodecs = [preferredVideoCodec]
}
// Use the preferred encoding parameters
if let encodingParameters = Settings.shared.getEncodingParameters() {
builder.encodingParameters = encodingParameters
}
// Use the preferred signaling region
if let signalingRegion = Settings.shared.signalingRegion {
builder.region = signalingRegion
}
builder.roomName = self.roomTextField.text
}
// Connect to the Room using the options we provided.
room = TwilioVideoSDK.connect(options: connectOptions, delegate: self)
logMessage(messageText: "Attempting to connect to room \(String(describing: self.roomTextField.text))")
When I connected in the group with remote participant I want to share the screen with remote participant.
To implement this feature I have referred the “ReplayKitExample” with in-app capture method. But not able to do that.
Remote participant not able to see the screen share content.
Nothing is happening related to screen share with this, and looking for inputs on implementing it.
I want to share the screen to remote participant.
Its happening because you are trying to send "cameraSource" and "videoSource" both data at the same time you have to unsubscribe the "cameraSource" before sending "viseoSource".
Heres my code you can refer:
//MARK: - Screen Sharing via replaykit
extension CallRoomViewController: RPScreenRecorderDelegate {
func broadCastButtonTapped(){
guard screenRecorder.isAvailable else {
print("Not able to Broadcast")
return
}
print("Can Broadcast")
if self.videoSource != nil {
self.stopConference()
} else {
self.startConference()
}
}
func publishVideoTrack(){
if let participant = self.room?.localParticipant,
let videoTrack = self.localVideoTrack {
participant.publishVideoTrack(videoTrack)
}
}
func unpublishVideoTrack(){
if let participant = self.room?.localParticipant,
let videoTrack = self.localVideoTrack {
participant.unpublishVideoTrack(videoTrack)
}
}
func stopConference() {
self.unpublishVideoTrack()
self.localVideoTrack = nil
self.videoSource = nil
self.localVideoTrack = LocalVideoTrack(source: cameraSource!, enabled: true, name: "Camera")
screenRecorder.stopCapture{ (captureError) in
if let error = captureError {
print("Screen capture stop error: ", error as Any)
} else {
print("Screen capture stopped.")
self.publishVideoTrack()
}
}
}
func startConference() {
self.unpublishVideoTrack()
self.localVideoTrack = nil
// We are only using ReplayKit to capture the screen.
// Use a LocalAudioTrack to capture the microphone for sharing audio in the room.
screenRecorder.isMicrophoneEnabled = false
// Use a LocalVideoTrack with a CameraSource to capture the camera for sharing camera video in the room.
screenRecorder.isCameraEnabled = false
// The source produces either downscaled buffers with smoother motion, or an HD screen recording.
self.videoSource = ReplayKitVideoSource(isScreencast: true,
telecineOptions: ReplayKitVideoSource.TelecineOptions.p60to24or25or30)
self.localVideoTrack = LocalVideoTrack(source: videoSource!,
enabled: true,
name: "Screen")
let videoCodec = Settings.shared.videoCodec ?? Vp8Codec()!
let (_, outputFormat) = ReplayKitVideoSource.getParametersForUseCase(codec: videoCodec,
isScreencast: true,
telecineOptions:ReplayKitVideoSource.TelecineOptions.p60to24or25or30)
self.videoSource?.requestOutputFormat(outputFormat)
screenRecorder.startCapture(handler: { (sampleBuffer, type, error) in
if error != nil {
print("Capture error: ", error as Any)
return
}
switch type {
case RPSampleBufferType.video:
self.videoSource?.processFrame(sampleBuffer: sampleBuffer)
break
case RPSampleBufferType.audioApp:
break
case RPSampleBufferType.audioMic:
// We use `TVIDefaultAudioDevice` to capture and playback audio for conferencing.
break
default:
print(error ?? "screenRecorder error")
}
}) { (error) in
if error != nil {
print("Screen capture error: ", error as Any)
} else {
print("Screen capture started.")
self.publishVideoTrack()
}
}
}
}
And you can connect room from viewDidLoad()
func connectToChatRoom(){
// Configure access token either from server or manually.
// If the default wasn't changed, try fetching from server.
accessToken = self.callRoomDetail.charRoomAccessToken
guard accessToken != "TWILIO_ACCESS_TOKEN" else {
let message = "Failed to fetch access token"
print( message)
return
}
// Prepare local media which we will share with Room Participants.
self.prepareLocalMedia()
// Preparing the connect options with the access token that we fetched (or hardcoded).
let connectOptions = ConnectOptions(token: accessToken) { (builder) in
// The name of the Room where the Client will attempt to connect to. Please note that if you pass an empty
// Room `name`, the Client will create one for you. You can get the name or sid from any connected Room.
builder.roomName = self.callRoomDetail.chatRoomName
// Use the local media that we prepared earlier.
if let audioTrack = self.localAudioTrack {
builder.audioTracks = [ audioTrack ]
}
if let videoTrack = self.localVideoTrack {
builder.videoTracks = [ videoTrack ]
}
// Use the preferred audio codec
if let preferredAudioCodec = Settings.shared.audioCodec {
builder.preferredAudioCodecs = [preferredAudioCodec]
}
// Use the preferred video codec
if let preferredVideoCodec = Settings.shared.videoCodec {
builder.preferredVideoCodecs = [preferredVideoCodec]
}
// Use the preferred encoding parameters
let videoCodec = Settings.shared.videoCodec ?? Vp8Codec()!
let (encodingParams, _) = ReplayKitVideoSource.getParametersForUseCase(codec: videoCodec,
isScreencast: true,
telecineOptions:ReplayKitVideoSource.TelecineOptions.p60to24or25or30)
builder.encodingParameters = encodingParams
// Use the preferred signaling region
if let signalingRegion = Settings.shared.signalingRegion {
builder.region = signalingRegion
}
builder.isAutomaticSubscriptionEnabled = true
builder.isNetworkQualityEnabled = true
builder.networkQualityConfiguration = NetworkQualityConfiguration(localVerbosity: .minimal,
remoteVerbosity: .minimal)
}
// Connect to the Room using the options we provided.
room = TwilioVideoSDK.connect(options: connectOptions, delegate: self)
print( "Attempting to connect to room \(self.callRoomDetail.chatRoomName ?? ""))")
self.showRoomUI(inRoom: true)
}
You can get ReplayKitVideoSource file and other files from twilio repository https://github.com/twilio/video-quickstart-ios/tree/master/ReplayKitExample
I work at Twilio and I can confirm that you should be able to publish video tracks for both camera and screen at the same time without issue.
It is difficult to identify why this is not working for you without a completely functional example app.
However I have tested this using one of our reference apps and confirmed it is working. More details are here: https://github.com/twilio/video-quickstart-ios/issues/650#issuecomment-1178232542
Hopefully this is a useful example for how to publish both camera and screen video at the same time.
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.
Im using SMS verification to verify users. My problem is that when I enter a code to verify I get invalid code. I can't for the life of me figure out why.
Calling cloud code function:
#IBAction func verifyCodeButtonTapped(sender: AnyObject) {
var verificationCode: String = verificationCodeTextField.text!
let textFieldText = verificationCodeTextField.text ?? ""
if verificationCode.utf16.count != 4 {
displayAlert("Error", message: "You must entert the 4 digit verification code sent yo your phone")
} else {
let params = ["verifyPhoneNumber" : textFieldText]
PFCloud.callFunctionInBackground("verifyPhoneNumber", withParameters: params, block: { (object: AnyObject?, error) -> Void in
if error == nil {
self.performSegueWithIdentifier("showVerifyCodeView", sender: self)
} else {
self.displayAlert("Sorry", message: "We couldnt verify you. Please check that you enterd the correct 4 digit code sent to your phone")
}
})
}
}
Cloud code to verify code:
Parse.Cloud.define("verifyPhoneNumber", function(request, response) {
var user = Parse.User.current();
var verificationCode = user.get("phoneVerificationCode");
if (verificationCode == request.params.phoneVerificationCode) {
user.set("phoneNumber", request.params.phoneNumber);
user.save();
response.success("Success");
} else {
response.error("Invalid verification code.");
}
});
Twilio developer evangelist here.
In the Parse code, you are expecting request.params.phoneVerificationCode but when you call the cloud function from iOS you let params = ["verifyPhoneNumber" : textFieldText].
So, either change that line to
let params = ["phoneVerificationCode" : textFieldText]
so that it matches the cloud code. Or change your cloud code to
if (verificationCode == request.params.verifyPhoneNumber) {
so that it matches the iOS code.