0

I'm using Xcode Version 9.4.1 (9F2000).

I have this code in AppDelegate.swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    showPushButtons()
    return true
}
func httpRequest(file: String, postKey1: String, postValue1: String, postKey2: String, postValue2: String) {
    let url = URL(string: "https://www.example.com/\(file)")!
    var request = URLRequest(url: url)
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    let postString = "\(postKey1)=\(postValue1)&\(postKey2)=\(postValue2)"
    request.httpBody = postString.data(using: .utf8)
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, error == nil else {
            print("error=\(String(describing: error))")
            return
        }
        if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(String(describing: response))")
        }

        let responseString = String(data: data, encoding: .utf8)
        print("responseString = \(String(describing: responseString))")
    }
    task.resume()
}
func showPushButtons(){
    let replyAction = UNTextInputNotificationAction(
        identifier: "reply.action",
        title: "Reply to message",
        textInputButtonTitle: "Send",
        textInputPlaceholder: "Write some text here")

    let pushNotificationButtons = UNNotificationCategory(
        identifier: "allreply.action",
        actions: [replyAction],
        intentIdentifiers: [],
        options: [])

    UNUserNotificationCenter.current().setNotificationCategories([pushNotificationButtons])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    if  response.actionIdentifier  ==  "reply.action" {
        if let textResponse =  response as? UNTextInputNotificationResponse {
            let sendText =  textResponse.userText
            print("Received text message: \(sendText)")
            httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "Peter")
        }
    }
    completionHandler()
}

What it does:

When receiving a push notification and making a force touch, a textfield and the keyboard will appear (as known from messaging apps like WhatsApp). You can write some text and submit/send it.

You can get and print that submitted message with this line:

print("Received text message: \(sendText)")

This is working without any problems.

But when trying to send the data to my server like this:

httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")

it's not working. There's no access to my server and I'm getting errors like this in console log:

Received text message: First try

2018-07-19 08:45:00.643935+0200 MyApp[4307:1502538] +[CATransaction synchronize] called within transaction

2018-07-19 08:45:00.644639+0200 MyApp[4307:1502538] +[CATransaction synchronize] called within transaction

2018-07-19 08:45:13.091958+0200 MyApp[4307:1502647] TIC TCP Conn Failed [1:0x1c4169a80]: 1:50 Err(50)

2018-07-19 08:45:13.093089+0200 MyApp[4307:1502647] Task <1E8151BB-7098-46CD-9F68-8AA0E320CB7D>.<1> HTTP load failed (error code: -1009 [1:50])

Received text message: Second try

2018-07-19 08:45:13.094756+0200 MyApp[4307:1503029] Task <1E8151BB-7098-46CD-9F68-8AA0E320CB7D>.<1> finished with error - code: -1009

2018-07-19 08:45:13.096208+0200 MyApp[4307:1502538] +[CATransaction synchronize] called within transaction

2018-07-19 08:45:13.096580+0200 MyApp[4307:1502538] +[CATransaction synchronize] called within transaction error=Optional(Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={NSUnderlyingError=0x1cc047320 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://www.example.com/message.php, NSErrorFailingURLKey=https://www.example.com/message.php, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=50, NSLocalizedDescription=The Internet connection appears to be offline.})

My function httpRequest() seems to work because I can e.g. call it from didFinishLaunchingWithOptions like this:

httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")

without any problem. That also means that my domain and my server are working fine.

But why can't I call my httpRequest() function from my UNUserNotificationCenter function?

When receiving a push notification, my app is of course in background or closed. Do I need some special code to make it work in background mode or so?

David
  • 2,898
  • 3
  • 21
  • 57

1 Answers1

0

Here is my working code from AppDelegate.swift:

//  AppDelegate.swift

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    var window: UIWindow?
    var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        pushAction()
        return true
    }
    func registerForPushNotifications() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
            (granted, error) in
            print("\nPermission granted: \(granted)\n")
            self.pushAction()
            guard granted else { return }
            self.getNotificationSettings()
        }
    }
    func getNotificationSettings() {
        UNUserNotificationCenter.current().getNotificationSettings { (settings) in
            print("\nNotification settings: \(settings)\n")
            guard settings.authorizationStatus == .authorized else { return }
            DispatchQueue.main.async(execute: {
                UIApplication.shared.registerForRemoteNotifications()
            })
        }
    }
    func httpRequest(file: String, postKey1: String, postValue1: String, postKey2: String, postValue2: String) {
        let url = URL(string: "https://www.example.com/\(file)")!
        var request = URLRequest(url: url)
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        let postString = "\(postKey1)=\(postValue1)&\(postKey2)=\(postValue2)"
        request.httpBody = postString.data(using: .utf8)
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data, error == nil else {
                print("\nerror=\(String(describing: error))\n")
                return
            }
            if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
                print("\nstatusCode should be 200, but is \(httpStatus.statusCode)\n")
                print("\nresponse = \(String(describing: response))\n")
            }
            let responseString = String(data: data, encoding: .utf8)
            print("\nresponseString = \(String(describing: responseString))\n")
        }
        task.resume()
    }
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let tokenParts = deviceToken.map { data -> String in
            return String(format: "%02.2hhx", data)
        }
        let token = tokenParts.joined()
        print("\nDevice Token: \(token)\n")
    }
    func application(_ application: UIApplication,
                     didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("\nFailed to register: \(error)\n")
    }
    func pushAction(){
        let replyAction = UNTextInputNotificationAction(
            identifier: "reply.action",
            title: "Reply to message",
            options:[],
            textInputButtonTitle: "Send",
            textInputPlaceholder: "Input/write text here")

        let pushNotificationButtons = UNNotificationCategory(
            identifier: "allreply.action",
            actions: [replyAction],
            intentIdentifiers: [],
            options: [])

        UNUserNotificationCenter.current().setNotificationCategories([pushNotificationButtons])
    }
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
//        If you don’t want to show notification when app is open, do something else here and make a return here.
//        Even if you don’t implement this delegate method, you will not see the notification on the specified controller. So, you have to implement this delegate and make sure the below line execute. i.e. completionHandler.
        completionHandler([.sound,.alert,.badge])
    }
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

        if response.actionIdentifier  ==  "reply.action" {
            if let textResponse =  response as? UNTextInputNotificationResponse {
                if UIApplication.shared.applicationState != .active{
                    self.registerBackgroundTask()
                }
                let sendText =  textResponse.userText
                print("\nReceived text message: \(sendText)\n")
                DispatchQueue.global(qos: .background).async {
                    self.httpRequest(file: "message.php", postKey1: "message", postValue1: sendText, postKey2: "user", postValue2: "Peter")
                }
            }
        }
        completionHandler()
    }
    func  registerBackgroundTask() {
        backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
            self?.endBackgroundTask()
        }
        assert(backgroundTask != UIBackgroundTaskInvalid)
    }
    func endBackgroundTask() {
        print("\nBackground task ended.\n")
        UIApplication.shared.endBackgroundTask(backgroundTask)
        backgroundTask = UIBackgroundTaskInvalid
    }
}

Don't forget:

– Create a push certificate

– You'll see your device token in console log

– Add "category":"allreply.action" in your aps payload like this:

{
    "aps":{
        "alert":{
            "title":"Hello",
            "body":"This is a test!"
            },
            "badge":0,
            "sound":"default",
            "category":"allreply.action"
    }
}

Enable Push Notifications and Background Modes in Capabilities:

enter image description here enter image description here

Big thank you to Raza K. from Freelancer.com!

David
  • 2,898
  • 3
  • 21
  • 57