didReceiveNewSession method of QBRTCClientDelegate is not getting invoked - ios

I have recently started working on the P2P video calling feature using Quickblox SDK. I have been following their documentation for the video calling implementation which is vague enough for any beginner to understand. Even though I have implemented it as mentioned in the documentation, I am facing an issue
When a call is initiated, the didReceiveNewSession method is not getting invoked at the receiving end device and later encounters a crash responding as below:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString count]: unrecognized selector sent to instance 0x10526bc10'
I have checked the below links and tried different combinations to solve the problem:
QuickBlox : didReceiveNewSession Method is not gettimg called in swift
Incoming video call issue in iOS 13 using Quickblox and CallKit
Quickbloc Documentation - Video Calling
In didFinishLaunchingWithOptions of AppDelegate :
QBRTCClient.initializeRTC()
In viewDidLoad of ChatViewController :
override func viewDidLoad() {
super.viewDidLoad()
QBChat.instance.addDelegate(self)
self.chatManager.connect() { _ in
QBRTCClient.instance().add(self)
}
}
Delegate method of QBRTCClientDelegate protocol (Not getting invoked):
func didReceiveNewSession(_ session: QBRTCSession, userInfo: [String : String]? = nil) {
print("[ChatViewContoller] didReceiveNewSession; userInfo: \(userInfo ?? ["value":"NA"])")
if self.session != nil {
// we already have a video/audio call session, so we reject another one
// userInfo - the custom user information dictionary for the call from caller. May be nil.
let userInfo = [String:String]() // optional
session.rejectCall(userInfo)
return
}
// saving session instance here
self.session = session
}
Delegate method of QBRTCClientDelegate (Getting invoked strangely and shows state as Pending) :
func session(_ session: QBRTCBaseSession, didChange state: QBRTCSessionState) {
var stateStr = String()
switch state {
case .new: stateStr = "New"
case .connecting: stateStr = "Connecting"
case .connected: stateStr = "Connected"
case .pending: stateStr = "Pending"
case .closed: stateStr = "Closed"
default: stateStr = "Default case"
}
print("[ChatViewContoller] didChange; state: \(stateStr)")
}
Please let me know if I am missing anything in the implemented process and guide me through it. Thanks in advance.

Related

How to end a CallKit call from custom UI without having caller UUID?

