Flutter NFC iOS Mifare UID reading - ios

Guys, I need your help, please.
What do I need: to get the tag's UID using iOS app
What do I have: I have an app Flutter, which works fine on Android (I can read the tag's UID) and do nothing on iOS
Details:
I have this type of cards (plastic card)
card's data, Mifare Classis 1k
As you can see, this is the Mifare Classic 1k card with payload "My text with spaces"
I used flutter channels to communicate between flutter and iOS native (swift)
On iOS side I created two implementations: one for NFCTagReaderSession, another one for NFCNDEFReaderSession (I do not use it at the same time, only separated)
1. NFCTagReaderSession implementation
import UIKit
import Flutter
import CoreNFC
#available(iOS 13.0, *)
var session_tag: NFCTagReaderSession?
#available(iOS 13.0, *)
var flutterResult: FlutterResult!
#available(iOS 13.0, *)
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate, NFCTagReaderSessionDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let nfcChannel = FlutterMethodChannel(name: "samples.flutter.dev/nfc", binaryMessenger: controller.binaryMessenger)
nfcChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
// Note: this method is invoked on the UI thread.
guard call.method == "getNFCTag" else {
result(FlutterMethodNotImplemented)
return
}
if NFCTagReaderSession.readingAvailable {
print("we are ready for reading");
self?.startReadingNFC(result: result)
} else {
print("sorry byt not working");
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func startReadingNFC(result: #escaping FlutterResult) {
flutterResult = result
session_tag = NFCTagReaderSession(pollingOption: .iso14443, delegate: self, queue: DispatchQueue.main)
session_tag?.alertMessage = "Hold ur phone!"
session_tag?.begin()
}
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
print("Tag session active")
}
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
print("Did detect tag reader session")
print(tags.count)
if case let NFCTag.miFare(tag) = tags.first! {
session.connect(to: tags.first!) { (error: Error?) in
let tagUIDData = tag.identifier
var byteData: [UInt8] = []
tagUIDData.withUnsafeBytes { byteData.append(contentsOf: $0) }
session.invalidate()
print(self.hexEncodedString(byteArray: byteData))
// DispatchQueue.main.async {
//flutterResult(self.hexEncodedString(byteArray: byteData))
// }
}
}
}
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
print("Tag Error: \(error.localizedDescription)")
}
func hexEncodedString(byteArray: [UInt8]) -> String {
let hexDigits = Array("0123456789abcdef".utf16)
var hexChars = [UTF16.CodeUnit]()
hexChars.reserveCapacity(byteArray.count * 2)
for byte in byteArray {
let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
hexChars.append(hexDigits[index1])
hexChars.append(hexDigits[index2])
}
return String(utf16CodeUnits: hexChars, count: hexChars.count)
}
}
When i'm pressing the button in my app, i can see the NFC reading window on my phone
iOS app NFC reading window
But, when i put the card up to a phone, there is noting happened. Android Studio console
Android studio console
2. NFCNDEFReaderSession
import UIKit
import Flutter
import CoreNFC
#available(iOS 13.0, *)
var session_tag: NFCNDEFReaderSession?
#available(iOS 13.0, *)
var flutterResult: FlutterResult!
#available(iOS 13.0, *)
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate, NFCNDEFReaderSessionDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let nfcChannel = FlutterMethodChannel(name: "samples.flutter.dev/nfc", binaryMessenger: controller.binaryMessenger)
nfcChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
// Note: this method is invoked on the UI thread.
guard call.method == "getNFCTag" else {
result(FlutterMethodNotImplemented)
return
}
if NFCNDEFReaderSession.readingAvailable {
print("we are ready for reading");
self?.startReadingNFC(result: result)
} else {
print("sorry byt not working");
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func startReadingNFC(result: #escaping FlutterResult) {
print("start reading session")
flutterResult = result
// session_tag = NFCNDEFReaderSession(pollingOption: [.iso14443], delegate: self, queue: DispatchQueue.main)
session_tag = NFCNDEFReaderSession(delegate: self, queue: DispatchQueue.main, invalidateAfterFirstRead: false)
session_tag?.alertMessage = NSLocalizedString("Hold it!", comment: "my comment")
session_tag?.begin()
}
func tagReaderSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
print("Tag session active")
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
print(messages)
for message in messages {
for record in message.records {
print("next message")
print(record)
}
}
}
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
if (tags.count > 0) {
session.connect(to: tags.first!, completionHandler: { (connection_error) in
tags.first?.readNDEF(completionHandler: { (message, error) in
if ((message?.records.count)! > 0) {
if let dataMessage = String(data: (message?.records.first!.payload)!, encoding:.utf8) {
session.invalidate()
print(dataMessage)
}
}
})
})
}
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print("Tag Error: \(error.localizedDescription)")
}
func tagReaderSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print("Tag Error: \(error.localizedDescription)")
// DispatchQueue.main.async {
// session.invalidate()
// print(error.localizedDescription)
// flutterResult(error.localizedDescription)
// }
}
}
After pressing the button in the app, NFC reading window also shows up. And with the card i can get the payload
Message: My message with spaces
Both approaches start fine and open the iOS's window with "read a tag", but with NFCTagReaderSession nothing is happening. It seems like there is no card. With NFCNDEFReaderSession implementation, I can read the message "My text with spaces" but, of course, I can't read the tag's UID
Am'I doing something wrong to get the UID of the card?

