Trigger a segue from Watch in iPhone - ios

Is it possible to trigger a segue in the iPhone app from the Watch code? I have been looking into OS2 and it says that data can be transferred while both apps are active, but want to know if it is possible to call code within the iPhone app from the Watch code.

You can not push segue from WatchApp(Child Application) to iPhone App (Parent Application), but you can surely send data from Watch to iPhone and according to that data you can perform segue on iPhone Application.
To Send update from Apple Watch to iPhone App you have to implement openParentApplication in WatchKit Extension.
class func openParentApplication(userInfo: [NSObject : AnyObject], reply: (([NSObject : AnyObject], NSError?) -> Void)?) -> Bool
Create NSDictionary and pass that data using below code :
func updateInformationToParentApp(emergencyInfo : [NSObject : AnyObject]!){
// Call the parent application from Apple Watch
WKInterfaceController.openParentApplication(emergencyInfo) { (returnUpdate, error) -> Void in
if((error) == nil){
print("Data send successfully");
} else {
print("Error : \(error?.localizedDescription)");
}
}
}
Implement below method in AppDelegate to handle WatchApp update.
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: ([NSObject : AnyObject]?) -> Void){
NSLog("Handler Apple Watch Event ");
watchEvent(userInfo, reply: reply);
}
func watchEvent(userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) ->Void)!) {
let dic = userInfo as NSDictionary;
NSLog("dic %#", dic);
// retrieved parameters from Apple Watch
print(userInfo["Key"])
//Perform Segue from Here according to Dictionary Info
//Pass back values to Apple Watch
var returnUpdate = Dictionary<String,AnyObject>()
let watchAppMessage = "Meesage Back To Apple Watch" as NSString;
returnUpdate["message"] = NSKeyedArchiver.archivedDataWithRootObject(watchAppMessage);
reply(returnUpdate)
}

You can see iPhone and iWatch contains different storyboards and different user interfaces, so segue from watch to iPhone is surely not possible but you can manage something like that with notifications as if in iWatch some button is pressed, you can send notification to iPhone environment and it can trigger some action.

Related

IOS trigger notification action when app is in the background

My app does the following when the app is opened from a remote notification. Basically, it saves article_id in UserDefaults so that I can use it when the app is launched:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
if let aps = userInfo["aps"] as? [String: AnyObject] {
let article_id = aps["article_id"]
UserDefaults.standard.set(article_id, forKey: "notification_article_id")
}
completionHandler()
}
}
However, this only works if the app is completely closed. If the app remains in the background and the user clicks the notification (e.g. from the lock screen), the function above will not be triggered. Thus, it will not save the data into my UserDefaults. Does any one know how to trigger a similar action in this situation? Thanks in advance!
Your extension delegate function declaration is correct, and should fire in the state that you described (lock screen, home screen, app backgrounded). Please make sure that you have set the delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
}
If this is already set, I would verify that your parsing code is correct, and test on the simulator or another device. I often use xcrun simctl to test push notifications in the simulator. You can do this by creating a dummy test file called 'payload.json', and exectuting the following command in the terminal:
xcrun simctl push booted com.yourapp.bundle.id payload.json
This is an example of a payload.json:
{
"Simulator Target Bundle": "com.yourapp.bundle.id",
"aps":{
"alert":{
"title":"Egon Spengler",
"body":"I collect spores, molds, and fungus"
},
"sound":"alert.caf",
"badge":3
},
"alert":{
"alertUuid":"asdfasdfasdfasdfasdf",
"state":1,
"lastUpdate":"2020-11-8T21:43:57+0000"
}
}
If the application has been terminated, you can obtain notification content at launch using the following code within didFinishLaunchingWithOptions:
let notificationOption = launchOptions?[.remoteNotification]
if let notification = notificationOption as? [String: AnyObject] {
}
Finally, make sure that you have enabled 'Remote Notifications' background mode in your project settings:

After restarting Apple Watch WCSession 'isRechable' returning false every time

