core-dataswift3export-to-csvmfmailcomposerxcode8

Exporting Core Data structure to .csv file attached to mail XCODE 8.1 Swift 3


I'm making an app that needs to take a managed object array from core data and export it to a csv file that I plan to attach to an email being sent out using the mfMailComposer system. I have the data properly stored in the core data systems and the mail composer functionality seems to be working. I'm reaching a snag when I try to find the correct process by which to export the data.

I have already taken a long look at both of these posts attempting to determine a solution:

from 2012, seems very outdated: how to export Core Data to CSV

from 2016, more recent, but swift 3 and Xcode 8 have since been released and I worry this has become outdated as well: How to create a CSV file from Core Data (swift)

I have been attempting to try the solutions proposed in the second link, but much of the code gets marked as incorrect when typing it, so I believe it is now obsolete with the upgrade.

The code below is based off of the second post and therefore, likely outdated, but in order to provide a reference of the process I am trying to accomplish...

// Called by the press of xcode UI button
@IBAction func ExportToCSV(_ sender: AnyObject)
{
    // Make our mail composer controller and fill it with the proper information
    let mailComposeViewController = configuredMailComposeViewController()

    // If the composer is functioning properly ...
    if MFMailComposeViewController.canSendMail()
    {
        // ... Present the generated mail composer controller
        self.present(mailComposeViewController, animated: true, completion: nil)
    }
    else
    {
        // ... Otherwise, show why it is not working properly
        self.showSendMailErrorAlert()
    }


}

// Used to set up the body of the outgoing email
func configuredMailComposeViewController() -> MFMailComposeViewController
{
    // Establish the controller from scratch
    let mailComposerVC = MFMailComposeViewController()
    mailComposerVC.mailComposeDelegate = self

    // Set preset information included in the email
    mailComposerVC.setSubject("Generic email subject")
    mailComposerVC.setMessageBody("Generic email body", isHTML: false)

    // Turn core data for responses into a .csv file

    // Pull core data in
    var CoreDataResultsList = [NSManagedObject]()

    // Register the proper delegate and managed context
    let appDelegate =
        UIApplication.shared.delegate as! AppDelegate

    let managedContext = appDelegate.managedObjectContext

    // Pull the data from core data
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ItemResponses")
    do {
        let results =
            try managedContext!.fetch(fetchRequest)
        CoreDataResultsList = results as! [NSManagedObject]
    } catch let error as NSError {
        print("Could not fetch \(error), \(error.userInfo)")
    }

    // Take the managed object array and turn it into a .csv sring to write in the file
    let csvString = writeCoreObjectsToCSV(objects: CoreDataResultsList, named: "Generic name")
    let data = csvString.dataUsingEncoding(NSUTF8StringEncoding)
    mailComposerVC.addAttachmentData(data, mimeType: "text/csv", fileName: "GenericFilename.csv")

    return mailComposerVC
}

// Takes a managed object and writes it to the .csv file ..?
func writeCoreObjectsToCSV(objects: [NSManagedObject], named: String) -> String
{
    // Make sure we have some data to export
    guard objects.count > 0 else
    {

        return ""
    }
    let firstObject = objects[0]
    let attribs =  Array(firstObject.entity.attributesByName.keys)

    // The attires.reduce function is throwing an error about originally using combine as in the second post, used auto fix, but noteworthy. 
    //Now gives an error that says "No '+' candidates produce the expected contextual result type NSString"
    let csvHeaderString = (attribs.reduce("", {($0 as String) + "," + $1 }) as NSString).substringFromIndex(1) + "\n"

    // This function says that substring from index has been renamed as well as a few other lines within it
    let csvArray = objects.map({object in
        (attribs.map({((object.valueForKey($0) ?? "NIL") as AnyObject).description}).reduce("",combine: {$0 + "," + $1}) as NSString).substringFromIndex(1) + "\n"
    })

    // Again with the reduce issue
    let csvString = csvArray.reduce("", combine: +)

    return csvHeaderString + csvString
}

