Easy Core Data CSV Exporter in Swift

A new app I’m working on has a Core Data back end and I wanted to implement a quick way to export all the entities into a CSV file and dump it into the Desktop. 

Here are the snippets to do it:

1. Make sure you get the NSObjects you want to export (in this case I use a function that will return an array with the fetched NSObjectSubclass Transaction)

   private func provideAllTransactions() -> Array<Transaction>{

        let fetchRequest = NSFetchRequest(entityName: “Transaction”)

        fetchRequest.sortDescriptors = [NSSortDescriptor(key: “date”, ascending: false), NSSortDescriptor(key: “concept”, ascending: true)]

        return try! managedObjectContext.executeFetchRequest(fetchRequest) as! Array<Transaction>

    }

2. Convert to text, properly separating by commas and lines

    private func convertTransactionsToCsvString(transactions: Array<Transaction>)->String{

        var result = “HEADER 1, HEADER 2, HEADER 3, \r”//Headers

        

        for transactionIterator in transactions{ //Data

            result = result + “\”” + (transactionIterator.accountItBelongsTo?.name)! + “\”” + “,”

            result = result + “\”” + ((transactionIterator.date)?.description)! ?? NSDate().description

            result = result + “\”” + “,” + “\”” + (transactionIterator.concept)! + “\””

            result = result + “\”” + “\r”

        }

        

        return result

    }

3. Save to a path 

    func exportAllToCsv() -> Bool{

        

        let contentsToWrite = convertTransactionsToCsvString(provideAllTransactions())

        

        //Select path

        let path = “/Users/USER_NAME/Desktop/csvTest.csv”

        //Try to save it

        do {

            try contentsToWrite.writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding)

        }catch{

            print(“ERROR: Export could not be saved in %”, path)

        }

        

        return true

 

    }

Note that in this example the path is hardcoded to the desktop of the user named USER_NAME, you should have the user choose where he wants to save it or you can hardcode a location in your disk. 

If you would have a NSDictionary it would be almost the same but starting at point 2 with the dictionary results. 

Questions / comments? I’m at @MarcMasVi

Marc

 

PS. Here is how you would have the path be selected by the user:        

 

    privatefunc userToChooseAFileInModalPannel() -> String?{

        let pannel: NSOpenPanel = NSOpenPanel()

        pannel.canChooseDirectories = true

        pannel.canChooseFiles = false

        pannel.allowsMultipleSelection = false

        pannel.runModal()

        

        return pannel.URL?.path

    }

 then in point 3 you would do 

//Select path

        let path = userToChooseAFileInModalPannel() + “/test.csv”


CSV Parser Swift 2

One of the features I’m adding to an upcoming app is the ability to import CSV files. For that purpose I looked around for code already available (and found several options, see below), however none of them compelled me enough so I decided to create my own. 

The main objective was to make it as simple as possible and, of course, coded in Swift 2. Note however that I’m still learning so if you would have any suggestions / feedback send them my way! Here it is in all of its glory:

//

//  CSVImporter.swift

//  Controller

//

//  Created by Marc on 5/8/15.

//  Copyright (c) 2015 MMV Solucions S.L. All rights reserved.

//

 

import Cocoa

 

class CSVImporter: NSObject {

 

    var columnDelimiter = “,”

    var textDelimiter = “\””

    var rowDelimiter = “\n”

    var doesItHaveHeader = true

    var rowsFromString : Array<String> = []

    var finalParsedCsv : Array<Array<String>> = []

    

    

 

    

    func retrieveCsvFileAndReturnRows(path path: String ) -> Array<Array<String>>? { //Main

        

        let stringToParse: String?

        do{

            stringToParse = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)

        } catch _ {

            stringToParse = nil

        }

        var unwrappedStringToParse=“”

        

        if (stringToParse == nil) { return nil } else {

            unwrappedStringToParse = stringToParse! }

        

        //Cleaning string

        unwrappedStringToParse = stringToParse!.stringByReplacingOccurrencesOfString(“\r”, withString: “\n”)

        unwrappedStringToParse = unwrappedStringToParse.stringByReplacingOccurrencesOfString(“\n\n”, withString: “\n”)

        

        //Storing by rows

        rowsFromString = unwrappedStringToParse.componentsSeparatedByString(rowDelimiter)

        

        //Removing header

        if (doesItHaveHeader == true){

            rowsFromString.removeAtIndex(0)

        }

        

        //Converting and adding rows

        for iteratorString in rowsFromString {

            

            if(iteratorString.rangeOfString(textDelimiter) != nil){ //We have detected double quotes

                let arrayContainingDoubleQuotes = iteratorString.componentsSeparatedByString(columnDelimiter)

                var newArrayAccountingForQuotes : Array <String> = []

                var tempArrayContainingNewWord : Array <String> = []

                var inDoubleQuoteLoop = false

                var newStringFromSplittedWord = “”

                for stringToTestForDoublequotes in arrayContainingDoubleQuotes{

                    if stringToTestForDoublequotes.rangeOfString(textDelimiter) != nil || inDoubleQuoteLoop == true {

                        if stringToTestForDoublequotes.rangeOfString(textDelimiter) != nil && inDoubleQuoteLoop == false {

                            tempArrayContainingNewWord.append(stringToTestForDoublequotes)

                            inDoubleQuoteLoop = true

                        }else{

                            tempArrayContainingNewWord.append(stringToTestForDoublequotes)

                            newStringFromSplittedWord = “”.join(tempArrayContainingNewWord)

                            newArrayAccountingForQuotes.append(newStringFromSplittedWord)

                            //Maybe add here option to remove “

                            inDoubleQuoteLoop = false

                        }

 

                    }else{ //No double quotes found and not in the middle of text, add to new array

                     newArrayAccountingForQuotes.append(stringToTestForDoublequotes)

                    }

                }

                finalParsedCsv.append(newArrayAccountingForQuotes)

            }else{

                finalParsedCsv.append(iteratorString.componentsSeparatedByString(columnDelimiter))

            }

            

        }

        

        //Final cleaning

        var row = 0

        for iteratorForFinalCleaning in finalParsedCsv{

            var column = 0

            for _ in iteratorForFinalCleaning{

                //Get the text of these coordinates

                var tempStringToRemoveCommas = finalParsedCsv[row][column]

                if (tempStringToRemoveCommas.rangeOfString(textDelimiter) != nil){

                //Do the needed changes into the text

                tempStringToRemoveCommas = tempStringToRemoveCommas.stringByReplacingOccurrencesOfString(textDelimiter, withString: “”)

                //Add result back to the arrayOfParsedContent

                finalParsedCsv[row].insert(tempStringToRemoveCommas, atIndex: column)

                }

                column++

            }

            row++

        }

        

        returnfinalParsedCsv

    }

    

 

}

 

 

Remember I’m still on my Swift beginnings, if you want other options check out SwiftCSV or this post