I'm using this function to download videos to a file called downloads using alamofire.
How would I edit it so it saves videos to the camera roll
func downloadVideoToCameraRoll() {
let destination: DownloadRequest.DownloadFileDestination = { _, response in
let pathComponent = response.suggestedFilename!
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let folderPath: URL = directoryURL.appendingPathComponent("Downloads", isDirectory: true)
let fileURL: URL = folderPath.appendingPathComponent(pathComponent)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(firstId, method: .get, parameters: nil, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
self.progresss.setProgress(Float(progress.fractionCompleted), animated: true)
//print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// Custom evaluation closure now includes file URLs (allows you to parse out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.temporaryURL!)
print(response.destinationURL!)
}
You can use this in swift 3:
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: urlToYourVideo)
}) { saved, error in
if saved {
print("Saved")
}
}
Noted: Need to import Photos
Try the code below
func downloadVideoToCameraRoll() {
let destination: DownloadRequest.DownloadFileDestination = { _, response in
let pathComponent = response.suggestedFilename!
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return (directoryURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(firstId, method: .get, parameters: nil, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
self.progresss.setProgress(Float(progress.fractionCompleted), animated: true)
//print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// Custom evaluation closure now includes file URLs (allows you to parse out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.temporaryURL!)
print(response.destinationURL!)
saveVideoTo(destinationURL)
}
func saveVideoTo(_ videoUrl:Url?){
if videoUrl != nil {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let createAssetRequest: PHAssetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(NSURL(string: videoUrl)!)!
createAssetRequest.placeholderForCreatedAsset
}) { (success, error) -> Void in
if success {
//saved successfully
}
else {
//error occured
}
}
}
}
Related
I initially asked this question, got the answer, and in the comments #LeoDabus said:
NSData(contentsOf: url) it is not mean to use with non local resources
urls
He suggested I use URLSession which I did, but the response is very slow. I'm wondering am I doing something wrong. The video is 2mb if that makes any difference.
Inside the the session's completionHandler I tried updating the returned data on the main queue but there was a scrolling glitch while doing that. Using DispatchQueue.global().async there is no scrolling glitch but it seems like it takes longer return
// all of this occurs inside my data model
var cachedURL: URL?
let videoUrl = dict["videoUrl"] as? String ?? "" // eg. "https://firebasestorage.googleapis.com/v0/b/myApp.appspot.com/o/abcd%277920FHqFBkl7D6j%2F-MC65EFG_qT0KZbdtFhU%2F48127-8C29-4666-96C9-E95BE178B268.mp4?alt=media&token=bf85dcd1-8cee-428e-87bc-91800b7316de"
guard let url = URL(string: videoUrl) else { return }
useURLSessionToCacheVideo(url)
func useURLSessionToCacheVideo(_ url: URL) {
let lastPathComponent = url.lastPathComponent
let cachesDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let file = cachesDir.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: file.path) {
self.cachedURL = file
print("url already exists in cache")
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error { return }
if let response = response as? HTTPURLResponse {
guard response.statusCode == 200 else {
return
}
}
guard let data = data else {
return
}
DispatchQueue.global().async { // main queue caused a hiccup while scrolling a cv
do {
try data.write(to: file, options: .atomic)
DispatchQueue.main.async { [weak self] in
self?.cachedURL = file
}
} catch {
print("couldn't cache video file")
}
}
}).resume()
}
You should write the file from the session's background thread:
func useURLSessionToCacheVideo(_ url: URL) {
let lastPathComponent = url.lastPathComponent
let fileURL = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: fileURL.path) {
self.cachedURL = fileURL
print("url already exists in cache")
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let data = data
else {
return
}
do {
try data.write(to: fileURL, options: .atomic)
DispatchQueue.main.async { [weak self] in
self?.cachedURL = fileURL
}
} catch {
print("couldn't cache video file")
}
}.resume()
}
This also accepts any 2xx HTTP response code.
That having been said, I’d suggest using a download task, which reduces the peak memory usage and writes the data to the file as you go along:
func useURLSessionToCacheVideo(_ url: URL) {
let lastPathComponent = url.lastPathComponent
let fileURL = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: fileURL.path) {
self.cachedURL = fileURL
print("url already exists in cache")
return
}
URLSession.shared.downloadTask(with: url) { location, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let location = location
else {
return
}
do {
try FileManager.default.moveItem(at: location, to: fileURL)
DispatchQueue.main.async { [weak self] in
self?.cachedURL = fileURL
}
} catch {
print("couldn't cache video file")
}
}.resume()
}
Personally, rather than having this routine update cachedURL itself, I'd use a completion handler pattern:
enum CacheError: Error {
case failure(URL?, URLResponse?)
}
func useURLSessionToCacheVideo(_ url: URL, completion: #escaping (Result<URL, Error>) -> Void) {
let lastPathComponent = url.lastPathComponent
let fileURL = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(lastPathComponent)
if FileManager.default.fileExists(atPath: fileURL.path) {
completion(.success(fileURL))
return
}
URLSession.shared.downloadTask(with: url) { location, response, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
guard
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
let temporaryLocation = location
else {
DispatchQueue.main.async {
completion(.failure(CacheError.failure(location, response)))
}
return
}
do {
try FileManager.default.moveItem(at: temporaryLocation, to: fileURL)
DispatchQueue.main.async {
completion(.success(fileURL))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}.resume()
}
And call it like so:
useURLSessionToCacheVideo(url) { result in
switch result {
case .failure(let error):
print(error)
case .success(let cachedURL):
self.cachedURL = cachedURL
}
}
That way, the caller is responsible for updating cachedURL, it now knows when it's done (in case you want to update the UI to reflect the success or failure of the download), and your network layer isn't entangled with the model structure of the caller.
func downLoad(fileName:String) {
let urlString : String = "\(myurl)\(fileName)"
var localPath: NSURL?
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(urlString, method: .get, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// Custom evaluation closure now includes file URLs (allows you to parse out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.destinationURL?.path)
print(response.destinationURL?.absoluteString)
let unzipDirectory = self.unzipPath(fileURL:fileName)
let success = SSZipArchive.unzipFile(atPath: (response.destinationURL?.path)!, toDestination: unzipDirectory!)
print(success)
if !success {
return
}
}
}
func unzipPath(fileName:String) -> String? {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let pathComponent = url.appendingPathComponent("test\(fileName)")
do {
try FileManager.default.createDirectory(at: pathComponent!, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return pathComponent?.absoluteString
}
i get right path in response.destinationURL
but success is false
i tried atPath : to response.destinationURL?.path and
response.destinationURL?.absoluteString
but failed too
What i am doing wrong
destinationURL?.path return this
Optional("/Users/MyUser/Library/Developer/CoreSimulator/Devices/3FBAD207-E5AB-4FC1-8199-2269A1249D97/data/Containers/Data/Application/CB1C2EF5-3100-430B-B869-774C09B8EA7F/Documents/testFile.zip")
response.destinationURL?.absoluteString
return this
Optional("file:///Users/MyUser/Library/Developer/CoreSimulator/Devices/3FBAD207-E5AB-4FC1-8199-2269A1249D97/data/Containers/Data/Application/CB1C2EF5-3100-430B-B869-774C09B8EA7F/Documents/testFile.zip")
i think this is correct URL
why failed unzip?
By looking at your code, the path you are using for the source is perfect.
For the destination path, try updating it to
func unzipPath(fileName:String) -> String? {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let pathWithComponent = path.appendingPathComponent("test\(fileName)")
do {
try FileManager.default.createDirectory(atPath: pathWithComponent, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return pathWithComponent
}
Try and share the results.
In alomofire 3.5 the following code worked well
self.sessionManager.download(.GET, AppConstants.musicFileURL + musicFilename, destination: { (url, response) -> NSURL in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let fileUrl = directoryURL.URLByAppendingPathComponent(musicFilename)
return fileUrl
})
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
self.percentageDownloaded[exhibitId]![artworkId] = (Double(totalBytesRead) ,Double(totalBytesExpectedToRead))
let meanPercentageDone = self.calculatePercentageDoneForExhibit(exhibitId, artworkArray: self.artworkArray)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.notifyDownloadProgress(meanPercentageDone)
}
.response { _, _, _, _ in
NSLog("Completed downloading audio file,%# , for artwork %#", musicFilename, artworkId)
if DataManager.saveMusicDownloadComplete(artworkId, exhibitId: exhibitId){
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.notifyMusicDownloadsComplete(exhibitId)
self.percentageDownloaded[exhibitId] = [String: (Double, Double)?]()
NSLog("when all music files have been downloaded")
}
}
}
How to migrate above code to Alamofire 4. Not able to see any comments in Migration document.
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent(musicFilename)
return (documentsURL, [.removePreviousFile])
}
Alamofire.download(AppConstants.musicFileURL + musicFilename, to: destination)
.downloadProgress { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseString(completionHandler: { (response) in
print("Success: \(response.result.isSuccess)")
print("Response String: \(response.result.value)")
})
I changed with Alamofire.download instead for download in SessionManager
In older version of Alamofire. This is how I download file
let destinationPath = Alamofire.Request.suggestedDownloadDestination( directory: .documentDirectory, domain: .userDomainMask);
Alamofire.download(.GET, urlString, destination: destinationPath)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
// print(totalBytesRead)
}
.response { request, response, _, error in
let downloadedFilePath = destinationPath(URL(string: "")!, response!);
NSUserDefaultsHelper.saveURL(downloadedFilePath, key: urlString);
completion(downloadedFilePath, true);
}
But now in the new version, my code is completely unusable and there is no similar function in the Alamofire library.
Any ideas please?
I used to use this statements:
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(
url,
method: .get,
parameters: parameters,
encoding: JSONEncoding.default,
headers: nil,
to: destination).downloadProgress(closure: { (progress) in
//progress closure
}).response(completionHandler: { (DefaultDownloadResponse) in
//here you able to access the DefaultDownloadResponse
//result closure
})
For more details read more in Alamofire docs about Migration to 4.0:
Swift 4.0
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent("file.csv")
return (documentsURL, [.removePreviousFile])
}
Alamofire.download(url, to: destination).responseData { response in
if let destinationUrl = response.destinationURL {
print("destinationUrl \(destinationUrl.absoluteURL)")
}
}
There are several enhancements in Alamofire 4. The first of which is the optionality of the destination closure. Now, by default, the destination closure is nil which means the file is not moved anywhere on the file system and the temporary URL is returned.
This is the default execution:-
Alamofire.download(urlString).responseData { response in
print("Temporary URL: \(response.temporaryURL)")
}
This is my code to download file with Alamofire 4.0 which return destination Url of file:-
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent("duck.png")
return (documentsURL, [.removePreviousFile])
}
Alamofire.download(url, to: destination).responseData { response in
if let destinationUrl = response.destinationURL ? {
completionHandler(destinationUrl)
}
}
Downloading mp3 file with Alamofire 4.0 Swift 4.x
Since almost all samples seems to be about downloading an image or a JSON file, it took me hours to find the right solution.
I will share it here hoping it would help others to save some time.
func startDownload(audioUrl:String) -> Void {
let fileUrl = self.getSaveFileUrl(fileName: audioUrl)
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
return (fileUrl, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(audioUrl, to:destination)
.downloadProgress { (progress) in
self.progressLabel.text = (String)(progress.fractionCompleted)
}
.responseData { (data) in
self.progressLabel.text = "Completed!"
}
}
func getSaveFileUrl(fileName: String) -> URL {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let nameUrl = URL(string: fileName)
let fileURL = documentsURL.appendingPathComponent((nameUrl?.lastPathComponent)!)
NSLog(fileURL.absoluteString)
return fileURL;
}
For the latest versions this is how it should look like:
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("image.png")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download("https://httpbin.org/image/png", to: destination).response { response in
debugPrint(response)
if response.error == nil, let imagePath = response.fileURL?.path {
let image = UIImage(contentsOfFile: imagePath)
}
}
Swift 3 Alamofire (4.4.0):
.plist add key "App Transport Security Settings->Allow Arbitrary Loads->Yes" if you copy and paste code below:
import Alamofire
let destination = DownloadRequest.suggestedDownloadDestination()
Alamofire.download("http://zmp3-mp3-lossless-te-zmp3-bdhcm-1.zadn.vn/151e407bb43f5d61042e/1223048424027738068?key=f-zMo3GZKlhVibnvGMsMuQ&expires=1495726053&filename=See%20You%20Again%20-%20Wiz%20Khalifa%20Charlie%20Puth%20(NhacPro.net).flac", to: destination).downloadProgress(queue: DispatchQueue.global(qos: .utility)) { (progress) in
print("Progress: \(progress.fractionCompleted)")
} .validate().responseData { ( response ) in
print(response.destinationURL!.lastPathComponent)
}
Use this code for download file
let fileManager = FileManager.default
let directoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
Alamofire.request(\(downloadUrl)).downloadProgress(closure : { (progress) in
print(progress.fractionCompleted)
}).responseData{ (response) in
print(response)
print(response.result.value!)
print(response.result.description)
let randomString = NSUUID().uuidString
if let data = response.result.value {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let videoURL = documentsURL.appendingPathComponent("\(randomString)")
do {
try data.write(to: videoURL)
} catch {
print("Something went wrong!")
}
}
}
I use this alamofire request to get a pdf file, i want to save it as NSData:
func makeDataCall(urlString: String, completionHandler: (responseObject: NSData?, error: NSError?) -> ()) {
//Perform request
Alamofire.request(.GET, urlString, headers: ["Authorization": auth])
.responseData { request, response, responseData in
print(request)
print(response)
print(responseData)
completionHandler(responseObject: responseData.data, error: nil)
}
}
In the response i get this:
"Content-Length" = 592783;
"Content-Type" = "application/pdf";
However responseData.data is nil.
What am i doing wrong?
Editing my previous response, I read your question too quickly.
To download a file like a pdf you should use Alamofire.download rather than request.
There's a section on it in the docs:
https://github.com/Alamofire/Alamofire#downloading-a-file
just checked with some random pdf from the internet and this works for me just fine:
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, "http://box2d.org/manual.pdf", destination: destination)
.response { _, _, _, error in
if let error = error {
print("Failed with error: \(error)")
} else {
print("Downloaded file successfully")
}
}
In Swift 4 use this code if you want to download a pdf
let h: HTTPHeaders = [
"Accept": "application/pdf",
"Content-Type": "application/pdf",
]
//random document name
let randomString = NSUUID().uuidString
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent("\(randomString).pdf")
return (documentsURL, [.removePreviousFile])
}
Alamofire.download("yourURL",method: .get,headers: h, to: destination).response { response in
if let destinationUrl = response.destinationURL {
print("destinationUrl \(destinationUrl.absoluteURL)")
}
}