diff --git a/LocalNotificationsTutorial.xcodeproj/project.pbxproj b/LocalNotificationsTutorial.xcodeproj/project.pbxproj index c2c71f7..11355ff 100644 --- a/LocalNotificationsTutorial.xcodeproj/project.pbxproj +++ b/LocalNotificationsTutorial.xcodeproj/project.pbxproj @@ -165,7 +165,9 @@ 7469E4231A7C8E8200C68120 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0620; + LastSwiftMigration = 0720; + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0720; TargetAttributes = { 7469E42A1A7C8E8200C68120 = { CreatedOnToolsVersion = 6.2; @@ -286,6 +288,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -351,6 +354,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = LocalNotificationsTutorial/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.lumarow.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -361,6 +365,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = LocalNotificationsTutorial/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.lumarow.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -379,6 +384,7 @@ ); INFOPLIST_FILE = LocalNotificationsTutorialTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.lumarow.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LocalNotificationsTutorial.app/LocalNotificationsTutorial"; }; @@ -394,6 +400,7 @@ ); INFOPLIST_FILE = LocalNotificationsTutorialTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.lumarow.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LocalNotificationsTutorial.app/LocalNotificationsTutorial"; }; diff --git a/LocalNotificationsTutorial.xcodeproj/project.xcworkspace/xcuserdata/chinnu.xcuserdatad/UserInterfaceState.xcuserstate b/LocalNotificationsTutorial.xcodeproj/project.xcworkspace/xcuserdata/chinnu.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..27c31bb Binary files /dev/null and b/LocalNotificationsTutorial.xcodeproj/project.xcworkspace/xcuserdata/chinnu.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/LocalNotificationsTutorial.xcodeproj/xcuserdata/chinnu.xcuserdatad/xcschemes/LocalNotificationsTutorial.xcscheme b/LocalNotificationsTutorial.xcodeproj/xcuserdata/chinnu.xcuserdatad/xcschemes/LocalNotificationsTutorial.xcscheme new file mode 100644 index 0000000..61af1c6 --- /dev/null +++ b/LocalNotificationsTutorial.xcodeproj/xcuserdata/chinnu.xcuserdatad/xcschemes/LocalNotificationsTutorial.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LocalNotificationsTutorial.xcodeproj/xcuserdata/chinnu.xcuserdatad/xcschemes/xcschememanagement.plist b/LocalNotificationsTutorial.xcodeproj/xcuserdata/chinnu.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..6ea026f --- /dev/null +++ b/LocalNotificationsTutorial.xcodeproj/xcuserdata/chinnu.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + LocalNotificationsTutorial.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 7469E42A1A7C8E8200C68120 + + primary + + + 7469E43F1A7C8E8200C68120 + + primary + + + + + diff --git a/LocalNotificationsTutorial/AppDelegate.swift b/LocalNotificationsTutorial/AppDelegate.swift index 7d8b7bc..5629c79 100644 --- a/LocalNotificationsTutorial/AppDelegate.swift +++ b/LocalNotificationsTutorial/AppDelegate.swift @@ -31,19 +31,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { todoCategory.setActions([remindAction, completeAction], forContext: .Default) // UIUserNotificationActionContext.Default (4 actions max) todoCategory.setActions([completeAction, remindAction], forContext: .Minimal) // UIUserNotificationActionContext.Minimal - for when space is limited (2 actions max) - application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert | .Badge | .Sound, categories: NSSet(array: [todoCategory]))) // we're now providing a set containing our category as an argument + application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: Set([todoCategory]))) // we're now providing a set containing our category as an argument return true } func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) { - var item = TodoItem(deadline: notification.fireDate!, title: notification.userInfo!["title"] as String, UUID: notification.userInfo!["UUID"] as String!) + let item = TodoItem(deadline: notification.fireDate!, title: notification.userInfo!["title"] as! String, UUID: notification.userInfo!["UUID"] as! String!) switch (identifier!) { case "COMPLETE_TODO": TodoList().removeItem(item) case "REMIND": TodoList().scheduleReminderforItem(item) default: // switch statements must be exhaustive - this condition should never be met - println("Error: unexpected notification action identifier!") + print("Error: unexpected notification action identifier!") } completionHandler() // per developer documentation, app will terminate if we fail to call this } @@ -57,8 +57,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func applicationWillResignActive(application: UIApplication) { // fired when user quits the application - var todoItems: [TodoItem] = TodoList().allItems() // retrieve list of all to-do items - var overdueItems = todoItems.filter({ (todoItem) -> Bool in + let todoItems: [TodoItem] = TodoList().allItems() // retrieve list of all to-do items + let overdueItems = todoItems.filter({ (todoItem) -> Bool in return todoItem.deadline.compare(NSDate()) != .OrderedDescending }) UIApplication.sharedApplication().applicationIconBadgeNumber = overdueItems.count // set our badge number to number of overdue items diff --git a/LocalNotificationsTutorial/Info.plist b/LocalNotificationsTutorial/Info.plist index c112b87..40c6215 100644 --- a/LocalNotificationsTutorial/Info.plist +++ b/LocalNotificationsTutorial/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.lumarow.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/LocalNotificationsTutorial/TodoList.swift b/LocalNotificationsTutorial/TodoList.swift index 5181ab8..1627bbd 100644 --- a/LocalNotificationsTutorial/TodoList.swift +++ b/LocalNotificationsTutorial/TodoList.swift @@ -13,8 +13,8 @@ class TodoList { private let savePath = (NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString).stringByAppendingPathComponent("todo.plist") // ~/todo.plist func allItems() -> [TodoItem] { - var items: [AnyObject] = self.rawItems() - return items.map({TodoItem(deadline: $0["deadline"] as NSDate, title: $0["title"] as String, UUID: $0["UUID"] as String!)}).sorted({ + let items: [AnyObject] = self.rawItems() + return items.map({TodoItem(deadline: $0["deadline"] as! NSDate, title: $0["title"] as! String, UUID: $0["UUID"] as! String!)}).sort({ return ($0.deadline.compare($1.deadline) == .OrderedAscending) }) } @@ -22,17 +22,20 @@ class TodoList { private func rawItems() -> [AnyObject] { var items: Array = [] // default to an empty array... if (NSArray(contentsOfFile: self.savePath) != nil) { // ...because init?(contentsOfFile:) will return nil if file doesn't exist yet - items = NSArray(contentsOfFile: self.savePath)! // load stored items, if available + items = NSArray(contentsOfFile: self.savePath)! as Array // load stored items, if available } return items } func setBadgeNumbers() { - var notifications = UIApplication.sharedApplication().scheduledLocalNotifications as [UILocalNotification] // all scheduled notifications - var todoItems: [TodoItem] = self.allItems() + guard let notifications = UIApplication.sharedApplication().scheduledLocalNotifications else { + return + } + + let todoItems: [TodoItem] = self.allItems() for notification in notifications { - var overdueItems = todoItems.filter({ (todoItem) -> Bool in // array of to-do items... + let _ = todoItems.filter({ (todoItem) -> Bool in // array of to-do items... return (todoItem.deadline.compare(notification.fireDate!) != .OrderedDescending) // ...where item deadline is before or on notification fire date }) UIApplication.sharedApplication().cancelLocalNotification(notification) // cancel old notification @@ -48,7 +51,7 @@ class TodoList { (items as NSArray).writeToFile(self.savePath, atomically: true) // items casted as NSArray because writeToFile:atomically: is not available on Swift arrays // create a corresponding local notification - var notification = UILocalNotification() + let notification = UILocalNotification() notification.alertBody = "Todo Item \"\(item.title)\" Is Overdue" // text that will be displayed in the notification notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view" notification.fireDate = item.deadline // todo item due date (when notification will be fired) @@ -62,7 +65,7 @@ class TodoList { } func scheduleReminderforItem(item: TodoItem) { - var notification = UILocalNotification() // create a new reminder notification + let notification = UILocalNotification() // create a new reminder notification notification.alertBody = "Reminder: Todo Item \"\(item.title)\" Is Overdue" // text that will be displayed in the notification notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view" notification.fireDate = NSDate().dateByAddingTimeInterval(30 * 60) // 30 minutes from current time @@ -74,15 +77,18 @@ class TodoList { } func removeItem(item: TodoItem) { - for notification in UIApplication.sharedApplication().scheduledLocalNotifications as [UILocalNotification] { // loop through notifications... - if (notification.userInfo!["UUID"] as String == item.UUID) { // ...and cancel the notification that corresponds to this TodoItem instance (matched by UUID) + guard let notifications = UIApplication.sharedApplication().scheduledLocalNotifications else { + return + } + for notification in notifications { // loop through notifications... + if (notification.userInfo!["UUID"] as! String == item.UUID) { // ...and cancel the notification that corresponds to this TodoItem instance (matched by UUID) UIApplication.sharedApplication().cancelLocalNotification(notification) // there should be a maximum of one match on UUID break } } var items: [AnyObject] = self.rawItems() - items = items.filter {($0["UUID"] as String? != item.UUID)} // remove item that matches UUID + items = items.filter {($0["UUID"] as! String? != item.UUID)} // remove item that matches UUID (items as NSArray).writeToFile(self.savePath, atomically: true) // overwrite todo.plist with new array self.setBadgeNumbers() diff --git a/LocalNotificationsTutorial/TodoSchedulingViewController.swift b/LocalNotificationsTutorial/TodoSchedulingViewController.swift index bd4bede..9e362eb 100644 --- a/LocalNotificationsTutorial/TodoSchedulingViewController.swift +++ b/LocalNotificationsTutorial/TodoSchedulingViewController.swift @@ -13,14 +13,15 @@ class TodoSchedulingViewController: UIViewController { @IBOutlet weak var deadlinePicker: UIDatePicker! @IBAction func savePressed(sender: UIButton) { - if (countElements(titleField.text.stringByTrimmingCharactersInSet(.whitespaceCharacterSet())) > 0) { // only save if titlefield text contains non-whitespace characters - let todoItem = TodoItem(deadline: deadlinePicker.date, title: titleField.text, UUID: NSUUID().UUIDString) - TodoList().addItem(todoItem) // schedule a local notification to persist this item - self.navigationController?.popToRootViewControllerAnimated(true) // return to list view where the newly created item will be displayed - } else { // text field was blank or contained only whitespace - var alertController = UIAlertController(title: "Error", message: "You must give this todo item a title", preferredStyle: .Alert) + guard let text = titleField.text where text.stringByTrimmingCharactersInSet(.whitespaceCharacterSet()).characters.count > 0 else { + // text field was blank or contained only whitespace + let alertController = UIAlertController(title: "Error", message: "You must give this todo item a title", preferredStyle: .Alert) alertController.addAction(UIAlertAction(title: "Ok", style: .Cancel, handler: nil)) self.presentViewController(alertController, animated: true, completion: nil) + return } + let todoItem = TodoItem(deadline: deadlinePicker.date, title: text, UUID: NSUUID().UUIDString) + TodoList().addItem(todoItem) // schedule a local notification to persist this item + self.navigationController?.popToRootViewControllerAnimated(true) // return to list view where the newly created item will be displayed } -} \ No newline at end of file +} diff --git a/LocalNotificationsTutorial/TodoTableViewController.swift b/LocalNotificationsTutorial/TodoTableViewController.swift index 448750a..74a50bc 100644 --- a/LocalNotificationsTutorial/TodoTableViewController.swift +++ b/LocalNotificationsTutorial/TodoTableViewController.swift @@ -59,14 +59,14 @@ class TodoTableViewController: UITableViewController { return true // all cells are editable } - override func tableView(tableView: UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath) -> String! { + override func tableView(tableView: UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath) -> String? { return "Complete" // alternate text for delete button } override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { // the only editing style we'll support // Delete the row from the data source - var item = todoItems.removeAtIndex(indexPath.row) // remove TodoItem from notifications array, assign removed item to 'item' + let item = todoItems.removeAtIndex(indexPath.row) // remove TodoItem from notifications array, assign removed item to 'item' tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) TodoList().removeItem(item) // delete backing property list entry and unschedule local notification (if it still exists) self.navigationItem.rightBarButtonItem!.enabled = true // we definitely have under 64 notifications scheduled now, make sure 'add' button is enabled diff --git a/LocalNotificationsTutorialTests/Info.plist b/LocalNotificationsTutorialTests/Info.plist index 749b5c4..ba72822 100644 --- a/LocalNotificationsTutorialTests/Info.plist +++ b/LocalNotificationsTutorialTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.lumarow.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName