Creating and Exporting a CSV File In Swift

A lot of apps offer options for the user to export data. I've even offered it as an option in my MPG Tracking app as a paid feature. One common way to do this is with a .csv file, so let's see how that's done.

HOW A CSV FILE WORKS

CSV Stands for Comma Separated Values. A .csv file is a text-based file where each value is separated by a comma. When the file is opened by a program like Excel, the software reads the data and separates each value into its own cell.

One row of data in a csv file would look like this - "Date,Task,Time Started,Time Ended"
You might want the second row to look like this - "9/12,Designing,09:35,11:02"

In swift, this would be written as a simple string - "Date,Task,Time Started,Time Ended\n9/12,Designing,09:35,11:02"
As you can see, creating a new line requires "\n". No commas or spaces are required. 

Ok cool, but can you give us a practical example?
Sure, but let's go over some important (but quick) steps first.

You need to create a filename and create a path to the file in the temporary directory.

let fileName = "Tasks.csv"
let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(fileName)

Then you want to create the initial text for the csv file

var csvText = "Date,Task,Time Started,Time Ended\n"

As you can see, I've included the line break "\n" at the end of the text. It's good to either always include it at the beginning or the end of each line as long as you are consistent.

In this scenario, we would then do a loop of tasks completed within the app in order to create a new line of data for for each task and add it to the csv file.

for task in tasks {
    let newLine = "\(task.date),\(task.name),\(task.startTime),\(task.endTime)\n"
    csvText.appendContentsOf(newLine)
}

For the final step of creating the csv file, we need to actually write the file to the path we created earlier.

do {
    try csvText.writeToURL(path, atomically: true, encoding: NSUTF8StringEncoding)
} catch {
    print("Failed to create file")
    print("\(error)")
}

Great! You've created a csv file! Well now you have to decide what to do with it. You can email it, save it, open it in another app, etc. I'm going to show you how to email it.

EXPORTING YOUR CSV FILE

Note: This may not work in simulator

Using the activity view controller is actually very simple and only requires 2 lines

let vc = UIActivityViewController(activityItems: [path], applicationActivities: [])
presentViewController(vc, animated: true, completion: nil)

You can also choose to exclude certain options from showing up as a share option like so:

vc.excludedActivityTypes = [
    UIActivityTypeAssignToContact,
    UIActivityTypeSaveToCameraRoll,
    UIActivityTypePostToFlickr,
    UIActivityTypePostToVimeo,
    UIActivityTypePostToTencentWeibo,
    UIActivityTypePostToTwitter,
    UIActivityTypePostToFacebook,
    UIActivityTypeOpenInIBooks
]

FULL EXAMPLE

@IBAction func export(sender: AnyObject) {
        
        let fileName = "\(currentCar.nickName).csv"
        let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(fileName)
        
        var csvText = "Make,Model,Nickname\n\(currentCar.make),\(currentCar.model),\(currentCar.nickName)\n\nDate,Mileage,Gallons,Price,Price per gallon,Miles between fillups,MPG\n"
        
        currentCar.fillups.sortInPlace({ $0.date.compare($1.date) == .OrderedDescending })
        
        let count = currentCar.fillups.count
        
        if count > 0 {
            
            for fillup in currentCar.fillups {
                
                let dateFormatter = NSDateFormatter()
                dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle
                let convertedDate = dateFormatter.stringFromDate(fillup.date)
                
                let newLine = "\(convertedDate),\(fillup.mileage),\(fillup.gallons),\(fillup.priceTotal),\(fillup.priceGallon),\(fillup.mileDelta),\(fillup.MPG)\n"
                
                csvText.appendContentsOf(newLine)
            }
            
            do {
                try csvText.writeToURL(path, atomically: true, encoding: NSUTF8StringEncoding)
                                
                let vc = UIActivityViewController(activityItems: [path], applicationActivities: [])
                vc.excludedActivityTypes = [
                    UIActivityTypeAssignToContact,
                    UIActivityTypeSaveToCameraRoll,
                    UIActivityTypePostToFlickr, 
                    UIActivityTypePostToVimeo,
                    UIActivityTypePostToTencentWeibo,
                    UIActivityTypePostToTwitter,
                    UIActivityTypePostToFacebook,
                    UIActivityTypeOpenInIBooks
                ]
                presentViewController(vc, animated: true, completion: nil)

            } catch {
                
                print("Failed to create file")
                print("\(error)")
            }
            
        } else {
            showErrorAlert("Error", msg: "There is no data to export")
        }
    }

