问题总结
我正在编写一个iOS应用,就像待办事项列表和日历之间的混合体。与待办事项列表不同,您不必将所有内容都检查完整。而是将待办事项放置在UITableView中用于活动和非活动的部分中。问题在于,该部分和排序可能会在相对于当前日期和时间的任何时间进行更改。
这是我的第一个iOS应用程序,我真的想确保自己正在学习最佳实践。我真的很想在应用程序首次启动时,何时从后台返回,何时触发通知以及什至在前台刷新计时器时何时何地刷新获取的数据。
当前,该应用程序可以很好地加载核心数据,并且在添加,更改或删除记录时,视图控制器会适当地调整行的节和排序顺序。当我的应用程序位于前台时,当通知被触发时,我还可以刷新单个记录。即,由于触发了通知,非定期提醒从“活动”更改为“非活动”,并且定期提醒也设置了其栏目和下一个提醒日期。
所以我目前在以下方面挣扎最多:
初始加载。在获取数据之后但视图控制器开始显示它之前,如何以及何时刷新首次应用程序加载时的section / nextAlert日期。
应用进入前景。当应用程序移至前台时,如何以及何时刷新数据。这是必需的,因为如果在后台运行应用程序时发送了通知,则直到用户与通知进行交互时,应用程序才会被触发。一旦用户与通知交互(包括简单地将其关闭),我就会更新刷新节和日期,但是如果用户忽略通知并加载应用程序,则数据将过时。
时间已经过去。当用户在应用程序中时,如何以及何时刷新数据,时间已经过去,应该刷新数据。这实际上仅是解决上述问题2的必要条件。
[已添加] 无法从通知启动x-callback-url 。 [我从原始帖子开始就添加了此内容,因为它可能会对我的应用程序的操作方式/时间/位置产生影响。]我的应用程序提醒的全部要点是,用户可以使用x-callback-url。我注意到,如果我的应用程序已经关闭,通知仍会传递,但是当用户对通知执行操作时,它只是将我的应用程序打开到主表视图中,而实际上没有执行启动其他应用程序的操作它应该。我真的不知道为什么,当然,当我关闭该应用程序时,我当然不能调试它;-)所以我也在寻求帮助。
如果很重要,我也希望将x-callback-url功能也添加到我自己的应用程序中。我希望其他应用能够创建,更新或删除提醒记录。与我的通知处理相关的代码如下。
关于我尝试过的内容的背景
我已经研究并尝试了许多不同的方法来解决这些问题。
现在,如果我尝试在刷新后将更改保存到核心数据中,这将变得很奇怪。该表显然是根据当前上下文中记录的更改来更新的,但是当上下文被保存时,它将再次触发更改!由于已经进行了更改,因此在尝试将行从旧位置移动到新位置后,该应用程序崩溃。参见下面的代码。
但是,当应用重新回到前台时,它实际上并没有触发,这让我感到困惑。
当我们从详细信息视图/编辑屏幕中选择返回时,它还具有触发的负面影响。我们不需要,因为segue已经可以创建新记录或更新现有记录并保存上下文。
刷新此处的字段也不会将数据标记为“脏”(已更改),因此表视图控制器不会移动它,并且仅在数据变脏时才触发的上下文保存不会触发当然。我什至尝试在获取后保存上下文,而不检查其是否已更改,但这也不起作用。也就是说,核心数据即使更改了,也确实没有任何改变。
代码在这里
这是tableViewController控制器功能中的.move情况
case .move:
if let oldPath = indexPath, let newPath = newIndexPath {
os_log("RemindersViewController: Move was triggered, now updating row in table. Old path was %{public}@ and new path is %{public}@", log: OSLog.default, type: .info, oldPath as CVarArg, newPath as CVarArg)
RemindersCell, withReminder: anObject as! Reminders)
configureCell(tableView.cellForRow(at: oldPath) as! RemindersCell, withReminder: anObject as! Reminders)
os_log("RemindersViewController: updated moved cell.", log: OSLog.default, type: .info)
// Don't actually try to move it if the old and new path are the same
if (newPath != oldPath) {
os_log("RemindersViewController: Moving row in table.", log: OSLog.default, type: .info)
tableView.moveRow(at: oldPath, to: newPath)
os_log("RemindersViewController: row moved.", log: OSLog.default, type: .info)
}
}
这是我的configureCell函数的简化版本。
func configureCell(_ cell: RemindersCell, withReminder reminder: Reminders) {
cell.labelTitleField!.text = reminder.title ?? "New Reminder"
cell.labelAlertField!.text = reminder.nextAlert!.description
}
这是控制台中与该代码相关的内容:
2019-03-23 12:31:09.307801-0500 Scheduler[5711:2218287] RemindersViewContoller in viewDidLoad: Fetched records successfully.
Refreshing reminders!
2019-03-23 12:31:09.311755-0500 Scheduler[5711:2218287] Reminder 'Test Non-recurring Reminder' section updated to Inactive.
2019-03-23 12:31:09.313254-0500 Scheduler[5711:2218287] RemindersViewController: Move was triggered, now updating row in table. Old path was <NSIndexPath: 0x28078e480> {length = 2, path = 0 - 0} and new path is <NSIndexPath: 0x28078f140> {length = 2, path = 1 - 2}
Scheduler was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb)
因此,似乎操作系统本身已在某种程度上不使用我的控制器功能而将提醒移动了。刷新各个提醒字段时必须执行此操作,但是当我将上下文保存到核心数据时,它将再次调用我的控制器函数来执行已经完成的移动。
// Handle notifications when our app is in the background
// Note that this isn't triggered when the notification is delivered, but rather when the user interacts with the notification
//
// TO-DO: THIS DOESN'T RUN THE SHORTCUT IF THE APP WAS CLOSED WHEN THE NOTIFICATION WAS RESPONDED TO!!!
//
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
// Get the user info from the notification
let userInfo = response.notification.request.content.userInfo
// Get the specific record id that triggered this notification
let itemID = userInfo["ITEM_ID"] as? String
let itemTitle = userInfo["ITEM_TITLE"] as? String
let itemShortcut = userInfo["ITEM_SHORTCUT"] as? String
let itemURL = userInfo["ITEM_URL"] as? String
// Handle the notification action
print("RemindersViewController: Received notification. actionIdentifier:", response.actionIdentifier)
switch response.actionIdentifier {
// If user selected the Run Shortcut option or simply tapped the notification, run the associated shortcut
case "RUN_SHORTCUT", "com.apple.UNNotificationDefaultActionIdentifier":
os_log("RemindersViewController: Notification Action Received: RUN_SHORTCUT: %{public}@ for shortcut %{public}@", log: .default, type: .info, String(describing: itemTitle!), String(describing: itemShortcut!))
if (itemShortcut != nil && itemShortcut != "") {
if (itemURL != nil) {
print("RemindersViewController: Shortcut URL=", itemURL!)
let launchURL = URL(string: itemURL!)
if UIApplication.shared.canOpenURL(launchURL!) {
UIApplication.shared.open(launchURL!, options: [:], completionHandler: { (success) in
print("RemindersViewController: Notification Action: Run shortcut: Open url : \(success)")
})
} else {
let alert = UIAlertController(title: "You don't have the Shortcuts app installed", message: "Please download from the Apple App Store", preferredStyle: .alert)
let action = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
print("RemindersViewController: Notification Action: User doesn't have the Shortcuts app.")
}
} else {
let alert = UIAlertController(title: "You don't have a shortcut name filled in", message: "Please fill in a shortcut name on your reminder", preferredStyle: .alert)
let action = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
print("RemindersViewController: Notification Action: No shortcut name filled in!")
}
}
break
default:
os_log("RemindersViewController: Default action selected, which is: %{public}@. Doing nothing.", log: .default, type: .info, response.actionIdentifier)
break
}
// Regardless of what the response was, update the reminder with section and nextAlert date
// REMOVE THIS once we auto-refresh when the app enters the foreground again as that will take care of it
if (itemID != nil) {
print("Refreshing reminder from the notification.")
refreshReminder(stringURI: itemID!)
}
// Call the completion handler to close out the notification
completionHandler()
}
预期和实际结果
我认为我已经在上面进行了介绍,但是总的来说,我想了解有关如何以及何时在初始加载时刷新时间相关数据,返回到前台以及随着时间流逝的最佳实践。 [添加]我还希望当用户请求关闭我的应用程序时,通知操作仍将执行该功能(通过x-callback-url调用另一个应用程序)。