I working on a video call application in iOS Swift 5, where call can only initiate from the backend web app and the mobile app can only answer to those calls,. Means there is no mobile-mobile communication, communication between web app and mobile app. In my application I'm using PushKit with CallKit for notifying the incoming call in the background or killed state. So in the background or killed state, if I get an incoming call, it will show the calling screen using CallKit and if I pressed Answer button, it will navigate to my own custom video call screen where I have end button for dismissing the call. But when I press the end button, the VoIP call get disconnected. But the CallKit call is not dismissing(Still show the green bar on the top of Homescreen). And I checked for how ending the CallKit call via code, but in most of the solution the CallKit is dismissing with the use of callUUID. But I don't know from where I will get that UUID. In some code I saw the UUID is received from the push payload, but in my case the call is initiated from the web app, so I'm not receiving the caller UUID. Please help me.
This is my code,
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: #escaping () -> Void) {
print(payload.dictionaryPayload)
let state = UIApplication.shared.applicationState
if state == .background || state == .inactive {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: callerName)
update.hasVideo = true
provider.reportNewIncomingCall(with: UUID(), update: update, completion: { error in })
} else if state == .active {
// no need for calling screen
}
}
And I tired the following code to end the call, but not working.
func endCall(call: UUID) {
let callController = CXCallController()
let endCallAction = CXEndCallAction(call: call)
let transaction = CXTransaction(action: endCallAction)
callController.request(transaction) { error in
if let error = error {
print("EndCallAction transaction request failed: \(error.localizedDescription).")
self.provider.reportCall(with: call, endedAt: Date(), reason: .remoteEnded)
return
}
print("EndCallAction transaction request successful")
}
Here I'm passing call as current UUID, then I'm getting error response as
EndCallAction transaction request failed: The operation couldn’t be
completed.
The uuid value is one that you provide.
You are providing it here:
provider.reportNewIncomingCall(with: UUID(),...
Because you are simply allocating a new UUID and passing it directly in reportNewIncomingCall you don't know the uuid when you need it later.
You need to store this uuid in a property so that you can provide it to your endCall function.

CallKit error 7 when I perform a call for the first time

I've done a call kit + twilio IOS app. The problem is (as far as I can tell) with ios 12.
When I run the app on a device with IOS 11 the call start as normal. When I run the app on a device with IOS 12, when I try to make the first call I get this error :
StartCallAction transaction request failed: The operation couldn’t be
completed. (com.apple.CallKit.error.requesttransaction error 7.)
This error represent this: The requested transaction contains actions that, if performed, would exceed the maximum number of call groups for the provider. But i set the callGroupMax number to 1 ( I tried to set it 2,3 but still the same)
I found just one thread with this error on google but no solution was provided. Pleas give me a hint on what causes this error because I'm stuck on this.
This error appears only when the first call is made after a fresh install. Then I can make calls as it was intended.
This is the callkitManager class:
class CallKitManager: NSObject {
class var shared: CallKitManager {
struct Static {
static let instance: CallKitManager = CallKitManager()
}
return Static.instance
}
fileprivate let callKitProvider: CXProvider
override init() {
callKitProvider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
callKitProvider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
let localizedName = NSLocalizedString("NAME", comment: "Name of application")
let configuration = CXProviderConfiguration(localizedName: localizedName)
configuration.supportsVideo = false
configuration.maximumCallsPerCallGroup = 1
configuration.ringtoneSound = "myringtone"
configuration.supportedHandleTypes = [.generic]
if let callKitIcon = UIImage(named: "callKitIcon") {
configuration.iconTemplateImageData = callKitIcon.pngData()
}
return configuration
}
I expect that the call to connect from the first time, but the result is that in the performStartCallAction() method I get the error from above.
So after 2 days I figure it out. The problem was that I use the callKit as a singleton which is wrong. You need "to mimic" a singleton using AppDelegate. See this tutorial https://www.raywenderlich.com/701-callkit-tutorial-for-ios and look in the AppDelegate and se how this was implemented.

Inconsistent behaviour with WatchKit app - Swift

I'm trying to make iOS app to communicate with watch, but i get inconsistent behaviour all the time - either the communication is too slow, or none of the data gets transferred at all.
Besides, i don't see any "Phone disabled" screen when the watchKit runs (which causes a crash, because i need to get data from the phone first).
This is what i have in regards to establishing the WCSession in the iPhone app
App Delegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if NSClassFromString("WCSession") != nil {
if #available(iOS 9.0, *) {
if(WCSession.isSupported()){
self.session = WCSession.defaultSession()
self.session.delegate = self
self.session.activateSession()
if session.paired {
print("Watch connected")
} else {
print("No watch")
}
}
} else {
}}
if NSClassFromString("WCSession") != nil {
if(WCSession.isSupported()){
session.sendMessage(["b":"delegateSaysHi"], replyHandler: nil, errorHandler: nil)
}}
}
MainViewController
(viewDidLoad)
if NSClassFromString("WCSession") != nil {
if(WCSession.isSupported()){
self.session = WCSession.defaultSession()
self.session.delegate = self
self.session.activateSession()
if session.paired {
print("Watch connected")
} else {
print("No watch")
}
}}
MainViewController (Method for transferring bunch of data from iOS app to watchKit app)
func transferData(){
do {
let dataArray = ["somedata": array2d1]
try WCSession.defaultSession().updateApplicationContext(dataArray)
let dataArray1 = ["somedata1": array2d2]
try WCSession.defaultSession().updateApplicationContext(dataArray1)
let dataArray2 = ["somedata2": array2d3]
try WCSession.defaultSession().updateApplicationContext(dataArray2)
let dataArray3 = ["somedata3": array2d4]
try WCSession.defaultSession().updateApplicationContext(dataArray3)
// and up to 12
}
catch {
print("Something wrong happened")
}
}
And this is for watchKit app
App Delegate
func applicationDidFinishLaunching() {
if(WCSession.isSupported()){
self.session = WCSession.defaultSession()
self.session.delegate = self
self.session.activateSession()
}
}
func applicationDidBecomeActive() {
if(WCSession.isSupported()){
self.session.sendMessage(["b":"peek"], replyHandler: nil, errorHandler: nil)
}
InterfaceController (awakeWithContext)
if(WCSession.defaultSession().reachable){
self.session.sendMessage(["b":"peek"], replyHandler: nil, errorHandler: nil)
}
Method for receiving ApplicationContext data
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
if let retrievedArray1 = applicationContext["somedata"] as? [[String]] {
self.watchAppArray = retrievedArray1
}
if let retrievedArray2 = applicationContext["somedata2"] as? [[String]] {
self.watchAppArray = retrievedArray1
// and so on for 12 arrays sent from phone
}
}
}}
Any advices on clearing out the situation are very welcome!
Thank you.
Multiple delegates/activations:
You're repeatedly setting up, delegating, and activating sessions in different parts of your app. You keep changing your delegate, so code in one part of the app will no longer be used after you delegated handling to a different part of your app.
You should use a single session/delegate throughout your app. One solution is to setup a WCSession singleton which would be available app-wide. Here's a guide which walks you through that process.
Only the most recent application context would get sent:
By trying to queue up multiple application context requests, the earlier ones would no longer be in the queue when the system gets around to transmitting it, as the system would have already replaced the preceding context with the later one. So only the last (dataArray3) would ever get transmitted.
Use the updateApplicationContext:error: method to communicate recent state information to the counterpart. When the counterpart wakes, it can use this information to update its own state. ... This method overwrites the previous data dictionary, so use this method when your app needs only the most recent data values.
If all of the arrays represent the recent state of your application, you want to transmit them together in a single dictionary.
var dataArray = [String: AnyObject]()
dataArray["somedata"] = array2d1
dataArray["somedata1"] = array2d2
dataArray["somedata2"] = array2d3
dataArray["somedata3"] = array2d4
do {
try session.updateApplicationContext(dataArray)
}
catch {
print(error)
}
It may also help to add some error handling to your sendMessage code, as the paired device may not always be reachable.
Slow communication:
As for the communication being too slow, there are two issues at hand.
Transfers may not happen immediately.
When only one session is active, the active session may still send updates and transfer files, but those transfers happen opportunistically in the background.
Remember that background transfers are not be delivered immediately. The system sends data as quickly as possible but transfers are not instantaneous, and the system may delay transfers slightly to improve power usage. Also, sending a large data file requires a commensurate amount of time to transmit the data to the other device and process it on the receiving side.
The more data you send, the longer it takes to transmit/receive it all.
When sending messages, send only the data that your app needs. All transfers involve sending data wireless to the counterpart app, which consumes power. Rather than sending all of your data every time, send only the items that have changed.
You can control how much data you send, as well as whether the data is sent interactively or in the background. If the watch is reachable, you could use sendMessage for immediate communication. If it's not reachable, you could fall back on a background method.