Related

When entering the application, no ads is displayed

I'm trying to display an ad with GADAppOpenAd
The problem is, every initial login to the application, no advertisement is displayed, only when I go to the background and return to the application, then an advertisement appears,
I'm trying to have an advertisement appear on the initial login as well
var appOpenAd: GADAppOpenAd?
var loadTime = Date()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
GADMobileAds.sharedInstance().start(completionHandler: nil)
requestAppOpenAd()
return true
}
extension AppDelegate:GADFullScreenContentDelegate {
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
requestAppOpenAd()
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
requestAppOpenAd()
}
func requestAppOpenAd() {
let request = GADRequest()
GADAppOpenAd.load(withAdUnitID: "ca-app-pub-3940256099942544/5662855259",
request: request,
orientation: UIInterfaceOrientation.portrait,
completionHandler: { (appOpenAdIn, _) in
self.appOpenAd = appOpenAdIn
self.appOpenAd?.fullScreenContentDelegate = self
self.loadTime = Date()
print("Ad is ready")
})
}
func tryToPresentAd() {
guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
return
}
if let gOpenAd = self.appOpenAd, wasLoadTimeLessThanNHoursAgo(thresholdN: 4){
gOpenAd.present(fromRootViewController: rootViewController)
} else {
self.requestAppOpenAd()
}
}
func wasLoadTimeLessThanNHoursAgo(thresholdN: Int) -> Bool {
let now = Date()
let timeIntervalBetweenNowAndLoadTime = now.timeIntervalSince(self.loadTime)
let secondsPerHour = 3600.0
let intervalInHours = timeIntervalBetweenNowAndLoadTime / secondsPerHour
return intervalInHours < Double(thresholdN)
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidBecomeActive(_ scene: UIScene) {
(UIApplication.shared.delegate as? AppDelegate)?.tryToPresentAd()
}
}

BGAppRefreshTask never called

BGAppRefreshTask is not working, I marked the option of background fetch capability
The method inside BGTaskScheduler.shared.register is never called
my code:
private let appRefreshTaskId = "internetDisconnectionsId"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13, *) {
BGTaskScheduler.shared.register(forTaskWithIdentifier: appRefreshTaskId, using: nil) { task in
self.handleAppRefreshTask(task: task as! BGAppRefreshTask)
}
}
return true
}
func handleAppRefreshTask(task: BGAppRefreshTask) {
task.expirationHandler = {
print("Background Working")
task.setTaskCompleted(success: true)
}
}
#available(iOS 13.0, *)
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: appRefreshTaskId)
request.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60) // Refresh after 2 minutes.
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh task \(error.localizedDescription)")
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as! AppDelegate).scheduleAppRefresh()
print("called")
}
I would love to understand where I am wrong

