我的 iOS 应用在尝试显示 UIActivityViewController 时崩溃
My iOS app crashes trying to display a UIActivityViewController
基本上我有一个应用程序接受用户输入(在 alertcontroller 的 5 个文本字段中),这些输入都连接成一个字符串,以一种看起来像是 csv 文件中的一行的形式存储到核心数据作为一个实体。然后该实体将显示在表视图中。我希望能够导出 csv 文件以通过电子邮件将其发送到其他地方。所有这些都将在 iOS 设备本身中完成。
我参考了()中的代码来做核心数据条目导出的代码。我的应用程序能够成功构建。在 tableview 中添加和删除项目都可以正常工作。直到我点击我的导出按钮,应用程序才崩溃。
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 { [=11=].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
}
}
它导致 "Thread 1: signal SIGABRT" 错误,但我在我的代码中找不到任何拼写错误。
这是第一个调用抛出堆栈:
完整的错误代码:
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:
(
throw调用栈在上图中
在 iPad 上,UIActivityViewController
需要在弹出窗口中显示,否则应用程序会崩溃,see this answer 了解详情。
基本上我有一个应用程序接受用户输入(在 alertcontroller 的 5 个文本字段中),这些输入都连接成一个字符串,以一种看起来像是 csv 文件中的一行的形式存储到核心数据作为一个实体。然后该实体将显示在表视图中。我希望能够导出 csv 文件以通过电子邮件将其发送到其他地方。所有这些都将在 iOS 设备本身中完成。
我参考了(
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 { [=11=].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
}
}
它导致 "Thread 1: signal SIGABRT" 错误,但我在我的代码中找不到任何拼写错误。
这是第一个调用抛出堆栈:
完整的错误代码:
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: (
throw调用栈在上图中
在 iPad 上,UIActivityViewController
需要在弹出窗口中显示,否则应用程序会崩溃,see this answer 了解详情。