How to share data using Watch Connectivity when working with Core Data

In my iOS application I use Core Data to store data and a fetch request to create an array of NSManagedObjects to display in a UITableView.
On the Watch OS I check if WCSession is supported and active a session, then send the iOS application a message from the watchOS extension.
When the iOS application receives the message from the watchOS it should send the array of Objects to the watchOS extension to display the data in the WKInterfaceTable, but I am unsure how to do this. Ultimately what I am trying to achieve is;
How to share the array of Objects with the watchOS extension?
If the user adds/edits/deletes objects in the array on the Watch, how can we update the data on the iPhone ?
Also, the iOS application is embedded within a UITabBarController so does it matter which view controller I communicate with?
Watch OS FavouritesInterfaceController
var session : WCSession!
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
//Check if session is supported and Activate
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Interface Objects
//Send Message
sendmessagetoiphone()
}
func sendMessageToIphone() {
if(WCSession.isSupported()){
session.sendMessage(["b":"goodBye"], replyHandler: nil, errorHandler: nil)
}
}
IOS Application : FavouritesViewController
var objects = [Objects]()
func loadData() {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let request = NSFetchRequest(entityName: "Objects")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
do {
try
self.objects = moc.executeFetchRequest(request) as! [Objects]
// success ...
} catch {
// failure
print("Fetch failed")
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
//handle received message
let value = message["Value"] as? String
dispatch_async(dispatch_get_main_queue()) {
self.messageLabel.text = value
}
//send a reply
replyHandler(["Value":"Hello Watch"])
}
How to share the array of Objects with the Watch OS Extension ?
Since you are using WatchConnectivity framework, send the array of objects from iPhone using sendMessage method and in your FavoritesInterfaceController implement the func session(session: WCSession, didReceiveMessage method in order to get the response or you can get the array in replyhandler to.
If the user adds/edits/deletes objects in the array on the Watch OS
how can we update the data on the iPhone ?
Send the objectId along the with new changes in your sendMessage method from watch to phone, on receiving on phone made the changes in your database save it and send the updated value in your replyHandler so that your watch content will be updated accordingly.
Also the iOS application is embedded within a UITabBarController so
does it matter which view controller I communicate with ?
You desired viewController to which you are communicating OR the one that is responsible for doing changes should be alive. If multiple ViewControllers are listening to WCSessionDelegates then when you send any message from watch all of the live controllers will receive that message. You should include some kind of identifier in your sendMessage dictionary so you can know which operation to perform. Like if you want to delete an object then when watch sends a message the identifier will contain delete so that on receiving you can check the identifier value and perform the delete operation.
You can use the replyHandler in the sendMessage to do this. Make sure you implement the reply handler on both Watch and iOS App to get this.
Basically, if you get it right, your reply handler can ensure what your iOS app does in response for a watch app's message.
Also, speaking of your response (of sending an array of objects) - you should send it as a dictionary and fetch it on the watch.
First of, this is a really good question. For starters I'd recommend that you watch this session from the WWDC 2015: Session 713 - Introducing Watch Connectivity. This can be found here.
Now to your actual question. There is a great tutorial and Github repo that show you how to communicate Core Data between your Apple Watch app and the container app using App Groups, as this enables you to access all shared content, such as Core Data and even NSUSerdefaults.
You can then find the complete tutorial on how to do this under the following link.
Hope that helps, Julian.

AppleWatch - "attempt to insert nil" when calling WKInterfaceDevice addCachedImage

Calling WKInterfaceDevice addCachedImage(_:name:) to send an image from my iPhone app to the Apple Watch (where the extension can tell an image view to show it) crashes. The exception is this:
2015-06-09 20:47:57.079 TimeInterval[20195:5186462] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3]'
Various Google and StackOverflow searches show this has to do with using a shortcut to create an NSDictionary that doesn't allow passing in nil. However, my code isn't making a dictionary at all. Furthermore, when the debugger breaks (breakpoint on exceptions) I verify that the UIImage and the NSString name that I am passing are both definitely not nil.
Has anyone else seen this? Any idea why it happens or how to fix it? Has anyone actually been successful using addCachedImage? (Considering how new AppleWatch is, who knows!)
My chunk of code, in case it helps:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
if let info = userInfo {
NSLog("watch kit request: %#", info)
if let element = info["element"] as? String {
//Request to render an element
if element == "timer" {
let timerView = NPProgressLabel(size: CGSizeMake(48, 48))
timerView.text = info["value"] as! String
timerView.progressPercent = (info["progress"] as! NSNumber).floatValue
timerView.render()
let device = WKInterfaceDevice.currentDevice()
var success = false
if let image = timerView.currentImage {
success = device.addCachedImage(image, name: timerView.currentImageName()) // <------- crashing here ----------
} else {
NSLog("no image");
}
if !success {
NSLog("failed")
} else {
NSLog("addCachedImage success")
}
reply(["imageName": timerView.currentImageName()])
} else {
reply(["error": "Unknown element"])
}
return
}
}
reply(["error": "Bad request"])
}
The exact error I get may be an Apple bug, but I think the answer to my question is that WKInterfaceDevice's addCachedImage should not be called from the iPhone app but rather from the WatchKit Extension. Between iPhone app and WatchKit Extension I have to use a shared container to save then load the image, then the extension can call addCachedImage.

Resources