Download file from server using Swift
Asked Answered
L

3

9

Hi I have a whole bunch of .mp3 files I want to use with NSFileManager and store in the documents folder. Is there a way I can download the .mp3 files online and then have it save to the documents folder? This is what I'm using for a local file.

let filemanager = NSFileManager.defaultManager()
let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
let destinationPath:NSString = documentsPath.stringByAppendingString("/Attention.mp3")

if (!filemanager.fileExistsAtPath(destinationPath)) {
  var theError: NSError?
  let fileForCopy = NSBundle.mainBundle().pathForResource("Attention",ofType:"mp3")
  filemanager.copyItemAtPath(fileForCopy!,toPath:destinationPath, error: &theError)

  if (theError == nil) {
    println("The music files has been saved.")
  } else {
    println("Error")
  }
} else {
  println("The files already exist")
}
Lenticularis answered 18/12, 2014 at 5:55 Comment(3)
How do you specify the files you want to download?Gunslinger
@ThomasKilian Well locally, you can do let fileForCopy = NSBundle.mainBundle().pathForResource("Attention",ofType:"mp3")Lenticularis
@ThomasKilian But what if I had a URL and want to download them from there?Lenticularis
S
38

edit/update: Xcode 11.5 • Swift 5.2

import UIKit
import AVFoundation

class ViewController: UIViewController {
    var player: AVPlayer!
    override func viewDidLoad() {
        super.viewDidLoad()
        let alarm = URL(string: "https://www.ringtonemobi.com/storage/upload/user_id_1/iphone-5-alarm-2016-08-21-01-49-25.mp3")!
        do {
            try alarm.download(to: .documentDirectory) { url, error in
                guard let url = url else { return }
                self.player = AVPlayer(url: url)
                self.player.play()
            }
        } catch {
            print(error)
        }
    }
}

import Foundation
extension URL {
    func download(to directory: FileManager.SearchPathDirectory, using fileName: String? = nil, overwrite: Bool = false, completion: @escaping (URL?, Error?) -> Void) throws {
        let directory = try FileManager.default.url(for: directory, in: .userDomainMask, appropriateFor: nil, create: true)
        let destination: URL
        if let fileName = fileName {
            destination = directory
                .appendingPathComponent(fileName)
                .appendingPathExtension(self.pathExtension)
        } else {
            destination = directory
            .appendingPathComponent(lastPathComponent)
        }
        if !overwrite, FileManager.default.fileExists(atPath: destination.path) {
            completion(destination, nil)
            return
        }
        URLSession.shared.downloadTask(with: self) { location, _, error in
            guard let location = location else {
                completion(nil, error)
                return
            }
            do {
                if overwrite, FileManager.default.fileExists(atPath: destination.path) {
                    try FileManager.default.removeItem(at: destination)
                }
                try FileManager.default.moveItem(at: location, to: destination)
                completion(destination, nil)
            } catch {
                print(error)
            }
        }.resume()
    }
}


Original answer

Xcode 8.3.2 • Swift 3.1

if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
    // create your document folder url
    let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    // your destination file url
    let destination = documentsUrl.appendingPathComponent(audioUrl.lastPathComponent)
    print(destination)
    // check if it exists before downloading it
    if FileManager.default.fileExists(atPath: destination.path) {
        print("The file already exists at path")
    } else {
        //  if the file doesn't exist
        //  just download the data from your url
        URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) in
            // after downloading your data you need to save it to your destination url
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("audio"),
                let location = location, error == nil
                else { return }
            do {
                try FileManager.default.moveItem(at: location, to: destination)
                print("file saved")
            } catch {
                print(error)
            }
        }).resume()
    }
}
Sall answered 25/12, 2014 at 1:58 Comment(4)
That's if I want to use a local file. I already know how to do that. I'm wondering how I can download a .mp3 via a link and save it to the downloads folder.Lenticularis
You're awesome dude! Exactly what I was looking for. Just a quick question, what if I had multiple links? Can I just throw them into an array some how?Lenticularis
No problem! You've been a huge help. Thank you!Lenticularis
#27650822Lenticularis
K
1

Xcode 10.1, Swift 4

I used the example above from @leo-dabus but broke up the code a bit into two functions. One flaw I found in that approach was that it did not handle the case where the file is already downloaded.

This example will remove any previous file that was already downloaded and write the latest version.

/// Downloads a file asynchronously
func loadFileAsync(url: URL, completion: @escaping (Bool) -> Void) {

    // create your document folder url
    let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)

    // your destination file url
    let destination = documentsUrl.appendingPathComponent(url.lastPathComponent)

    log.info(m: "downloading file from URL: \(url.absoluteString)")
    if FileManager().fileExists(atPath: destination.path) {
        print("The file already exists at path, deleting and replacing with latest")

        if FileManager().isDeletableFile(atPath: destination.path){
            do{
                try FileManager().removeItem(at: destination)
                print("previous file deleted")
                self.saveFile(url: url, destination: destination) { (complete) in
                    if complete{
                        completion(true)
                    }else{
                        completion(false)
                    }
                }
            }catch{
                print("current file could not be deleted")
            }
        }
    // download the data from your url
    }else{
        self.saveFile(url: url, destination: destination) { (complete) in
            if complete{
                completion(true)
            }else{
                completion(false)
            }
        }
    }
}


func saveFile(url: URL, destination: URL, completion: @escaping (Bool) -> Void){
    URLSession.shared.downloadTask(with: url, completionHandler: { (location, response, error) in
        // after downloading your data you need to save it to your destination url
        guard
            let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
            let location = location, error == nil
            else { print("error with the url response"); completion(false); return}
        do {
            try FileManager.default.moveItem(at: location, to: destination)
            print("new file saved")
            completion(true)
        } catch {
            print("file could not be saved: \(error)")
            completion(false)
        }
    }).resume()
}
Ked answered 5/3, 2019 at 5:19 Comment(2)
How to use this method?Serriform
Not replacing a file is not a flaw, and actually, his code does handle the case where a file with that name already exists; he chose to skip it, which is a perfectly reasonable solution. Your solution obliterates an existing file, which might not be the right option.Set
B
0

I found the @leo-dabus worked straight away, but had to make two minor changes for my needs. This might be helpful for others.

Change #1: Handle filenames that come included with a path-extension

    if let fileName = fileName {
        if fileName.hasSuffix(self.pathExtension) {
            destination = directory
                .appendingPathComponent(fileName)
        } else {
            destination = directory
                .appendingPathComponent(fileName)
                .appendingPathExtension(self.pathExtension)
        }
    } else {
        destination = directory
            .appendingPathComponent(lastPathComponent)
    }

Change #2: If the destination file exists, generate a unique name

E.g. generate File (2).txt to avoid overwriting File.txt, like a web browser would.

    if !overwrite {
        let pathExtension = destination.pathExtension
        let lastComponent = destination.deletingPathExtension().lastPathComponent
        var copyNumber = 2
        var attemptedURL = destination
        while FileManager.default.fileExists(atPath: attemptedURL.path) {
            attemptedURL = destination
                .deletingPathExtension()
                .deletingLastPathComponent()
                .appendingPathComponent("\(lastComponent) (\(copyNumber))")
                .appendingPathExtension(pathExtension)
            copyNumber += 1
        }
        destination = attemptedURL
    }
Butler answered 18/6, 2021 at 2:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.