Installation • Usage • Issues • Contributing • License
Import CSV files line by line with ease.
"Why yet another CSVImporter" you may ask. "There is already SwiftCSV and CSwiftV" you may say. The truth is that these frameworks work well for smaller CSV files. But once you have a really large CSV file (or could have one, because you let the user import whatever CSV file he desires to) then those solutions will probably cause delays and memory issues for some of your users.
CSVImporter on the other hand works both asynchronously (prevents delays) and reads your CSV file line by line instead of loading the entire String into memory (prevents memory issues). On top of that it is easy to use and provides beautiful callbacks for indicating failure, progress, completion and even data mapping if you desire to.
Currently the recommended way of installing this library is via Carthage. Cocoapods is supported too.
You can of course also just include this framework manually into your project by downloading it or by using git submodules.
Simply add this line to your Cartfile:
github "Flinesoft/CSVImporter" ~> 1.7
And run carthage update
. Then drag & drop the HandySwift.framework in the Carthage/build folder to your project. Also do the same with the dependent framework HandySwift
. Now you can import CSVImporter
in each class you want to use its features. Refer to the Carthage README for detailed / updated instructions.
Add the line pod 'CSVImporter'
to your target in your Podfile
and make sure to include use_frameworks!
at the top. The result might look similar to this:
platform :ios, '8.0'
use_frameworks!
target 'MyAppTarget' do
pod 'CSVImporter', '~> 1.7'
end
Now close your project and run pod install
from the command line. Then open the .xcworkspace
from within your project folder.
Build your project once (with Cmd+B
) to update the frameworks known to Xcode. Now you can import CSVImporter
in each class you want to use its features.
Refer to CocoaPods.org for detailed / updates instructions.
Please have a look at the UsageExamples.playground and the Tests/Code/CSVImporterSpec.swift files for a complete list of features provided.
Open the Playground from within the .xcworkspace
in order for it to work.
First create an instance of CSVImporter and specify the type the data within a line from the CSV should have. The default data type is an array of String
objects which would look like this:
let path = "path/to/your/CSV/file"
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
}
}
Note that you can specify an alternative delimiter when creating a CSVImporter
object alongside the path. The delimiter defaults to ,
if you don't specify any.
CSVImporter works asynchronously by default and therefore doesn't block the main thread. As you can see the onFinish
method is called once it finishes for using the results. There is also onFail
for failure cases (for example when the given path doesn't contain a CSV file), onProgress
which is regularly called and provides the number of lines already processed (e.g. for progress indicators). You can chain them as follows:
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.")
}
By default the real importing work is done in the .utility
global background queue and callbacks are called on the main
queue. This way the hard work is done asynchronously but the callbacks allow you to update your UI. If you need a different behavior, you can customize the queues when creating a CSVImporter object like so:
let path = "path/to/your/CSV/file"
let importer = CSVImporter<[String]>(path: path, workQosClass: .background, callbacksQosClass: .utility)
If you know your file is small enough or blocking the UI is not a problem, you can also use the synchronous import methods to import your data. Simply call importRecords
instead of startImportingRecords
and you will receive the end result (the same content as in the onFinish
closure when using startImportingRecords
) directly:
let importedRecords = importer.importRecords { $0 }
Note that this method doesn't have any option to get notified about progress or failure – you just get the result. Check if the resulting array is empty to recognize potential failures.
As stated above the default type is a [String]
but you can provide whatever type you like. For example, let's say you have a class like this
class Student {
let firstName: String, lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
and your CSV file looks something like the following
Harry,Potter
Hermione,Granger
Ron,Weasley
then you can specify a mapper as the closure instead of the { $0 }
from the examples above like this:
let path = "path/to/Hogwarts/students"
let importer = CSVImporter<Student>(path: path)
importer.startImportingRecords { recordValues -> Student in
return Student(firstName: recordValues[0], lastName: recordValues[1])
}.onFinish { importedRecords in
for student in importedRecords {
// Now importedRecords is an array of Students
}
}
Last but not least some CSV files have the structure of the data specified within the first line like this:
firstName,lastName
Harry,Potter
Hermione,Granger
Ron,Weasley
In that case CSVImporter can automatically provide each record as a dictionary like this:
let path = "path/to/Hogwarts/students"
let importer = CSVImporter<[String: String]>(path: path)
importer.startImportingRecords(structure: { (headerValues) -> Void in
print(headerValues) // => ["firstName", "lastName"]
}) { $0 }.onFinish { importedRecords in
for record in importedRecords {
print(record) // => e.g. ["firstName": "Harry", "lastName": "Potter"]
print(record["firstName"]) // prints "Harry" on first, "Hermione" on second run
print(record["lastName"]) // prints "Potter" on first, "Granger" on second run
}
}
Note: If a records values count doesn't match that of the first lines values count then the record will be ignored.
Contributions are welcome. Please just open an Issue on GitHub to discuss a point or request a feature or send a Pull Request with your suggestion.
Pull requests with new features will only be accepted when the following are given:
- Tests for the new feature exist and all tests pass successfully for all targets.
- Usage examples of the new feature are given in the Playgrounds.
This library is released under the MIT License. See LICENSE for details.