Быстрое уведомление iOS после завершения загрузки с использованием URLSession - PullRequest
0 голосов
/ 02 апреля 2019

Я загружаю длинный файл, используя URLSession, и после завершения загрузки я пытаюсь показать пользователю уведомление о том, что загрузка завершена.

Уведомление работает отлично, когда приложение работает. Но не работает, когда приложение выходит на задний план. Когда приложение снова выходит на передний план, запускается код уведомления.

Мои коды:

import Foundation
import Zip
import UserNotifications

class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {

    static var shared = DownloadManager()
    var selectedBook: Book!

    typealias ProgressHandler = (Float, Float, Float) -> ()

    var onProgress : ProgressHandler? {
        didSet {
            if onProgress != nil {
                let _ = activate()
            }
        }
    }

    override private init() {
        super.init()
    }

    func activate() -> URLSession {
        let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")

        // Warning: If an URLSession still exists from a previous download, it doesn't create a new URLSession object but returns the existing one with the old delegate object attached!
        return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    }

    private func calculateProgress(session : URLSession, completionHandler : @escaping (Float, Float, Float) -> ()) {
        session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
            let progress = downloads.map({ (task) -> Float in
                if task.countOfBytesExpectedToReceive > 0 {
                    return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
                } else {
                    return 0.0
                }
            })
            let countOfBytesReceived = downloads.map({ (task) -> Float in
                return Float(task.countOfBytesReceived)
            })
            let countOfBytesExpectedToReceive = downloads.map({ (task) -> Float in
                return Float(task.countOfBytesExpectedToReceive)
            })
            if progress.reduce(0.0, +) == 1.0 {
                self.postNotification()
            }
            completionHandler(progress.reduce(0.0, +), countOfBytesReceived.reduce(0.0, +), countOfBytesExpectedToReceive.reduce(0.0, +))
        }
    }

    func postUnzipProgress(progress: Double) {
        NotificationCenter.default.post(name: .UnzipProgress, object: progress)
    }

    func postNotification() {
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
            // Enable or disable features based on authorization.
        }

        let content = UNMutableNotificationContent()
        content.title = NSString.localizedUserNotificationString(forKey: "Download Completed", arguments: nil)
        content.body = NSString.localizedUserNotificationString(forKey: "Quran Touch app is ready to use", arguments: nil)
        content.sound = UNNotificationSound.default()
        content.categoryIdentifier = "com.qurantouch.qurantouch"
        // Deliver the notification in 60 seconds.
        let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 2.0, repeats: false)
        let request = UNNotificationRequest.init(identifier: "downloadCompleted", content: content, trigger: trigger)

        // Schedule the notification.
        center.add(request)
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {

        if totalBytesExpectedToWrite > 0 {
            if let onProgress = onProgress {
                calculateProgress(session: session, completionHandler: onProgress)
            }
            let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
            debugPrint("Progress \(downloadTask) \(progress)")

        }
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        debugPrint("Download finished: \(location)")

        let folder = URL.createFolder(folderName: selectedBook.folder)
        let fileURL = folder!.appendingPathComponent("ClipsAndTacksF1ForModeler.zip")

        if let url = URL.getFolderUrl(folderName: selectedBook.folder) {
            do {
                try FileManager.default.moveItem(at: location, to: fileURL)
                try Zip.unzipFile((fileURL), destination: url, overwrite: true, password: nil, progress: { (progress) -> () in
                    self.postUnzipProgress(progress: progress)
                    if progress == 1 {
//                        self.postNotification()
                        UserDefaults.standard.set("selected", forKey: self.selectedBook.fileKey)
                        URL.removeFile(file: fileURL)
                    }
                }, fileOutputHandler: {(outputUrl) -> () in
                })
            } catch {
                print(error)
            }
        }
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        debugPrint("Task completed: \(task), error: \(error)")
    }

}

И начали загрузку здесь

func downloadBookWithUrl(url: String) {
        DownloadManager.shared.selectedBook = selectedBook
        let url = URL(string: url)!
        let task = DownloadManager.shared.activate().downloadTask(with: url)
        task.resume()        
    }

Я получил пример от apple, который написан на Objective C. Но я не смог пройти через это, поскольку я не знаю Objective C. Вот пример: https://developer.apple.com/library/archive/samplecode/SimpleBackgroundTransfer/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013416

1 Ответ

0 голосов
/ 02 апреля 2019

Я следовал инструкции Документы Apple, предложенные subdan в комментариях

Внедрен метод handleEventsForBackgroundURLSession в appDelegate.И прежде чем показывать уведомление, я вызвал обработчик завершения из appDelegate, и он начал работать.

В appDelegate:

var backgroundCompletionHandler: (() -> Void)?
func application(_ application: UIApplication,
                     handleEventsForBackgroundURLSession identifier: String,
                     completionHandler: @escaping () -> Void) {
        backgroundCompletionHandler = completionHandler
    }

И перед вызовом уведомления:

DispatchQueue.main.async {
                            guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
                                let backgroundCompletionHandler =
                                appDelegate.backgroundCompletionHandler else {
                                    return
                            }
                            backgroundCompletionHandler()
                            self.postNotification()
                        }
...