Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion LocalNotificationsTutorial.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@
7469E4231A7C8E8200C68120 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0620;
LastSwiftMigration = 0720;
LastSwiftUpdateCheck = 0720;
LastUpgradeCheck = 0720;
TargetAttributes = {
7469E42A1A7C8E8200C68120 = {
CreatedOnToolsVersion = 6.2;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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";
};
Expand All @@ -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";
};
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7469E42A1A7C8E8200C68120"
BuildableName = "LocalNotificationsTutorial.app"
BlueprintName = "LocalNotificationsTutorial"
ReferencedContainer = "container:LocalNotificationsTutorial.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7469E43F1A7C8E8200C68120"
BuildableName = "LocalNotificationsTutorialTests.xctest"
BlueprintName = "LocalNotificationsTutorialTests"
ReferencedContainer = "container:LocalNotificationsTutorial.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7469E42A1A7C8E8200C68120"
BuildableName = "LocalNotificationsTutorial.app"
BlueprintName = "LocalNotificationsTutorial"
ReferencedContainer = "container:LocalNotificationsTutorial.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7469E42A1A7C8E8200C68120"
BuildableName = "LocalNotificationsTutorial.app"
BlueprintName = "LocalNotificationsTutorial"
ReferencedContainer = "container:LocalNotificationsTutorial.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7469E42A1A7C8E8200C68120"
BuildableName = "LocalNotificationsTutorial.app"
BlueprintName = "LocalNotificationsTutorial"
ReferencedContainer = "container:LocalNotificationsTutorial.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>LocalNotificationsTutorial.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>7469E42A1A7C8E8200C68120</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>7469E43F1A7C8E8200C68120</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
10 changes: 5 additions & 5 deletions LocalNotificationsTutorial/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion LocalNotificationsTutorial/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.lumarow.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
Expand Down
28 changes: 17 additions & 11 deletions LocalNotificationsTutorial/TodoList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,29 @@ 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)
})
}

private func rawItems() -> [AnyObject] {
var items: Array<AnyObject> = [] // 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<AnyObject> // 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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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()
Expand Down
15 changes: 8 additions & 7 deletions LocalNotificationsTutorial/TodoSchedulingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
4 changes: 2 additions & 2 deletions LocalNotificationsTutorial/TodoTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion LocalNotificationsTutorialTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.lumarow.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
Expand Down