13

I'm downloading a video thanks to downloadTaskWithURL and I'm saving it to my gallery with this code :

    func saveVideoBis(fileStringURL:String){

    print("saveVideoBis");

    let url = NSURL(string: fileStringURL);
    (NSURLSession.sharedSession().downloadTaskWithURL(url!) { (location:NSURL?, r:NSURLResponse?, e:NSError?) -> Void in

        let mgr = NSFileManager.defaultManager()

        let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0];

        print(documentsPath);

        let destination = NSURL(string: NSString(format: "%@/%@", documentsPath, url!.lastPathComponent!) as String);

        print(destination);

        try? mgr.moveItemAtPath(location!.path!, toPath: destination!.path!)

        PHPhotoLibrary.requestAuthorization({ (a:PHAuthorizationStatus) -> Void in
            PHPhotoLibrary.sharedPhotoLibrary().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(destination!);
                }) { completed, error in
                    if completed {
                        print(error);
                        print("Video is saved!");
                        self.sendNotification();
                    }
            }
        })
    }).resume()
}

It works perfectly fine on my simulator but on my iPad the video isn't saved even if the print("Video is saved!"); appears. Do you have any idea why ?

I also have that message appearing in my console

Unable to create data from file (null)

Melanie Journe
  • 1,249
  • 5
  • 16
  • 36
  • The documentation for the `completionHandler` part of the `downloadTaskWithURL` method says "You must move this file or open it for reading before your completionHandler returns". Now, you seem to still be inside the completion handler at all times, but maybe (and I'm guessing here) you calling another method ('performChanges') inside your completionHandler is messing something up. Could you maybe try to copy the file to another location before you call `performChanges`? – pbodsk Feb 19 '16 at 11:47
  • I'm relatively new to Swift, can you give me an example of how to do this please ? – Melanie Journe Feb 19 '16 at 12:04
  • You need a `sourceURL` of type NSURL, thats your `location` then you need a `destinationURL` you could maybe take the `location` and append something to it, maybe `let destination = NSURL(fileURLWithPath: location.absoluteString + "_dest")`. Once you have that you need a `fileManager` of type `NSFileManager`, like so `let fileManager = NSFileManager.defaultManager()`. And then you are ready to move the file by saying `fileManager.moveItemAtURL(location, toURL: destination)`. That method `throws` so you need to wrap that in a `do...try...catch`. Hope that makes sense – pbodsk Feb 19 '16 at 12:24
  • You can read more about the NSFileManager here: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/#//apple_ref/doc/uid/20000305-SW28 And this article from NSHipster is worth a read too: http://nshipster.com/nsfilemanager/ – pbodsk Feb 19 '16 at 12:26
  • I did it and edited my code, I fall directly in the error case – Melanie Journe Feb 19 '16 at 13:00
  • In the error case of the `moveItemAtURL` you mean? OK, what does it say then if you do a print of the error? `do {try something} catch let error { print("error goes here: \(error)")}` – pbodsk Feb 19 '16 at 13:02
  • Try '(BOOL)createDirectoryAtURL:(NSURL *)url withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError * _Nullable *)error' If YES, this method creates any non-existent parent directories as part of creating the directory in url. If NO, this method fails if any of the intermediate parent directories does not exist.... set it to yes. – Woodstock Feb 19 '16 at 13:08
  • I totally change my code on it works on my emulator but it doesn't work on my iPad.... – Melanie Journe Feb 19 '16 at 14:10
  • @MelanieJourne you should better name your variables to improve readability. you should also work with url and use its path property if needed – Leo Dabus Feb 19 '16 at 14:11
  • fileURL it is totally misleading name. It is a string (weblink) and it is not a local file – Leo Dabus Feb 19 '16 at 14:14
  • I change the variable name. Also I didn't find my answer in your link :/ – Melanie Journe Feb 19 '16 at 14:18
  • 1
    You are not suppose to find the answer. You need to understand the difference between absoluteString and path. If you need a url for a local file just use NSURL initialiser fileURLWithPath (note that absoluteString has file:// prefix and you can't include that when using NSURL fileURLWithPath initialiser) – Leo Dabus Feb 19 '16 at 14:20
  • Yes, I logged everything to see the content variable and understand this. But it's not a duplicate of my question because your link won't explain me why my code works on simulator and not on a real device... – Melanie Journe Feb 19 '16 at 14:24
  • Yes it was definitely a duplicate as you were trying to use absoluteString instead of path using NSURL fileURLWithPath initializer. I can reopen your question but I think it is all about when use one or the other – Leo Dabus Feb 19 '16 at 14:27
  • If it's about that, why does it work on the simulator of Xcode and not on my device ? – Melanie Journe Feb 19 '16 at 14:30
  • I am taking about your original issue. You should have opened a new question. I will reopen it anyway and leave the duplicated link here http://stackoverflow.com/questions/16176911/nsurl-path-vs-absolutestring – Leo Dabus Feb 19 '16 at 14:32

2 Answers2

30

Please check comments through the code:

Xcode 8 • Swift 3

import UIKit
import Photos

class ViewController: UIViewController {

    func downloadVideoLinkAndCreateAsset(_ videoLink: String) {

        // use guard to make sure you have a valid url
        guard let videoURL = URL(string: videoLink) else { return }

        guard let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }

        // check if the file already exist at the destination folder if you don't want to download it twice
        if !FileManager.default.fileExists(atPath: documentsDirectoryURL.appendingPathComponent(videoURL.lastPathComponent).path) {

            // set up your download task
            URLSession.shared.downloadTask(with: videoURL) { (location, response, error) -> Void in

                // use guard to unwrap your optional url
                guard let location = location else { return }

                // create a deatination url with the server response suggested file name
                let destinationURL = documentsDirectoryURL.appendingPathComponent(response?.suggestedFilename ?? videoURL.lastPathComponent)

                do {

                    try FileManager.default.moveItem(at: location, to: destinationURL)

                    PHPhotoLibrary.requestAuthorization({ (authorizationStatus: PHAuthorizationStatus) -> Void in

                        // check if user authorized access photos for your app
                        if authorizationStatus == .authorized {
                            PHPhotoLibrary.shared().performChanges({
                                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: destinationURL)}) { completed, error in
                                    if completed {
                                        print("Video asset created")
                                    } else {
                                        print(error)
                                    }
                            }
                        }
                    })

                } catch { print(error) }

            }.resume()

        } else {
            print("File already exists at destination url")
        }

    }

    override func viewDidLoad() {
        super.viewDidLoad()
        downloadVideoLinkAndCreateAsset("https://www.yourdomain.com/yourmovie.mp4")
    }

}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • It works perfectly fine thanks you ! :) Only one little downside : when I download a movie, then delete it, then try to download it again, it tells me that "the file already exists at destination url". – Melanie Journe Feb 23 '16 at 09:01
  • You can remove that condition and save it with a new name – Leo Dabus Feb 23 '16 at 10:16
  • @LeoDabus Can you please tell me how i can use `NSURLSessionDownloadDelegate` methods with this code? because its methods did't works with this code? – ZAFAR007 Jan 11 '17 at 16:37
  • 2
    is there a way to fetch that specific asset from the photos library after downloading and saving it ? – JAHelia Aug 13 '17 at 13:54
  • @LeoDabus : I used same code to save a .m3u8 file. But, when try to save the file it getting error , ' The operation couldn’t be completed. (Cocoa error -1.)' – Vineesh TP Nov 02 '17 at 08:34
  • @LeoDabus how to download pdf in same way? – Rashid KC Aug 08 '18 at 11:30
  • https://stackoverflow.com/questions/47140314/how-to-display-remote-document-using-qlpreviewcontroller-in-swift/47141577#47141577 – Leo Dabus Aug 08 '18 at 11:34
  • @LeoDabus I need to save the downloaded pdf into the files folder. I tried the above answer. but it shows "no preview to show" – Rashid KC Aug 08 '18 at 12:39
  • Maybe your link is not https. I can only guess It would be easier for you to open a new question and put your issue there – Leo Dabus Aug 08 '18 at 12:40
  • Received this error: `Optional(Error Domain=PHPhotosErrorDomain Code=3302 "(null)")` – Emm Oct 26 '22 at 21:04
  • Your url points to your photo asset you need to use the framework method to fetch its data – Leo Dabus Oct 27 '22 at 00:21
  • @Emm check this post https://stackoverflow.com/a/43944769/2303865 – Leo Dabus Oct 27 '22 at 00:22
0

If you want to download Video from URL but don't want to store it on Gallery. following code works for me...

 func createDownloadTask(videoURL: String,index: Int) {
    let downloadRequest = NSMutableURLRequest(url: URL(string: videoURL)!)
    let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main)
    self.downloadTask = session.downloadTask(with: downloadRequest as URLRequest, completionHandler: { (url, response, error) in
        print("asdasd")
        if error != nil{
            if super.reachability.connection == .none{
                self.showalert(msg: error?.localizedDescription ?? "")
            }else{
                self.downloadTask?.resume()
            }
        }

        guard let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
        // create a deatination url with the server response suggested file name
        let destinationURL = documentsDirectoryURL.appendingPathComponent(response?.suggestedFilename ?? "")

        do {
            if url != nil{
            try FileManager.default.moveItem(at: url!, to: destinationURL)
            }

            }

        } catch { print(error) }

    })
    self.downloadTask!.resume()
}
Rahul Gusain
  • 269
  • 3
  • 6