Daisy the cat
Daisy the cat

Reputation: 392

Error when trying to copy a file with FileManager: Operation not permitted

I'm new to FileManager in iOS. I'm trying to create a simple app: there's a list of files, and when the user press a cell the the file opens. The user can also add new files.

A while ago I asked a question about another error I had, and I got an answer that work on a simulator, but it doesn't work on a real device!

My code: (the error is in storeAndShare())

typealias UrlLink = String

extension UrlLink {
var url: URL {
    var url = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    url.appendPathComponent(self)
    return url
}
}

class TableViewController: UITableViewController, UIDocumentPickerDelegate {

var names: [UrlLink] = []

override func viewDidLoad() {
    super.viewDidLoad()

    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
     self.navigationItem.rightBarButtonItem = self.editButtonItem
}

func urlForFilename(_ filename: String) -> URL {
    var url = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    url.appendPathComponent(filename)
    return url
}

@IBAction func newButtonPressed(_ sender: Any) {
    let types = UTType(tag: "pdf", tagClass: .filenameExtension, conformingTo: nil)!
    let controller = UIDocumentPickerViewController(forOpeningContentTypes: [types])
    controller.delegate = self
    self.present(controller, animated: true, completion: nil)
}

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    for url in urls {
        self.storeAndShare(withURL: url)
    }
}

// MARK: - Table view data source

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        let removedName = self.names.remove(at: indexPath.row)
        do {
            try FileManager.default.removeItem(atPath: removedName.url.path)
        } catch let err {
            print("Error while trying to remove the file \(err)")
        }
        
        tableView.deleteRows(at: [indexPath], with: .automatic)
    }
}

override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return names.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)

    cell.textLabel?.text = names[indexPath.row]
    
    if FileManager.default.fileExists(atPath: names[indexPath.row].url.path) {
        cell.accessoryType = .disclosureIndicator
    } else {
        cell.accessoryType = .none
    }

    return cell
}

lazy var documentInteractionController: UIDocumentInteractionController = {
    let vc = UIDocumentInteractionController()
    vc.delegate = self
    return vc
}()

func getSelf() -> TableViewController {
    return self
}

func share(url: URL) {
    documentInteractionController.url = url
    documentInteractionController.uti = url.typeIdentifier ?? "public.data, public.content"
    documentInteractionController.name = url.localizedName ?? url.lastPathComponent
    documentInteractionController.presentPreview(animated: true)
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    share(url: self.names[indexPath.row].url)
}

func storeAndShare(withURL url: URL) {

    let directoryURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    var newURL = directoryURL.appendingPathComponent(url.lastPathComponent)
    
    var index = 1
    let originalNewURL = newURL
    while FileManager.default.fileExists(atPath: newURL.path) {
        newURL = originalNewURL
        let comp: NSString = newURL.lastPathComponent as NSString
        newURL.deleteLastPathComponent()
        let newName: String = "\(comp.deletingPathExtension) \(index).\(comp.pathExtension)"
        newURL.appendPathComponent(newName)
        index = index + 1
    }
    
    do {
        try FileManager.default.copyItem(at: url, to: newURL)
        DispatchQueue.main.async { [self] in
            names.append(newURL.lastPathComponent)
            tableView.insertRows(at: [IndexPath(row: names.count - 1, section: 0)], with: .automatic)
        }
    } catch {
        print("ERROR: \(error).") // ERROR: Error Domain=NSCocoaErrorDomain Code=257 "The file “I am a file and you put documents in me.pdf” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/תיקיות/I am a file and you put documents in me.pdf, NSUnderlyingError=0x281ab2790 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}.
    }
}

}

extension TableViewController: UIDocumentInteractionControllerDelegate {
/// If presenting atop a navigation stack, provide the navigation controller in order to animate in a manner consistent with the rest of the platform
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
    guard let navVC = self.navigationController else {
        return self
    }
    return navVC
}
}
extension URL {
var typeIdentifier: String? {
    return (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier
}
var localizedName: String? {
    return (try? resourceValues(forKeys: [.localizedNameKey]))?.localizedName
}
}

Basically, when the user adds a new file, my app is trying to copy it into the documents folder, so it can store it there.

I'm getting the following error when trying to copy the selected file into the documents folder:

Error Domain=NSCocoaErrorDomain Code=257 "The file “I am a file and you put documents in me.pdf” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/תיקיות/I am a file and you put documents in me.pdf, NSUnderlyingError=0x281ab2790 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

It seems like I don't have permission to view the file. But it's weird, because I've just selected the file with a UIDocumentInteractionController, and it's working fine on a simulator, as I said earlier.

How do I get permission to copy the file? thanks in advance!

Upvotes: 3

Views: 2687

Answers (1)

Zayne Liu
Zayne Liu

Reputation: 585

I had the same problem, following @Cameron's answer here,

In short, all you need to do is wrap the part of your code that deals with files, with this:

func readFile(url: URL){
    // `url` to the directory or file you are trying to access.
    url.startAccessingSecurityScopedResource()

    // deal with your file here

    url.stopAccessingSecurityScopedResource()
}

Apple Official Documentation

In your case, it should be:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)

    cell.textLabel?.text = names[indexPath.row]

    // newly added    
    names[indexPath.row].url.startAccessingSecurityScopedResource()

    if FileManager.default.fileExists(atPath: names[indexPath.row].url.path) {
        cell.accessoryType = .disclosureIndicator
    } else {
        cell.accessoryType = .none
    }
    // newly added
    names[indexPath.row].url.stopAccessingSecurityScopedResource()

    return cell
}

Upvotes: 5

Related Questions