And this is what it looks like

SEND YOUR CSV FILE IN A CUSTOM EMAIL

Note: This may not work in simulator

First, you'll need to get set up for sending emails. Import MessageUI and add MFMailCompseViewControllerDelegate to your class.

import MessageUI

class ViewController: UIViewController, MFMailComposeViewControllerDelegate {

Then you need to get the email to pop up with some pre-set information:

if MFMailComposeViewController.canSendMail() {
    let emailController = MFMailComposeViewController()
    emailController.mailComposeDelegate = self
    emailController.setToRecipients([]) //I usually leave this blank unless it's a "message the developer" type thing
    emailController.setSubject("Here is your fancy email")
    emailController.setMessageBody("Wow, look at this cool email", isHTML: false)
}

Let's quickly run through what's happening here. First, we're checking if it's even possible to check mail by creating the if statement. Within the if statement, we're initializing the mail view controller and setting the delegate to self. From there, we can pre-load recipients, subject, and body. I have isHTML set to false, it shouldn't really matter if the message is only text.

But wait! We still need to do 2 things before we can send this email. We need to attach the csv file and get the email to actually pop up onto the screen. Go back into the if statement from before and add the following two lines

emailController.addAttachmentData(NSData(contentsOfURL: path)!, mimeType: "text/csv", fileName: "Tasks.csv")
                    
presentViewController(emailController, animated: true, completion: nil)

And that's all we need in order to create and email a csv file except for one small piece. With the code as it is, when you send or cancel the email, it will not dismiss the screen and the email will be visible forever until you close the app, so now we have to add a function to tell the app what do do when the user is done with the email view controller.

func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
        controller.dismissViewControllerAnimated(true, completion: nil)
    }

 

FULL EXAMPLE

@IBAction func export(sender: AnyObject) {
        
        let fileName = "\(currentCar.nickName).csv"
        let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(fileName)
        
        var csvText = "Make,Model,Nickname\n\(currentCar.make),\(currentCar.model),\(currentCar.nickName)\n\nDate,Mileage,Gallons,Price,Price per gallon,Miles between fillups,MPG\n"
        
        currentCar.fillups.sortInPlace({ $0.date.compare($1.date) == .OrderedDescending })
        
        let count = currentCar.fillups.count
        
        if count > 0 {
            
            for fillup in currentCar.fillups {
                
                let dateFormatter = NSDateFormatter()
                dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle
                let convertedDate = dateFormatter.stringFromDate(fillup.date)
                
                let newLine = "\(convertedDate),\(fillup.mileage),\(fillup.gallons),\(fillup.priceTotal),\(fillup.priceGallon),\(fillup.mileDelta),\(fillup.MPG)\n"
                
                csvText.appendContentsOf(newLine)
            }
            
            do {
                try csvText.writeToURL(path, atomically: true, encoding: NSUTF8StringEncoding)
                
                if MFMailComposeViewController.canSendMail() {
                    let emailController = MFMailComposeViewController()
                    emailController.mailComposeDelegate = self
                    emailController.setToRecipients([])
                    emailController.setSubject("\(currentCar.nickName) data export")
                    emailController.setMessageBody("Hi,\n\nThe .csv data export is attached\n\n\nSent from the MPG app: http://www.justindoan.com/mpg-fuel-tracker", isHTML: false)
                    
                    emailController.addAttachmentData(NSData(contentsOfURL: path)!, mimeType: "text/csv", fileName: "\(currentCar.nickName).csv")
                    
                    presentViewController(emailController, animated: true, completion: nil)
                }

            } catch {
                
                print("Failed to create file")
                print("\(error)")
            }
            
        } else {
            showErrorAlert("Error", msg: "There is no data to export")
        }
    }
    
    func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
        controller.dismissViewControllerAnimated(true, completion: nil)
    }

And this is what it looks like