Apple Watch Companion App: sendMessage doesn't work with quit iOS App

I'm currently building an Apple Watch Companion App with Swift and Flutter. I'm doing this with the help of theamorn's Github project. Everything works in the simulator (iOS 15.0 and WatchOS 8.0), even if the iOS App is force quit. However, when testing on my AW Series 3 (WatchOS 8.0) and iPhone 11 (iOS 15.0) it will only work, as long as the iOS App is opened.
My AppDelegate.swift of the iOS App
import UIKit
import Flutter
import WatchConnectivity
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
var session: WCSession?
let methodChannelName: String = "app.controller.watch"
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
initFlutterChannel()
if WCSession.isSupported() {
session = WCSession.default;
session!.delegate = self;
session!.activate();
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func initFlutterChannel() {
if let controller = window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: methodChannelName,
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({ [weak self] (
call: FlutterMethodCall,
result: #escaping FlutterResult) -> Void in
switch call.method {
case "flutterToWatch":
guard let watchSession = self?.session, watchSession.isPaired,
watchSession.isReachable, let methodData = call.arguments as? [String: Any],
let method = methodData["method"], let data = methodData["data"] as? Any else {
result(false)
return
}
let watchData: [String: Any] = ["method": method, "data": data]
watchSession.sendMessage(watchData, replyHandler: nil, errorHandler: nil)
result(true)
default:
result(FlutterMethodNotImplemented)
}
})
}
}
}
extension AppDelegate: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionReachabilityDidChange(_ session: WCSession) {
print("Watch reachability: \(session.isReachable)")
if (session.isReachable) {
//invoke sendWakeupToFlutter via MethodChannel when reachability is true
DispatchQueue.main.async {
if let controller = self.window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: self.methodChannelName,
binaryMessenger: controller.binaryMessenger)
channel.invokeMethod("sendWakeupToFlutter", arguments: [])
}
}
}
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.async {
if let method = message["method"] as? String, let controller = self.window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: self.methodChannelName,
binaryMessenger: controller.binaryMessenger)
channel.invokeMethod(method, arguments: message)
}
}
}
}
My WatchViewModel.swift in my Watch Extension
import Foundation
import WatchConnectivity
class WatchViewModel: NSObject, ObservableObject {
var session: WCSession
var deviceList: String = ""
#Published var loading: Bool = false
#Published var pubDeviceList: [Device]?
// Add more cases if you have more receive method
enum WatchReceiveMethod: String {
case sendLoadingStateToNative
case sendSSEDeviceListToNative
}
// Add more cases if you have more sending method
enum WatchSendMethod: String {
case sendWakeupToFlutter
case sendCloseToFlutter
}
init(session: WCSession = .default) {
self.session = session
super.init()
self.session.delegate = self
session.activate()
}
func sendDataMessage(for method: WatchSendMethod, data: [String: Any] = [:]) {
sendMessage(for: method.rawValue, data: data)
}
}
extension WatchViewModel: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionReachabilityDidChange(_ session: WCSession) {
print("iPhone reachability: \(session.isReachable)")
if(session.isReachable) {
//invoke sendWakeupToFlutter via sendMessage when reachability is true
sendDataMessage(for: .sendWakeupToFlutter)
}
}
// Receive message From AppDelegate.swift that send from iOS devices
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.sync {
guard let method = message["method"] as? String, let enumMethod = WatchReceiveMethod(rawValue: method) else {
return
}
switch enumMethod {
case .sendLoadingStateToNative:
self.loading = (message["data"] as? Bool) ?? false
case .sendSSEDeviceListToNative:
self.deviceList = (message["data"] as? String) ?? ""
let data = self.deviceList.data(using: .utf8)!
do {
self.pubDeviceList = try JSONDecoder().decode([Device].self, from: data)
} catch let error {
print(error)
}
}
}
}
func sendMessage(for method: String, data: [String: Any] = [:]) {
guard session.isReachable else {
print("ios not reachable")
return
}
print("ios is reachable")
let messageData: [String: Any] = ["method": method, "data": data]
let callDepth = 10
session.sendMessage(messageData, replyHandler: nil, errorHandler: nil)
}
}
Does someone know how to fix this? Thanks in advance!
EDIT:
The change in my watch extension so far:
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if (activationState == WCSessionActivationState.activated) {
sendDataMessage(for: .sendWakeupToFlutter)
}
}
NOTE: The accepted answer didn't solve my problem entirely, but improved the situation. I ended up making a rather independent Watch App w/o using Flutter MethodChannels.
It probably won't fix your issue but you should be careful about using session.isReachable - its value is only valid for a session that is activated, which it almost certainly is (activate session is async but quick I think), but WatchOS has a number of APIs where the value can only be trusted if certain conditions are met, and you should check, otherwise you end up with a value like true or false when the real value should be 'don't know'
IIRC isReachable is normally true from watch to counterpart iPhone app, you should consider sending the wake up proactively when the session activates.
tl;dr It should probably work when the quit app was built for release.
Long Version
I had the same issue. What I found out is that it works fine when building the flutter app in release mode (rather than just running it via XCode/non release mode).
The reason for that is that, when opening a quit app that wasn't built for release, you'll see the screen that you need to run it via a Flutter IDE or XCode. Which indicates that the app is not properly launched when trying to launch a non-release app via the home screen. That is why the message that is being sent from the watch will probably not reach the iOS WCSession or make it through the platform channel.

