Reputation: 392
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
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()
}
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