I want to display data from firebase realtime database in watch app.
I have one method to fetch data from firebase in iOS parent App.
From watch app I am sending message to iOS parent app, iOS parent app will fetch data from firebase and reply to watch app with that data.
In iOS parent App activated WCSession as below,
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Watch Connectivity setup
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
return true
}
Same way activated session in ExtensionDelegate in Watch Extension,
ExtensionDelegate.swift
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
Now in InterfaceController class sent message to iOS parent app and get data in reply.
InterfaceController.swift
override func awake(withContext context: Any?) {
super.awake(withContext: context)
getData()
}
func getData() {
if WCSession.default.isReachable {
session.sendMessage(["Request": "GetData"], replyHandler: { (dictReply) in
print(dictReply)
}) { (error) in
print(error)
}
} else {
let action = WKAlertAction(title: "Ok", style: .cancel) {
}
self.presentAlert(withTitle: "Error", message: "iPhone is not reachable, try reconnecting watch with iPhone and try again.", preferredStyle: .alert, actions: [action])
}
}
Handled received message in iOS parent app and sent data in reply as dictionary.
AppDelegate.swift
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
guard let request = message["Request"] as? String else {
replyHandler([:])
return
}
switch request {
case "GetData":
// Call method to get data from firebase realtime database and send response dictionary in replyHandler for demo purpose currently passed test data
replyHandler(["test" : "Hello..!!!"])
default:
replyHandler([:])
}
}
Now first time when run in device its working fine, bt after restarting apple watch opening watch app and its giving me error in alert "iPhone is not reachable, try reconnecting watch with iPhone and try again." that means WCSession.default.isReachable returning false every time. Application stuck here and just showing black screen on Apple watch.
Also If I run app from xcode then its working every time.
I am using iPhone 8 and Apple Watch series 1

How to subscribe to changes for a public database in CloudKit?