New the bottom of the code I have commented in the multiple errors with the suggested code from the second post and the issues pertaining after I use xCode's auto-fix feature.

I would like to thank you in advance for helping me with this issue. I am merely looking for the most up-to-date way to export core data as a .csv file and send it out. Thanks!


Solution

  • I ended up working around it. At first I didn't understand how .csv files were written until I saw the acronym stood for "comma-separated values". Then it all clicked for me and I wrote my own, I did a more manual route for the head entry but the data on the following lines is still auto mated.

    Below is the relevant functions in their new working form:

    // Called by the press of xcode UI button
    @IBAction func ExportToCSV(_ sender: AnyObject)
    {
        // Make our mail composer controller and fill it with the proper information
        let mailComposeViewController = configuredMailComposeViewController()
    
        // If the composer is functioning properly ...
        if MFMailComposeViewController.canSendMail()
        {
            // ... Present the generated mail composer controller
            self.present(mailComposeViewController, animated: true, completion: nil)
        }
        else
        {
            // ... Otherwise, show why it is not working properly
            self.showSendMailErrorAlert()
        }
    
    
    }
    
    // Used to set up the body of the outgoing email
    func configuredMailComposeViewController() -> MFMailComposeViewController
    {
        // Establish the controller from scratch
        let mailComposerVC = MFMailComposeViewController()
        mailComposerVC.mailComposeDelegate = self
    
        // Set preset information included in the email
        mailComposerVC.setSubject("Generic Subject")
        mailComposerVC.setMessageBody("Generic Email Body", isHTML: false)
    
        // Turn core data for responses into a .csv file
    
        // Pull core data in
        var CoreDataResultsList = [NSManagedObject]()
    
        // Register the proper delegate and managed context
        let appDelegate =
            UIApplication.shared.delegate as! AppDelegate
    
        let managedContext = appDelegate.managedObjectContext
    
        // Pull the data from core data
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ItemResponses")
        do {
            let results =
                try managedContext!.fetch(fetchRequest)
            CoreDataResultsList = results as! [NSManagedObject]
        } catch let error as NSError {
            print("Could not fetch \(error), \(error.userInfo)")
        }
    
        // Take the managed object array and turn it into a .csv sring to write in the file
        // In doing this, we are writing just like we would to any string
        let csvString = writeCoreObjectsToCSV(objects: CoreDataResultsList)
        let data = csvString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false)
        mailComposerVC.addAttachmentData(data!, mimeType: "text/csv", fileName: "GenericFilename.csv")
    
        return mailComposerVC
    }
    
    // Takes a managed object and writes it to the .csv file ..?
    func writeCoreObjectsToCSV(objects: [NSManagedObject]) -> NSMutableString
    {
        // Make sure we have some data to export
        guard objects.count > 0 else
        {
    
            return ""
        }
    
        var mailString = NSMutableString()
    
    
        mailString.append("Generic Header 1, Generic Header 2, Generic Header 3")
    
        for object in objects
        {
            // Put "\n" at the beginning so you don't have an extra row at the end
            mailString.append("\n\(object.value(forKey: "Generic Core Data Key 1")!),\(object.value(forKey: "Generic Core Data Key 2")!), \(object.value(forKey: "Generic Core Data Key 3")!)")
        }
        return mailString
    }
    

    I'm having an issue where one of my keys is a string containing commas and need a proper way to escape from them. I've heard double quotes is how to do it but inserting them gave me no success.

    Regardless, this is one current way to take core data into an array and write it to a string, save it to a .csv file and mail it out. This answers my question, but my next task is reading the data back in. I have no idea how to access the file to do that on an iPad. If you come across this and know how to do that, please let me know! I will probably be making another post on that topic if I can't find a solution and will then drop a new question and a link to that in the replies below this answer.

    Thank you!