BackgroundTask iOS 13 Swift

I want to run the function in the background. But I get an error. The error I received is
"nw_connection_receive_internal_block_invoke.
Why am I getting this problem? I'm trying it on an iOS 13 device. But I can't run the application in the background. I added a background run feature in Info.plist. I want to run the interator.tickTimer function in the background. But I'm getting an error. Does not work in the background. In the background, I want to periodically retrieve data from firebase.
import BackgroundTasks
#available(iOS 13.0, *)
extension AppDelegate {
func cancelAllPandingBGTask() {
BGTaskScheduler.shared.cancelAllTaskRequests()
}
func scheduleImageFetcher() {
let request = BGProcessingTaskRequest(identifier: "....")
request.requiresNetworkConnectivity = true // Need to true if your task need to network process. Defaults to false.
request.requiresExternalPower = false
request.earliestBeginDate = Date(timeIntervalSinceNow: 40)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule image featch: \(error)")
}
}
func handleAppRefreshTask(task: BGAppRefreshTask) {
task.expirationHandler = {
}
DispatchQueue.main.async {
let interator = MainTableViewController()
interator.tickTimer()
}
task.setTaskCompleted(success: true)
}
func handleImageFetcherTask(task: BGProcessingTask) {
scheduleImageFetcher() // Recall
//Todo Work
task.expirationHandler = {
}
task.setTaskCompleted(success: true)
}
}
xtension AppDelegate {
func registerLocalNotification() {
let notificationCenter = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .sound, .badge]
notificationCenter.requestAuthorization(options: options) {
(didAllow, error) in
if !didAllow {
// print("User has declined notifications")
}
}
}
func scheduleLocalNotification() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getNotificationSettings { (settings) in
if settings.authorizationStatus == .authorized {
self.fireNotification()
}
}
}
func fireNotification() {
// Create Notification Content
let notificationContent = UNMutableNotificationContent()
// Configure Notification Content
notificationContent.title = "Bg"
notificationContent.body = "BG Notifications."
// Add Trigger
let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 1.0, repeats: false)
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: "local_notification", content: notificationContent, trigger: notificationTrigger)
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
}
}
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13.0, *) {
registerBackgroundTaks()
registerLocalNotification()
} else {
// Fallback on earlier versions
}
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
if #available(iOS 13.0, *) {
cancelAllPandingBGTask()
scheduleImageFetcher()
} else {
// Fallback on earlier versions
}
}
//MARK: Regiater BackGround Tasks
#available(iOS 13.0, *)
private func registerBackgroundTaks() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "....-...", using: nil) { task in
//This task is cast with processing request (BGProcessingTask)
self.scheduleLocalNotification()
self.handleImageFetcherTask(task: task as! BGProcessingTask)
}
}