What is the best way to subscribe to a public database in CloudKit?
I have a table with persons. Every person contains a name and a location.
Once the location changes, the location is updated in CloudKit.
That part is working fine.
But I am not able to make it work to get a notification when there is a record update.
Some example would be really helpful, as I have looked into the possible option already.
I have looked into the options where I save the subscription in the database and also the CKModifySubscriptionsOperation option.
Currently, my code to subscribe looks like this:
let predicate = NSPredicate(format: "TRUEPREDICATE")
let newSubscription = CKQuerySubscription(recordType: "Person", predicate: predicate, options: [.firesOnRecordCreation, .firesOnRecordDeletion, .firesOnRecordUpdate])
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
newSubscription.notificationInfo = info
database.save(newSubscription, completionHandler: {
(subscription, error) in
if error != nil {
print("Error Creating Subscription")
print(error)
} else {
userSettings.set(true, forKey: "subscriptionSaved")
}
})
Can someone also show me how my AppDelegate should look like?
I have added the didReceiveRemoteNotification function to my AppDelegate. I also called application.registerForRemoteNotifications(). This is how my didReceiveRemoteNotification function looks like:
The print is not even coming for me.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Notification!!")
let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) as? CKDatabaseNotification
if notification != nil {
AppData.checkUpdates(finishClosure: {(result) in
OperationQueue.main.addOperation {
completionHandler(result)
}
})
}
}
Here are a few other things you can check:
= 1 =
Make sure the CloudKit container defined in your code is the same one you are accessing in the CloudKit dashboard. Sometimes we overlook what we selected in Xcode as the CloudKit container when we create and test multiple containers.
= 2 =
Check the Subscriptions tab in the CloudKit dashboard and make sure your Person subscription is being created when you launch your app. If you see it, try deleting it in the CK Dashboard and then run your app again and make sure it shows up again.
= 3 =
Check the logs in the CK Dashboard. They will show a log entry of type push whenever a push notification is sent. If it's logging it when you update/add a record in the CK Dashboard, then you know the issue lies with your device.
= 4 =
Remember that push notifications don't work in the iOS simulator. You need an actual device (or a Mac if you are making a macOS app).
= 5 =
Through extensive testing, I've found notifications are more reliable if you always set the alertBody even if it's blank. Like this:
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
info.alertBody = "" //This needs to be set or pushes don't always get sent
subscription.notificationInfo = info
= 6 =
For an iOS app, my app delegate handles notifications like this:
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Ask Permission for Notifications
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { authorized, error in
DispatchQueue.main.async {
if authorized {
UIApplication.shared.registerForRemoteNotifications()
}
}
})
return true
}
//MARK: Background & Push Notifications
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]{
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
if let sub = notification.subscriptionID{
print("iOS Notification: \(sub)")
}
}
//After we get permission, register the user push notifications
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
//Add your CloudKit subscriptions here...
}
}
Getting permission for notifications isn't required if you are only doing background pushes, but for anything the user sees in the form of a popup notification, you must get permission. If your app isn't asking for that permission, try deleting it off your device and building again in Xcode.
Good luck! : )
I am using RxCloudKit library, here's an a code snippet of how it handles query notifications -
public func applicationDidReceiveRemoteNotification(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
switch notification.notificationType {
case CKNotificationType.query:
let queryNotification = notification as! CKQueryNotification
self.delegate.query(notification: queryNotification, fetchCompletionHandler: completionHandler)
...
This method is called from func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
Before you can receive notifications, you will need to do the following -
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
application.registerForRemoteNotifications()
...
UPDATE:
Info.plist should contain the following -
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
Update: As Reinhard mentioned in his comment: you can in fact still subscribe to changes from the public database and manually import the changes into Core Data. Still I am unsure whether it is a good idea to rely on subscriptions in the public database if Apple specifically mentioned these differences
Original answer:
I don't think the accepted answer fully answers the question here.
Short answer would be that the CloudKit public database does not support subscriptions like the private database. Instead only a polling mechanism can be used. NSPersistentCloudKitContainer handles this automatically, but only updates very rarely.
This talk from WWDC2020 explains this in detail and I recommend watching it because there are other important details mentioned where public database differs from private database: https://developer.apple.com/wwdc20/10650
In the talk they mentioned thet a pull is initiated on each app start and after about 30 mins of application usage.

WCSession issue with Xcode 7.3

Helo
Before updating Xcode to the 7.3 version I had an app with an WatchOS 2 app, The watch app would call the func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
and the iOS app would pick the call and insert the passed value. All was fine.
But since updating to Xcode 7.3 one issue i noticed, the func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
is being called twice ONLY when the iOS app is launched for the first time, if the app is running or is in the background, that function is only called once.
If I pass the values 1, 5, and 10 and the iOS app is not running, the values 1, 5, 10, 1, 5, and 10 are added. But if the app is running in any form, the values 1, 5, and 10 are added.
Any idea why?
Here is the code from the WatchOS side, I did think of that myself, but according to my tests they are only called once. I have done many tests, and this is only happening when the iOS app is launched, not when its ruling in the background.
#IBAction func ConfirmButtonPressed() {
let applicationDict = ["Amount out": self.AmountText ]// Create a dict of application data
//applicationDict = ["status": "0" ]// Create a dict of application data
WCSession.defaultSession().transferUserInfo(applicationDict)
}
Here is the iOS app code from the app delegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if (WCSession.isSupported()) {
print("xyz3")
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
..........
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
var status = false
var AmountUILabel = ""
status = false
AmountUILabel = userInfo["Amount out"] as! String
print(userInfo["Amount out"] )
let i = NSString (string: AmountUILabel ).doubleValue
let when = NSDate()
let list :[AnyObject] = controller.viewControllers!
let j = list[1].topViewController as! AllEntriesTableViewController
j.AddAmount(i , date: when, what: "---", status: status)
}
I was able to figure out the answer after a whole day of research.
I should have started the didReceiveUserInfo with dispatch_async
That fixed it and increased up the communication speed between the watch app and the iOS one.
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
dispatch_async(dispatch_get_main_queue()) {

watchconnectivity not working on device - only simulator

I have followed many tutorials online and seen how to connect the watch with the iPhone to be able to send data and have the following code to:
Send:
watchSession.sendMessage(["name":"Maz"], replyHandler: nil) { (error) -> Void in
}
Receive:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
myLabel.setText(message["name"]! as? String)
//reloadWatchTable()
}
The code works when I use the simulator but not when I'm running on my iPhone and Apple Watch.

Resources