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

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.

Related

didReceiveNewSession method of QBRTCClientDelegate is not getting invoked

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.

Is it possible to change NSUserActivity's userInfo from another app?

I am trying to open app B, from app A using universal links as below:
#IBAction func openButtonPressed(_ sender: Any) {
if let appURL = URL(string: "https://website.com/section/Paris") {
UIApplication.shared.open(appURL) { success in
if success {
print("The URL was delivered successfully.")
} else {
print("The URL failed to open.")
}
}
} else {
print("Invalid URL specified.")
}
}
In App B's AppDelegate -> application continueUserActivity restorationHandler method, it calls another function where the userActivity is processed.
Here there is a check for NSUserActivity's userInfo.
It is checking for a key PageName in NSUserActivity's userInfo property, and according to the value it assigns a type to the activity and routes the app accordingly.
NSString *page = activity.userInfo[#"PageName"];
if ([page isEqualToString:#"Places"]) {
return UserActivityTypePlaces;
} else {
return UserActivityTypeLink;
}
I was wondering if I can change or add a key value pair to userInfo in App A, so that App B can route me to another tab. (In this case I want it to open another tab and search the location string that is in the universal link)
I have checked other questions related with NSUserActivity's userInfo on StackOverflow and Apple Developer forums but they are not changing it on the calling app.(such as in App A)
Thank you
I made these in App A:
let activity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
activity.title = "Places"
activity.userInfo = ["Page" : "Places"]
let userInfoSet:Set = ["Page"]
activity.requiredUserInfoKeys = userInfoSet
self.userActivity = activity
self.userActivity?.becomeCurrent()
and added the NSUserActivityDelegate method:
override func updateUserActivityState(_ activity: NSUserActivity) {
let dict:Dictionary = ["Page":"Places"]
userActivity?.addUserInfoEntries(from: dict)
}
But the NSUserActivity object is not something that goes from one app to the other. So this change only works within app A.
Hope this helps someone else too.

CoreData fails to initialise a managed object when accessed through UserNotification custom action

I've been trying to solve this problem for some time, to no avail.
When accessing a managed object from UserNotification custom action and then trying to save the changes to this object I get the following message:
[error] error: CoreData: error: Failed to call designated initializer on NSManagedObject class 'NSManagedObject'.
Basically, the setup is as follows:
1. User gets a notification
2. Chooses a custom action
3. From the info in the notification the UserNotification Center Delegate extracts the URI of the object and then extracts it from the persistent store
4. Once done and type-casted the delegate calls appropriate method on the object
5. After method returns delegate tries to save the context, and that's where the error appears.
Here is some relevant code:
// - UNUserNotification Centre delegate
extension HPBUserNotificationsHandler: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
guard let object = getAssociatedObject(id: response.notification.request.identifier) else { return }
switch response.actionIdentifier {
case UNNotificationDismissActionIdentifier:
....
case UNNotificationDefaultActionIdentifier:
....
case HPBReminderAction.take.rawValue:
// get intake object
guard let reminder = object as? HPBIntake else { return }
reminder.take(at: Date())
try! dataController.saveContext() // here is when the error is raised
default:
break
}
completionHandler()
}
The function to extract an object from persistent store:
func getAssociatedObject(id: String) -> NSManagedObject? {
guard let psc = dataController.managedObjectContext.persistentStoreCoordinator else { return nil }
guard let objURL = URL(string: id) else { return nil }
guard let moid = psc.managedObjectID(forURIRepresentation: objURL) else { return nil }
return dataController.managedObjectContext.object(with: moid)
}
If I make the same changes on this object directly in the app - everything works. So I assume the matter is with getting the object from a custom action on User Notification. But I can't figure out what is the problem.
Here is some additional info. When I inspect the reminder object right before calling the take(on:) function, it shows as a fault:
Home_Pillbox.HPBIntake: 0x7fb1a9074e90 (entity: Intake; id: 0x7fb1a9069e50 x-coredata:///Intake/tAC4BBCD4-B128-4C6F-8E1B-2EE7D4EDBCB34 ; data: fault)
Of course, when the function is called, the fault is fired but the object is not initialised correctly and instead populates all properties as nil:
Home_Pillbox.HPBIntake: 0x7fb1a9074e90 (entity: Intake; id: 0x7fb1a9069e50 x-coredata:///Intake/tAC4BBCD4-B128-4C6F-8E1B-2EE7D4EDBCB34 ; data: {dosage = 0; identifier = nil; localNotification = nil; log = nil; meal = 0; medName = nil; notificationRequest = nil; profileName = nil; schedule = nil; status = 1; treatment = nil; unit = 0; userNotes = nil;})
So when the context tries to save it can't, as properties are nil, which is not allowed by the data model. What also bothers me is that the error mentions designated initialiser on NSManagedObject instead of the name of the subclass HPBIntake, even though the object is clearly correctly typed.
Any help will be highly appreciated.
EDIT:
Here's the implementation of saveContext() function in the DataController:
func saveContext() throws {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch let syserr as NSError {
throw syserror
}
}
}
One more idea: Since you get an initializer error, it seems to me that the object whose id you transfer via the notification is not yet initialized when you try to save it.
In this link I found the following:
object(with:) throws an exception if no record can be found for the
object identifier it receives. For example, if the application deleted
the record corresponding with the object identifier, Core Data is
unable to hand your application the corresponding record. The result
is an exception.
The existingObject(with:) method behaves in a similar
fashion. The main difference is that the method throws an error if it
cannot fetch the managed object corresponding to the object
identifier.
So I suggest to replace in getAssociatedObject(id: String) the call to object(with: moid) by existingObject(with: moid). If this throws an error, you know that the related object does not yet or no longer exist.
If this is allowed by your app, you had to initialize it by the designated initialiser
init(entity entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?)
before you try to store it.
EDIT:
In this answer, you can find more suggestions how to debug your core data handling.
Just an idea: You said
If I make the same changes on this object directly in the app -
everything works.
I assume you do these changes on the main thread.
Did you check if userNotificationCenter(_:didReceive:withCompletionHandler:) is also executed on the main thread? If not, you might have a problem here, since core data expects to be executed on one thread only. In this case you could try to execute the body of this function with
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
DispatchQueue.main.async {
// Your code here
}
}
You should also set the launch argument -com.apple.CoreData.ConcurrencyDebug 1 in your scheme, together with exception breakpoints:
Then your app will stop when a core data multi-thread violation happens.

Authenticating the user with biometrics causing application to crash

So i'm following the books in terms of authenticating a user using biometrics. Below is some code i've wrote in a custom class called biometrics manager.
func authenticateUser(completion: #escaping (_ result: BiometricsStatus) -> Void) {
DispatchQueue.main.async {
guard self.deviceHasBiometricCapabilities() else { completion(.fail(error: .touchIDNotAvailable)); return }
let authMethod = self.biometricType() == .faceID ? "Face ID" : "Touch ID"
let loginMessage = "\(authMethod) to sign in"
self.context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: loginMessage) { success, evaluateError in
if success {
completion(.success)
return
}
if let error = evaluateError {
completion(.fail(error: self.getBiometricsError(from: error)))
return
}
}
}
}
I've debugged my application and it seems to be causing a crash on the evaluate policy line, i've enabled exception breakpoints to try and catch the crash but i'm receiving nothing at all in the console logs. The only thing that i seem to be getting in the console is the following.
Message from debugger: Terminated due to signal 9
Which isn't super helpful any possible pointers or ideas that may be causing this crash to occur at all?
You need to add the NSFaceIDUsageDescription key to your info.plist
From https://developer.apple.com/documentation/localauthentication/lacontext
Important
Include the NSFaceIDUsageDescription key in your app’s Info.plist file if your app allows biometric authentication. Otherwise, authorization requests may fail.

