Can someone please talk me through adding a button to a long-look local notification? I'm new to both the watch kit and notifications.
The long-look is functioning correctly. I'm setting up a UILocalNotification in my host app, setting the alertBody, category, userInfo, etc. and sending it off.
In my notification controller I'm setting everything up in didReceiveLocalNotification and it's working fine.
It seems, from my research, that I'm supposed to somehow add the button to the notification and use the method handleActionWithIdentifier: forLocalNotification but I'm unclear exactly how to do this.
I'm writing in Objective-C. Thanks
Here is notification payload I'm using:
{
"aps": {
"alert": {
"body": "Hi! How you doing? This is a test message. Press 'Reply' button to reply. Thanks!",
"title": "Petya"
},
"category": "newMessage"
},
"WatchKit Simulator Actions": [
{
"title": "Reply",
"identifier": "replyPressed"
}
],
}
Create custom notification subclass and override following method and don't forget to set that subclass in corresponding storyboard controller:
override func didReceiveRemoteNotification(remoteNotification: [NSObject : AnyObject], withCompletion completionHandler: (WKUserNotificationInterfaceType) -> Void) {
if let aps = remoteNotification["aps"] as? NSDictionary {
if let alert = aps["alert"] as? NSDictionary {
if let title = alert["title"] as? String {
titleLabel.setText(title)
}
if let body = alert["body"] as? String {
bodyLabel.setText(body)
}
}
}
completionHandler(WKUserNotificationInterfaceType.Custom)
}
After that in your notification custom button with title 'Reply' will appear. And when you press it it will launch watch app main interface controller and call handleActionWithIdentifier:localNotification: or handleActionWithIdentifier:remoteNotification:, depends on kind of notification you received. You have to override it like that:
override func handleActionWithIdentifier(identifier: String?, forRemoteNotification remoteNotification: [NSObject : AnyObject]) {
println("identifier: \(identifier)")
println("remoteNotification: \(remoteNotification)")
if identifier == "replyPressed" {
if let aps = remoteNotification["aps"] as? NSDictionary {
if let alert = aps["alert"] as? NSDictionary {
if let title = alert["title"] as? NSString {
let context = Context()
context.object = title
pushControllerWithName(kTCChatRoomControllerIdentifier, context: context)
}
}
}
}
}
P.S. Context is my own class for passing data between WKInterfaceController's subclasses
Hope this will help you!)
The Notification Essentials section of the Apple Watch Programming Guide describes how to add action buttons to Long-Look notifications for local notifications.
Basically, you need to create a UIMutableUserNotificationAction for every button that you want to add to your Long-Look interface, along with a category for your local notification. This is done in the App Delegate of your iOS app, when you register for notifications. Listing 15-1 in that guide shows exactly how to do it. Then, in the storyboard for your Apple Watch app, set your notification's category to the category you just created. The next section of the guide, "Responding to Taps in Action Buttons," should hopefully tell you everything you need to know in order to either launch your WatchKit app, or execute a task in the background on the iPhone.
Related
I have a Firebase childAdded event listener for the notifications table in my app that I would like to trigger push notifications when the app is in the background.
Here is the listener:
#objc func observeNotificationsChildAddedBackground() {
self.notificationsBackgroundHandler = FIREBASE_REF!.child("notifications/\(Defaults.userUID!)")
self.notificationsBackgroundHandler!.queryOrdered(byChild: "date").queryLimited(toLast: 99).observe(.childAdded, with: { snapshot in
let newNotificationJSON = snapshot.value as? [String : Any]
if let newNotificationJSON = newNotificationJSON {
let status = newNotificationJSON["status"]
if let status = status as? Int {
if status == 1 {
self.sendPushNotification()
}
}
}
})
}
func sendPushNotification() {
let content = UNMutableNotificationContent()
content.title = "Here is a new notification"
content.subtitle = "new notification push"
content.body = "Someone did something which triggered a notification"
content.sound = UNNotificationSound.default()
let request = UNNotificationRequest(identifier: "\(self.notificationBackgroundProcessName)", content: content, trigger: nil)
NotificationCenter.default.post(name: notificationBackgroundProcessName, object: nil)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request){ error in
if error != nil {
print("error sending a push notification :\(error?.localizedDescription)")
}
}
}
This works great when the app is in the foreground. I then add a background observer in my app delegates applicationWillResignActive method to observe it in the background:
func applicationWillResignActive(_ application: UIApplication) {
NotificationCenter.default.addObserver(self, selector: #selector(MainNavViewController.observeNotificationsChildAdded), name: notificationBackgroundProcessName, object: nil)
}
But the event observer doesn't fire when the app is in the background. Ive looked into Firebase Cloud Messenger to solve this problem and come across posts like this:
Is it possible to send PushNotifications, using Firebase Cloud Messaging (FCM) to special UDID, directly from device?
however I'm not trying to send a push notification from one user to another, I'm trying to activate a UNNotificationRequest from within the database listener.
I also found this post:
FCM background notifications not working in iOS
but again, this differs from my use case because its being used within the FIRMessaging framework.
So, is it possible to run this database listener when the app is in the background? If not, is it possible for me to make Firebase Cloud Messenger observe changes in a table and send a notification?
I had the same problem and came across your post months ago. I have managed to finally understand what is required to observe notifications in the background.
I have detailed the solution that I am using in this post https://stackoverflow.com/a/42240984/7048719
The technique is as follows
use a custom data service class
initialise a sharedInstance of the data service class
call the appropriate function in the data service class
sharedInstance from your initial view controller.
This way the observer stays in memory
Update: Apple have implemented updates in regards to background modes which have stopped this method from working.
Devices have their own way of managing background network calls so handling background push notifications like this will not work on real devices. The observers are removed from memory shortly after the app goes into the background. I was able to solve this by creating a Node.js server which observes the notifications table and handles push notifications using the Firebase-Messenger framework. It was actually pretty easy. I used this blog post as my guide:
Sending notifications between Android devices with Firebase Database and Cloud Messaging
This method will work however in the simulator (but not on real devices) so long as you pass the object containing the observer (the app delegate in this case) to background thread call like this:
func applicationWillResignActive(_ application: UIApplication) {
NotificationCenter.default.addObserver(self,
selector: #selector(self.observeNotificationsChildAddedBackground),
name: notificationBackgroundProcessName,
object: self)
}
Simple solution would be to use background fetch. You can also use background fetch to periodically fetch the table for new entries and show a notification if a new row is added.
This does not assure that notifications are received immediately. But implementation is much simpler compared to using an APN.
You can add background task to firebase observer
#objc func observeNotificationsChildAddedBackground() {
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
backgroundTask = UIApplication.shared.beginBackgroundTask {
[unowned self] in
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
assert(backgroundTask != UIBackgroundTaskInvalid)
self.notificationsBackgroundHandler = FIREBASE_REF!.child("notifications/\(Defaults.userUID!)")
self.notificationsBackgroundHandler!.queryOrdered(byChild: "date").queryLimited(toLast: 99).observe(.childAdded, with: { snapshot in
let newNotificationJSON = snapshot.value as? [String : Any]
if let newNotificationJSON = newNotificationJSON {
let status = newNotificationJSON["status"]
if let status = status as? Int {
if status == 1 {
self.sendPushNotification()
}
}
}
})
}
Hy I am new to iOS but somehow I manged to complete some tasks. I am working on app that sets reminders for user. So for this I am using Local notifications (UNUserNotificationCenterDelegate).
Everything is working good and fine. I have written some code, I am receiving notification at scheduled time and I have handled following cases.
When app is in background
When app is in forground.
My app handles these both cases perfectly or you can say as I needed. but I am helpless in following case
when the App is removed from recent, or not even running in
background at all,and a that time if the scheduled notification pops up, and user taps the notification, It opens the splash view controller then opens my app main view controller, where as I need to go to same view controller from where user set the scheduled time for reminder.
I think I am quite clear in what I want and what is happening. Is there any changes to do that. I know it can be possible as Whats App and other apps are also doing this. Please help me in doing this. ...
Note:
I am using UserNotifications (Local notification) and Deployment target is 10.3
Update:
I saw this link has same need as mine but I do not know what the selected answer suggest as I am new to iOS so I am not sure what and how to do:
So, your problem is when the app is killed or inactive and then when user tap the notification the reminder screen will show up, right?
Here's the case:
Notification shows (inactive/killed) -> tap notification -> splash -> reminder screen.
You should save your data that you want to show in notification. iOS will save any notification data in remoteNotification.
So, when user opens the app from inactive, the first thing that will be called is launchOption in AppDelegate.
Here's the example:
if launchOptions != nil {
// get data from notificaioton when app is killed or incative
if let userInfo = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary {
// Do what you want, you can set set the trigger to move it the screen as you want, for your case is to reminder screen
}
}
When your app launches via LocalNotification your UNUserNotificationCenterDelegate method userNotificationCenter didReceive response will be called.
So I would recommend you to present your notification on top of current view controller as like below approach.
//Add this extension in any of your class
extension UIApplication {
#objc class func topViewController(_ base: UIViewController?) -> UIViewController? {
var baseController = base
if baseController == nil{
baseController = UIApplication.shared.keyWindow?.rootViewController
}
if let nav = baseController as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = baseController as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
return baseController
}
}
//In your `userNotificationCenter didReceive response` method
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "YourIdentifier"{
let controllerToPresent = MyNotificationViewController(nibName: "MyNotificationViewController", bundle: nil)
controllerToPresent.notificationInfo = response.notification.request.content.userInfo
//If navigation flow needed, add controllerToPresent to nav controller
let navConroller = UINavigationController(rootViewController: controllerToPresent)
UIApplication.topViewController(nil)?.present(navConroller, animated: true, completion: nil)
//If navigation flow not needed, present directly
UIApplication.topViewController(nil)?.present(controllerToPresent, animated: true, completion: nil)
}
completionHandler()
}
CloudKit manages my notifications (not my dedicated server)
My first device changes something in CloudKit Container and pushes notification.
... but on my second device my app is currently running in background mode. So, the notification arrives to device with Alert, but the app itself doesn't know about it.
What is the elegant and effective way to catch this one missed notification (or even more) when the app goes back to the foreground mode?
Suppose the change is related to my top visible controller, and I would like to apply that change without fetching anything on viewDidAppear:.
Simply you can do the following, implemented inside UIApplicationDelegate method:
func applicationWillEnterForeground(application: UIApplication) {
var queryNotifications = [CKQueryNotification]()
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
operation.notificationChangedBlock = { notification in
if let queryNotification = notification as? CKQueryNotification {
queryNotifications.append(queryNotification)
}
}
operation.fetchNotificationChangesCompletionBlock = { token, error in
var notificationIdentifiers = [CKNotificationID]()
for queryNotification in queryNotifications {
let recordID = queryNotification.recordID!
//here you can do enything you need with your recordID
container.publicCloudDatabase.fetchRecordWithID(recordID, completionHandler: { object, error in
notificationIdentifiers.append(queryNotification.notificationID!)
if queryNotifications.count == notificationIdentifiers.count {
let operationQueue = NSOperationQueue()
operationQueue.addOperation(CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIdentifiers))
}
})
}
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(operation)
}
I'm trying to do a little messenger, I would like to display an alert saying that a new message has been received everywhere on the app except on the conversation itself. For now my alert is displayed everywhere, is it possible to filter it so it is not displayed on the ConversationDetailController?
Here is my code in appdelegate
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
var mess:String?
var displayName:String?
if let result = userInfo["aps"] as? NSDictionary {
if let alert = result["alert"] as? String {
mess = alert
if let extra = userInfo["extra"] as? NSDictionary
{
if let name = extra["displayName"] as? String
{
displayName = name
var alert = UIAlertView(title: "You have a new message from \(displayName!)", message: mess, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
}
NSNotificationCenter.defaultCenter().postNotificationName("newMessageReceived", object: nil)
}
}
}
You could do something along the lines of:
Create a new class which is an alert presentation manager
Create an instance of this class when you receive a notification
Move all of the alert logic into that class
The app delegate posts the newMessageReceived notification and passes the presentation manager as the object
Anyone who observes the notification can interrogate the presentation manager for details about the notification and cancel the presentation
If anyone cancels then the notification isn't shown
After posting the notification the app delegate asks the presentation manager to presentAlertIfAppropriate (which checks if it's been cancelled)
Notifications are sent to all observers directly, so at this point in time you can post the notification and immediately check if presentation is required. It may be better to allow a little time for recipients of the notification to decide about cancellation but things get a good deal more complicated if you need to do something like that...
I'd like to add to my Watch app functionality which send to iPhone app a Local Notification (while iPhone app is on the background or iPhone is locked).
I know how to create Local Notification itself.
What Im asking for is way, how to trigger background process (which contains also Local Notification) on iPhone by (for example) tapping on button on Apple Watch.
WKInterfaceController.openParentApplication is the official way to communicate with the iPhone. Documentation.
You pass parameters in the userInfo dictionary and retrieve results via the reply block.
On the iPhone the request is handled by appDelegate's handleWatchKitExtensionRequest method. Documentation
Code in my InterfaceController.swift:
#IBAction func btn() {
sendMessageToParentApp("Button tapped")
}
// METHODS #2:
func sendMessageToParentApp (input:String) {
let dictionary = ["message":input]
WKInterfaceController.openParentApplication(dictionary, reply: { (replyDictionary, error) -> Void in
if let castedResponseDictionary = replyDictionary as? [String:String], responseMessage = castedResponseDictionary["message"] {
println(responseMessage)
self.lbl.setText(responseMessage)
}
})
}
Next i made new method in my AppDelegate.swift:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
if let infoDictionary = userInfo as? [String:String], message = infoDictionary["message"] {
let response = "iPhone has seen this message." // odešle se string obsahující message (tedy ten String)
let responseDictionary = ["message":response] // tohle zase vyrobí slovník "message":String
NSNotificationCenter.defaultCenter().postNotificationName(notificationWatch, object: nil)
reply(responseDictionary)
}
}
As you can see I use Notification to get iOS app know that button has been tapped. In ViewController.swift I have Notification Observer and function which is executed every time observer catch notification that user tapped on button on watch ("notificationWatch" is global variable with notification key). Hope this will help to anybody.