-1

Basically I have an app that takes in user input (in 5 textfields in an alertcontroller) that are all concatenated in to a string to be in a form that looks like it is a row in a csv file and stored to coredata as an entity. This entity will then be displayed in a tableview. I would like to be able to export the csv file to email it elsewhere. All this will be done in the iOS device itself.

I have referred to the code in (How to Export Core Data to CSV in Swift 3?) to do the code for the exporting of coredata entries. My app is able to be built successfully. Adding and removing items from the tableview all work fine. It is only until I click on my export button that the app crashes.

class ViewController: UITableViewController, NSFetchedResultsControllerDelegate {

    let cellId = "cellId"

    fileprivate lazy var fetchedResultsController: NSFetchedResultsController<AlarmItem> = {
        //create fetch request
        let fetchRequest: NSFetchRequest<AlarmItem> = AlarmItem.fetchRequest()

        //configure fetch request
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "alarmAttributes", ascending: true)]
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)

        let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

        fetchedResultsController.delegate = self

        return fetchedResultsController
    }()

    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

        switch(type) {
        case .insert:
            if let indexPath = newIndexPath {
                tableView.insertRows(at: [indexPath], with: .fade)
            }
            break;
        case .delete:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
            break;
        case .update:

            if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) {
                configureCell(cell, at: indexPath)
            }
            break;
        case .move:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
            if let newIndexPath = newIndexPath {
                tableView.insertRows(at: [newIndexPath], with: .fade)
            }
            break;
            @unknown default:
                print("Something odd is happening")
            }
        }


        override func viewDidLoad() {
            super.viewDidLoad()


        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            do {
                try fetchedResultsController.performFetch()
        } catch let err as NSError {
            print("Failed to fetch items", err)
        }
    }

        @objc func addAlarmItem(_ sender: AnyObject) {

        let alertController = UIAlertController(title: "Add New Item", message: "Please fill in the blanks", preferredStyle: .alert)
        let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in

            //combined string of attributes
            let myStrings: [String] = alertController.textFields!.compactMap { $0.text }
            let myText = myStrings.joined(separator: ", ")

            self.save(myText)
            self.tableView.reloadData()
        }

        let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: nil)

        alertController.addTextField { (textField) in
            textField.placeholder = "Enter Name of Engineer"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Enter Date of Alarm in DD/MM/YYYY"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Enter Time of Alarm in 24h (eg: 2300)"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Please indicate True/False (type True or False)"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Insert comments (if any), or NIL"
        }


    func save(_ itemName: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "AlarmItem", in: managedContext)!
        let item = NSManagedObject(entity: entity, insertInto: managedContext)
        item.setValue(itemName, forKey: "alarmAttributes")

        do {
            try managedContext.save()
            tableView.reloadData()

        } catch let err as NSError {
            print("Failed to save an item", err)
        }
    }


 @objc func exportCSV(_ sender: AnyObject) {
        exportDatabase()
    }

    func exportDatabase() {
        let exportString = createExportString()
        saveAndExport(exportString: exportString)
    }

    func saveAndExport(exportString: String) {
        let exportFilePath = NSTemporaryDirectory() + "itemlist.csv"
        let exportFileUrl = NSURL(fileURLWithPath: exportFilePath)
        FileManager.default.createFile(atPath: exportFilePath, contents: NSData() as Data, attributes: nil)
        var fileHandle: FileHandle? = nil

        do {
            fileHandle = try FileHandle(forUpdating: exportFileUrl as URL)
        } catch {
            print("filehandle has error")
        }

        if fileHandle != nil {
            fileHandle!.seekToEndOfFile()
            let csvData = exportString.data(using: String.Encoding.utf8, allowLossyConversion: false)
            fileHandle!.write(csvData!)
            fileHandle!.closeFile()

            let firstActivityItem = NSURL(fileURLWithPath: exportFilePath)
            let activityViewController : UIActivityViewController = UIActivityViewController(activityItems: [firstActivityItem], applicationActivities: nil)

            activityViewController.excludedActivityTypes = [
                UIActivity.ActivityType.assignToContact,
                UIActivity.ActivityType.saveToCameraRoll,
                UIActivity.ActivityType.postToFlickr,
                UIActivity.ActivityType.postToVimeo,
                UIActivity.ActivityType.postToTencentWeibo
            ]
            self.present(activityViewController, animated: true, completion: nil)
        }
    }

    func createExportString() -> String {
        var alarmAttributes: String?
        var export: String = NSLocalizedString("Engineer Name,Date of Alarm,Time of Alarm,True or False,Engineer Comments \n", comment: "")

        for (index, AlarmItem) in fetchedStatsArray.enumerated() {
            if index <= fetchedStatsArray.count - 1 {
                alarmAttributes = AlarmItem.value(forKey: "alarmAttributes") as! String?
                let alarmAttributeStrings = alarmAttributes
                export += "\(alarmAttributeStrings ?? "0") \n"
            }
        }
        print("the app will now print: \(export) ")
        return export
    }

        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
            let sectionInfo = fetchedResultsController.sections![section]
            return sectionInfo.numberOfObjects
        }

        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
            let alarmItem = fetchedResultsController.object(at: indexPath) as NSManagedObject
            cell.textLabel?.text = alarmItem.value(forKeyPath: "alarmAttributes") as? String
        return cell
    }

    func tableView(_ tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
        return true
     }

    overr ide func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
     }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        guard editingStyle == .delete else { return }

    //fetch
            let toBeDeleted = fetchedResultsController.object(at: indexPath)

    //delete
        fetchedResultsController.managedObjectContext.delete(toBeDeleted)

        do {
            try fetchedResultsController.managedObjectContext.save()
        } catch let err as NSError {
        print("failed to save item", err)
        }

    }

    func tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "AlarmItem", for: indexPath)
        configureCell(cell, at: indexPath)

        return cell
    }

    func configureCell(_ cell: UITableViewCell, at indexPath: IndexPath) {
        let alarmItem = fetchedResultsController.object(at: indexPath)

        //configure cell
        cell.textLabel?.text = alarmItem.value(forKeyPath: "alarmAttributes") as? String
   }
}

