Быстрый; HTTP-запрос от UNUserNotificationCenter не работает: подключение к Интернету отключено

Я использую Xcode Version 9.4.1 (9F2000).

У меня есть этот код в 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()
}

Что он делает:

При получении push-уведомления и принудительном касании появляется текстовое поле и клавиатура (как известно из приложений для обмена сообщениями, таких как WhatsApp). Вы можете написать текст и отправить/отправить его.

Вы можете получить и распечатать отправленное сообщение с помощью этой строки:

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

Это работает без проблем.

Но при попытке отправить данные на мой сервер следующим образом:

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

это не работает. Нет доступа к моему серверу, и я получаю такие ошибки в журнале консоли:

Полученное текстовое сообщение: Первая попытка

2018-07-19 08:45:00.643935+0200 MyApp[4307:1502538] +[CATransactionsync] вызывается внутри транзакции

2018-07-19 08:45:00.644639+0200 MyApp[4307:1502538] +[CATransactionsync] вызывается внутри транзакции

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] Задача ‹1E8151BB-7098-46CD-9F68-8AA0E320CB7D>.‹1> Загрузка HTTP не удалась (код ошибки: -1009 [1:50])

Полученное текстовое сообщение: Вторая попытка

2018-07-19 08:45:13.094756+0200 MyApp[4307:1503029] Задача ‹1E8151BB-7098-46CD-9F68-8AA0E320CB7D>.‹1> завершена с ошибкой - код: -1009

2018-07-19 08:45:13.096208+0200 MyApp[4307:1502538] +[CATransactionsync] вызывается внутри транзакции

2018-07-19 08:45:13.096580+0200 MyApp[4307:1502538] +[CATransactionsync] вызывается внутри транзакции error=Optional(Error Domain=NSURLErrorDomain Code=-1009 "Подключение к Интернету отключено". UserInfo= {NSUnderlyingError=0x1cc047320 {Домен ошибки=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=Интернет-соединение отключено.})

Моя функция httpRequest(), кажется, работает, потому что я могу, например. вызовите его из didFinishLaunchingWithOptions следующим образом:

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

без каких-либо проблем. Это также означает, что мой домен и мой сервер работают нормально.

Но почему я не могу вызвать свою функцию httpRequest() из моей функции UNUserNotificationCenter?

При получении push-уведомления мое приложение, конечно, находится в фоновом режиме или закрыто. Нужен ли мне какой-то специальный код, чтобы заставить его работать в фоновом режиме или около того?


person David    schedule 17.07.2018    source источник


Ответы (1)


Вот мой рабочий код от 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
    }
}

Не забывайте:

– Создать push-сертификат

– Вы увидите токен своего устройства в журнале консоли.

– Добавьте "category":"allreply.action" в полезную нагрузку ваших приложений следующим образом:

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

Включите Push Notifications и Background Modes в Capabilities:

введите здесь описание изображения введите здесь описание изображения

Большое спасибо Разе К. из Freelancer.com!

person David    schedule 19.07.2018