REST request from iOS background URLSession using APNs

Update 2018-05-25:
I replaced datatask with downloadTask after reading Rob's answer here: https://stackoverflow.com/a/44140059/4666760 . It still does not work when the app is backgrounded.
Hello
I need some help with iOS background tasks. I want to use Apple Push Notification service (APNs) to wake up my app in the background so that it can do a simple RESTful API call to my server. I am able to make it work when the app is in the foreground, but not in the background. I think I do something wrong with the configuration of the URLSession, but I don't know. The entire code for the app and the server is at my repo linked below. Please, clone it and do whatever you like - I just want your help :)
https://github.com/knutvalen/ping
In AppDelegate.swift the app listen for remote notifications:
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
// MARK: - Properties
var window: UIWindow?
// MARK: - Private functions
private func registerForPushNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
guard granted else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
// MARK: - Delegate functions
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Login.shared.username = "foo"
registerForPushNotifications()
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { data -> String in
return String(format: "%02.2hhx", data)
}
let token = tokenParts.joined()
os_log("AppDelegate application(_:didRegisterForRemoteNotificationsWithDeviceToken:) token: %#", token)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
os_log("AppDelegate application(_:didFailToRegisterForRemoteNotificationsWithError:) error: %#", error.localizedDescription)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
os_log("AppDelegate application(_:didReceiveRemoteNotification:fetchCompletionHandler:)")
if let aps = userInfo["aps"] as? [String: AnyObject] {
if aps["content-available"] as? Int == 1 {
RestController.shared.onPing = { () in
RestController.shared.onPing = nil
completionHandler(.newData)
os_log("AppDelegate onPing")
}
RestController.shared.pingBackground(login: Login.shared)
// RestController.shared.pingForeground(login: Login.shared)
}
}
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
RestController.shared.backgroundSessionCompletionHandler = completionHandler
}
}
The RestController.swift handles URLSessions with background configurations:
class RestController: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate {
// MARK: - Properties
static let shared = RestController()
let identifier = "no.qassql.ping.background"
let ip = "http://123.456.7.89:3000"
var backgroundUrlSession: URLSession?
var backgroundSessionCompletionHandler: (() -> Void)?
var onPing: (() -> ())?
// MARK: - Initialization
override init() {
super.init()
let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
configuration.isDiscretionary = false
configuration.sessionSendsLaunchEvents = true
backgroundUrlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
// MARK: - Delegate functions
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let completionHandler = self.backgroundSessionCompletionHandler {
self.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
os_log("RestController urlSession(_:task:didCompleteWithError:) error: %#", error.localizedDescription)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
do {
let data = try Data(contentsOf: location)
let respopnse = downloadTask.response
let error = downloadTask.error
self.completionHandler(data: data, response: respopnse, error: error)
} catch {
os_log("RestController urlSession(_:downloadTask:didFinishDownloadingTo:) error: %#", error.localizedDescription)
}
}
// MARK: - Private functions
private func completionHandler(data: Data?, response: URLResponse?, error: Error?) {
guard let data = data else { return }
if let okResponse = OkResponse.deSerialize(data: data) {
if okResponse.message == ("ping_" + Login.shared.username) {
RestController.shared.onPing?()
}
}
}
// MARK: - Public functions
func pingBackground(login: Login) {
guard let url = URL(string: ip + "/ping") else { return }
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 20)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = login.serialize()
if let backgroundUrlSession = backgroundUrlSession {
backgroundUrlSession.downloadTask(with: request).resume()
}
}
func pingForeground(login: Login) {
guard let url = URL(string: ip + "/ping") else { return }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = login.serialize()
URLSession.shared.dataTask(with: request) { (data, response, error) in
return self.completionHandler(data: data, response: response, error: error)
}.resume()
}
}
By adding App provides Voice over IP services as Required Background Mode in info.plist and using PushKit to handle the APNs payloads I were able to do what I wanted. A SSCCE (example) is available at my repository:
https://github.com/knutvalen/ping

Resources