diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..bf77d54 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +4.2 diff --git a/SDDownloadManager.podspec b/SDDownloadManager.podspec index e221387..ebfbdf6 100644 --- a/SDDownloadManager.podspec +++ b/SDDownloadManager.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SDDownloadManager' -s.version = '1.0.3' +s.version = '1.1.0' s.summary = 'A simple, robust and elegant download manager written in Swift' s.description = <<-DESC @@ -12,7 +12,9 @@ s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'Sagar Dagdu' => 'shags032@gmail.com' } s.source = { :git => 'https://github.com/SagarSDagdu/SDDownloadManager.git', :tag => s.version.to_s } -s.ios.deployment_target = '11.2' +s.swift_version = '4.2' +s.ios.deployment_target = '10.0' +s.framework = 'UserNotifications' s.source_files = 'SDDownloadManager/Classes/*.swift' end diff --git a/SDDownloadManager.xcodeproj/project.pbxproj b/SDDownloadManager.xcodeproj/project.pbxproj index afc6bdd..28ac302 100644 --- a/SDDownloadManager.xcodeproj/project.pbxproj +++ b/SDDownloadManager.xcodeproj/project.pbxproj @@ -116,7 +116,13 @@ TargetAttributes = { ACE3B6801F35D5BA00684BE6 = { CreatedOnToolsVersion = 8.3.3; + LastSwiftMigration = 1010; ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 0; + }; + }; }; }; }; @@ -289,7 +295,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.sagar.thinkinfinite.SDDownloadManager; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -302,7 +308,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.sagar.thinkinfinite.SDDownloadManager; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/SDDownloadManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SDDownloadManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SDDownloadManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SDDownloadManager.xcodeproj/project.xcworkspace/xcuserdata/sagardagdu.xcuserdatad/UserInterfaceState.xcuserstate b/SDDownloadManager.xcodeproj/project.xcworkspace/xcuserdata/sagardagdu.xcuserdatad/UserInterfaceState.xcuserstate index 6ba56b4..44b7f76 100644 Binary files a/SDDownloadManager.xcodeproj/project.xcworkspace/xcuserdata/sagardagdu.xcuserdatad/UserInterfaceState.xcuserstate and b/SDDownloadManager.xcodeproj/project.xcworkspace/xcuserdata/sagardagdu.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SDDownloadManager.xcodeproj/xcuserdata/sagardagdu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/SDDownloadManager.xcodeproj/xcuserdata/sagardagdu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..fe2b454 --- /dev/null +++ b/SDDownloadManager.xcodeproj/xcuserdata/sagardagdu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/SDDownloadManager/AppDelegate.swift b/SDDownloadManager/AppDelegate.swift index b38ee11..fd9e8a1 100644 --- a/SDDownloadManager/AppDelegate.swift +++ b/SDDownloadManager/AppDelegate.swift @@ -24,6 +24,7 @@ // THE SOFTWARE. import UIKit +import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -31,8 +32,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + let center = UNUserNotificationCenter.current() + // Request permission to display alerts and play sounds. + center.requestAuthorization(options: [.alert, .sound]) + { (granted, error) in + // Enable or disable features based on authorization. + } return true } @@ -58,6 +65,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - + func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { + debugPrint("handleEventsForBackgroundURLSession: \(identifier)") + SDDownloadManager.shared.backgroundCompletionHandler = completionHandler + } } diff --git a/SDDownloadManager/Classes/SDDownloadManager.swift b/SDDownloadManager/Classes/SDDownloadManager.swift index 9e05a76..d55fb4e 100644 --- a/SDDownloadManager/Classes/SDDownloadManager.swift +++ b/SDDownloadManager/Classes/SDDownloadManager.swift @@ -24,33 +24,46 @@ // THE SOFTWARE. import UIKit +import UserNotifications final public class SDDownloadManager: NSObject { public typealias DownloadCompletionBlock = (_ error : Error?, _ fileUrl:URL?) -> Void public typealias DownloadProgressBlock = (_ progress : CGFloat) -> Void + public typealias BackgroundDownloadCompletionHandler = () -> Void // MARK :- Properties - var session: URLSession = URLSession() - var ongoingDownloads: [String : SDDownloadObject] = [:] + private var session: URLSession! + private var ongoingDownloads: [String : SDDownloadObject] = [:] + private var backgroundSession: URLSession! + + public var backgroundCompletionHandler: BackgroundDownloadCompletionHandler? + public var showLocalNotificationOnBackgroundDownloadDone = true + public var localNotificationText: String? public static let shared: SDDownloadManager = { return SDDownloadManager() }() //MARK:- Public methods - public func dowloadFile(withRequest request: URLRequest, + public func downloadFile(withRequest request: URLRequest, inDirectory directory: String? = nil, withName fileName: String? = nil, + shouldDownloadInBackground: Bool = false, onProgress progressBlock:DownloadProgressBlock? = nil, onCompletion completionBlock:@escaping DownloadCompletionBlock) -> String? { if let _ = self.ongoingDownloads[(request.url?.absoluteString)!] { - print("Already in progress") + debugPrint("Already in progress") return nil } + var downloadTask: URLSessionDownloadTask + if shouldDownloadInBackground { + downloadTask = self.backgroundSession.downloadTask(with: request) + } else{ + downloadTask = self.session.downloadTask(with: request) + } - let downloadTask = self.session.downloadTask(with: request) let download = SDDownloadObject(downloadTask: downloadTask, progressBlock: progressBlock, completionBlock: completionBlock, @@ -109,6 +122,8 @@ final public class SDDownloadManager: NSObject { super.init() let sessionConfiguration = URLSessionConfiguration.default self.session = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil) + let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: Bundle.main.bundleIdentifier!) + self.backgroundSession = URLSession(configuration: backgroundConfiguration, delegate: self, delegateQueue: OperationQueue()) } private func isDownloadInProgress(forUniqueKey key:String?) -> (Bool, SDDownloadObject?) { @@ -121,6 +136,29 @@ final public class SDDownloadManager: NSObject { return (false, nil) } + private func showLocalNotification(withText text:String) { + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.getNotificationSettings { (settings) in + guard settings.authorizationStatus == .authorized else { + debugPrint("Not authorized to schedule notification") + return + } + + let content = UNMutableNotificationContent() + content.title = text + content.sound = UNNotificationSound.default + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, + repeats: false) + let identifier = "SDDownloadManagerNotification" + let request = UNNotificationRequest(identifier: identifier, + content: content, trigger: trigger) + notificationCenter.add(request, withCompletionHandler: { (error) in + if let error = error { + debugPrint("Could not schedule notification, error : \(error)") + } + }) + } + } } extension SDDownloadManager : URLSessionDelegate, URLSessionDownloadDelegate { @@ -164,6 +202,10 @@ extension SDDownloadManager : URLSessionDelegate, URLSessionDownloadDelegate { didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + guard totalBytesExpectedToWrite > 0 else { + debugPrint("Could not calculate progress as totalBytesExpectedToWrite is less than 0") + return; + } if let download = self.ongoingDownloads[(downloadTask.originalRequest?.url?.absoluteString)!], let progressBlock = download.progressBlock { @@ -190,4 +232,26 @@ extension SDDownloadManager : URLSessionDelegate, URLSessionDownloadDelegate { } } + public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + session.getTasksWithCompletionHandler { (dataTasks, uploadTasks, downloadTasks) in + if downloadTasks.count == 0 { + OperationQueue.main.addOperation({ + if let completion = self.backgroundCompletionHandler { + completion() + } + + if self.showLocalNotificationOnBackgroundDownloadDone { + var notificationText = "Download completed" + if let userNotificationText = self.localNotificationText { + notificationText = userNotificationText + } + + self.showLocalNotification(withText: notificationText) + } + + self.backgroundCompletionHandler = nil + }) + } + } + } } diff --git a/SDDownloadManager/Info.plist b/SDDownloadManager/Info.plist index 9a21b4f..2504b7d 100644 --- a/SDDownloadManager/Info.plist +++ b/SDDownloadManager/Info.plist @@ -20,6 +20,11 @@ 1 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -34,10 +39,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - diff --git a/SDDownloadManager/ViewController.swift b/SDDownloadManager/ViewController.swift index 28a8184..7cb40a7 100644 --- a/SDDownloadManager/ViewController.swift +++ b/SDDownloadManager/ViewController.swift @@ -33,26 +33,37 @@ class ViewController: UIViewController { @IBOutlet weak var progressLabel: UILabel! @IBOutlet weak var finalUrlLabel: UILabel! + private let downloadManager = SDDownloadManager.shared let directoryName : String = "TestDirectory" + let fiveMBUrl = "https://sample-videos.com/video123/mp4/480/big_buck_bunny_480p_5mb.mp4" + let tenMBUrl = "https://sample-videos.com/video123/mp4/480/big_buck_bunny_480p_10mb.mp4" + //MARK:- Lifecycle override func viewDidLoad() { super.viewDidLoad() + self.setupUI() + self.foregrounDownloadDemo() + self.backgroundDownloadDemo() + } + + private func setupUI() { self.progressView.setProgress(0, animated: false) self.progressLabel.text = "0.0 %" self.finalUrlLabel.text = "" + } + + private func foregrounDownloadDemo() { + let request = URLRequest(url: URL(string: self.fiveMBUrl)!) - let request = URLRequest.init(url: URL.init(string: "http://www.sample-videos.com/video/3gp/144/big_buck_bunny_144p_5mb.3gp")!) - - let downloadKey = SDDownloadManager.shared.dowloadFile(withRequest: request, - inDirectory: directoryName, - withName: nil, - onProgress: { [weak self] (progress) in - let percentage = String(format: "%.1f %", (progress * 100)) - self?.progressView.setProgress(Float(progress), animated: true) - self?.progressLabel.text = "\(percentage) %" + let downloadKey = self.downloadManager.downloadFile(withRequest: request, + inDirectory: directoryName, + onProgress: { [weak self] (progress) in + let percentage = String(format: "%.1f %", (progress * 100)) + self?.progressView.setProgress(Float(progress), animated: true) + self?.progressLabel.text = "\(percentage) %" }) { [weak self] (error, url) in if let error = error { print("Error is \(error as NSError)") @@ -65,10 +76,29 @@ class ViewController: UIViewController { } print("The key is \(downloadKey!)") + } + + private func backgroundDownloadDemo() { + let request = URLRequest(url: URL(string: self.tenMBUrl)!) + + self.downloadManager.showLocalNotificationOnBackgroundDownloadDone = true + self.downloadManager.localNotificationText = "All background downloads complete" + let downloadKey = self.downloadManager.downloadFile(withRequest: request, inDirectory: directoryName, withName: directoryName, shouldDownloadInBackground: true, onProgress: { (progress) in + let percentage = String(format: "%.1f %", (progress * 100)) + debugPrint("Background progress : \(percentage)") + }) { [weak self] (error, url) in + if let error = error { + print("Error is \(error as NSError)") + } else { + if let url = url { + print("Downloaded file's url is \(url.path)") + self?.finalUrlLabel.text = url.path + } + } + } + print("The key is \(downloadKey!)") } - - }