I am using the CSVImporter pod in order to parse a CSV file uploaded from the iCloud Drive, the intention for every row, that is parsed, is to be uploaded to my database.
When the importFile()
is executed it prints the path but then it prints "The CSV file couldn't be read."
Q1: What am I doing wrong?
import UIKit
import MobileCoreServices
import CSVImporter
class importBatchVC: UIViewController,UIDocumentPickerDelegate,UINavigationControllerDelegate {
var path=""
var docURL = URL(string: "")
@IBAction func chooseDoc(_ sender: Any) {
let importMenu = UIDocumentPickerViewController(documentTypes: [String(kUTTypeContent),String(kUTTypePlainText)], in: .import)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
self.present(importMenu, animated: true, completion: nil
}
@IBAction func importFile(_ sender: Any) {
if docURL==nil {
let alert = UIAlertController(title: "Error", message: "Please select a spreadsheet.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
} else {
do {
self.path = String(describing:docURL)
print(path)
let importer = CSVImporter<[String]>(path: path)
importer.startImportingRecords { $0 }.onFail {
print("The CSV file couldn't be read.")
}.onProgress { importedDataLinesCount in
print("\(importedDataLinesCount) lines were already imported.")
}.onFinish { importedRecords in
print("Did finish import with \(importedRecords.count) records.")
}
}
}
}
@IBAction func cancel(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
print("The Url is : \(String(describing: url))")
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
do {
try FileManager.default.moveItem(at: url.standardizedFileURL, to: documentDirectory.appendingPathComponent(url.lastPathComponent))
self.docURL = documentDirectory.appendingPathComponent(url.lastPathComponent)
print("now check this: \(docURL!)")
} catch {
print(error)
}
}
func documentMenu(_ documentMenu: UIDocumentPickerViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
present(documentPicker, animated: true, completion: nil)
}
}
Note: This is repost question that was closed as "duplicate" and the code above is the implementation suggested.
My initial and preferred implementation was to get the link of the iCloud drive file via documentPicker
and parse/upload it to database on the fly but i was getting the same error as i get now: "The CSV file couldn't be read."
The explanation that was provided by Leo Dabus was:
"UIDocumentPickerModeImport The URL refers to a copy of the selected document. This document is a temporary file. It remains available only until your application terminates. To keep a permanent copy, you must move this file to a permanent location inside your sandbox."
Q2: Considering that i don't need to keep a permanent file-just need to keep it till its parsed and then it will be in my database, why do i need to import it to documentDirectory
, is there any way i could parse it via the link i get from documentPicker?
Code for my initial implementation:
import UIKit
import MobileCoreServices
import CSVImporter
class importBatchVC: UIViewController,UIDocumentPickerDelegate,UINavigationControllerDelegate {
var path=""
var docURL = URL(string: "")
@IBAction func chooseDoc(_ sender: Any) {
let importMenu = UIDocumentPickerViewController(documentTypes: [String(kUTTypeContent),String(kUTTypePlainText)], in: .import)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
self.present(importMenu, animated: true, completion: nil)
}
@IBAction func importFile(_ sender: Any) {
if docURL==nil {
let alert = UIAlertController(title: "Error", message: "Please select a spreadsheet.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
else{
do {
self.path = docURL!.path
print(path)
let importer = CSVImporter<[String]>(path: path)
importer.startImportingRecords { $0 }.onFinish { importedRecords in
for record in importedRecords {
// record is of type [String] and contains all data in a line
print(record)
}
}
}
}
}
@IBAction func cancel(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
docURL = url as URL
print("The Url is : \(String(describing: url))")
}
func documentMenu(_ documentMenu: UIDocumentPickerViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
present(documentPicker, animated: true, completion: nil)
}
}
Q1:
The problem I see when trying to parse the file at docURL
is the following line:
self.path = String(describing:docURL)
This is not the correct way to convert a file URL into a path. The proper code is:
self.path = docURL.path
Q2:
When you use the UIDocumentPickerViewController
in import
mode, the URL you are given in the delegate method is only valid until the end of the delegate method. This is why you must copy/move the selected file.
From the documentation:
The URLs refer to a copy of the selected documents. These documents are temporary files. They remain available only until your application terminates. To keep a permanent copy, move these files to a permanent location inside your sandbox.
So yes, you must make a copy. But you can delete the file when you are done parsing it.