Google Autocomplete function Crash after Call

I am using Google PlaceAutoComplete method to get suggestions of the Addess that is entered in textField.
func placeAutocomplete(text:String) {
let placesClient = GMSPlacesClient()
let filter = GMSAutocompleteFilter()
filter.type = .Address
placesClient.autocompleteQuery("New Delhi", bounds: nil, filter: nil) { (results, error) in
guard error == nil else {
print("Autocomplete error \(error)")
return
}
self.addressArray.removeAll()
for result in results! {
self.addressArray.append(result.attributedFullText.string)
print("Result \(result.attributedFullText.string) with placeID \(result.placeID)")
}
}
}
When i call this method. It crashes, say the Following error.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary boolValue]: unrecognized selector sent to instance 0x7fe338f01e40'
I have tried to found using exception breakpoint but doesn't work.
Can any have idea, where i am wrong?
I have resolved the issue by correcting in the plist for "allow arbitrary loads" in App Transport security Settings. I was typed it true but its type was set string instead for Boolean
Somewhere a NSDictionary is being passed to the code where it is expecting a something that can be interpreted as a boolean such as an NSString or NSNumber. I don't see anything like that in the code you provided. If exception breakpoints aren't working I would try adding normal breakpoints somewhere and stepping over code until it crashes. You could also try removing certain sections and code and seeing if the crash is still happening, this will let you narrow down what portion of your code is to blame.

Resources