It results in a "Thread 1: signal SIGABRT" error but I cannot find any typos in my code.

Here's the first call throw stack: first call throw stack

The full error code:

2019-06-26 10:04:33.843955+0800 TrueFalseAlarmV3[913:13287] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
the app will now print: Engineer Name,Date of Alarm,Time of Alarm,True or False,Engineer Comments 

2019-06-26 10:31:56.087370+0800 TrueFalseAlarmV3[913:13287] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/danialaqil/Library/Developer/CoreSimulator/Devices/BF0A3A59-A660-4F1D-B0FE-F0D226479D8D/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2019-06-26 10:31:56.087928+0800 TrueFalseAlarmV3[913:13287] [MC] Reading from private effective user settings.
2019-06-26 10:31:56.088526+0800 TrueFalseAlarmV3[913:18176] [MC] Filtering mail sheet accounts for bundle ID: imdadsl.TrueFalseAlarmV3, source account management: 1
2019-06-26 10:31:56.100398+0800 TrueFalseAlarmV3[913:18176] [MC] Filtering mail sheet accounts for bundle ID: imdadsl.TrueFalseAlarmV3, source account management: 2
2019-06-26 10:31:56.445312+0800 TrueFalseAlarmV3[913:13287] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIActivityViewController (). In its current trait environment, the modalPresentationStyle of a UIActivityViewController with this style is UIModalPresentationPopover. You must provide location information for this popover through the view controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem.  If this information is not known when you present the view controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'
*** First throw call stack:
(

the throw call stack is in the image above

rmaddy
  • 314,917
  • 42
  • 532
  • 579
danaq
  • 117
  • 11
  • Your error is when presenting a view controller. Given the stack trace, there should be more of an error message and it's probably about not setting the barButtonItem or the sourceVIew. – rmaddy Jun 26 '19 at 02:08
  • I'm sorry but what does that mean? My code for the button is like this: navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Export CSV", style: .plain, target: self, action: #selector(exportCSV)) – danaq Jun 26 '19 at 02:25
  • You need to show the full error message that appears before the stacktrace. And please paste errors as text, not pictures. – rmaddy Jun 26 '19 at 02:30
  • I have added the error. Thank you so much for your assistance – danaq Jun 26 '19 at 02:34

1 Answers1

1

On iPad, UIActivityViewController needs to be presented in a popover or the app will crash, see this answer for details.

Barnyard
  • 273
  • 3
  • 11