Swift;来自 UNUserNotificationCenter 的 HTTP 请求不起作用:互联网连接似乎处于离线状态
Swift; HTTP request from UNUserNotificationCenter not working: The Internet connection appears to be offline
我正在使用 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()
}
它的作用:
当收到推送通知并用力触摸时,将出现一个文本字段和键盘(如 WhatsApp 等消息应用程序所示)。你可以写一些文字然后 submit/send 它。
您可以使用此行获取并打印提交的消息:
print("Received text message: \(sendText)")
这没有任何问题。
但是当尝试像这样将数据发送到我的服务器时:
httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")
它不工作。无法访问我的服务器,我在控制台日志中收到类似这样的错误:
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.})
我的功能 httpRequest()
似乎可以工作,因为我可以,例如从 didFinishLaunchingWithOptions
中这样调用它:
httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")
没有任何问题。这也意味着我的域和我的服务器工作正常。
但为什么我不能从我的 UNUserNotificationCenter
函数调用我的 httpRequest()
函数?
收到推送通知时,我的应用程序当然在后台或关闭。我是否需要一些特殊代码才能使其在后台模式下工作?
这是我的工作代码 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
}
}
别忘了:
– 创建推送证书
– 您将在控制台日志中看到您的设备令牌
– 在您的 aps 负载中添加 "category":"allreply.action"
,如下所示:
{
"aps":{
"alert":{
"title":"Hello",
"body":"This is a test!"
},
"badge":0,
"sound":"default",
"category":"allreply.action"
}
}
在Capabilities
中启用Push Notifications
和Background Modes
:
我正在使用 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()
}
它的作用:
当收到推送通知并用力触摸时,将出现一个文本字段和键盘(如 WhatsApp 等消息应用程序所示)。你可以写一些文字然后 submit/send 它。
您可以使用此行获取并打印提交的消息:
print("Received text message: \(sendText)")
这没有任何问题。
但是当尝试像这样将数据发送到我的服务器时:
httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")
它不工作。无法访问我的服务器,我在控制台日志中收到类似这样的错误:
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.})
我的功能 httpRequest()
似乎可以工作,因为我可以,例如从 didFinishLaunchingWithOptions
中这样调用它:
httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")
没有任何问题。这也意味着我的域和我的服务器工作正常。
但为什么我不能从我的 UNUserNotificationCenter
函数调用我的 httpRequest()
函数?
收到推送通知时,我的应用程序当然在后台或关闭。我是否需要一些特殊代码才能使其在后台模式下工作?
这是我的工作代码 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
}
}
别忘了:
– 创建推送证书
– 您将在控制台日志中看到您的设备令牌
– 在您的 aps 负载中添加 "category":"allreply.action"
,如下所示:
{
"aps":{
"alert":{
"title":"Hello",
"body":"This is a test!"
},
"badge":0,
"sound":"default",
"category":"allreply.action"
}
}
在Capabilities
中启用Push Notifications
和Background Modes
: