Skip to content

Commit

Permalink
Notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
mriddle committed May 3, 2016
1 parent c6dc1d9 commit db4213a
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 4 deletions.
1 change: 0 additions & 1 deletion Checklists/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
dataModel.saveChecklists()
}


}

84 changes: 84 additions & 0 deletions Checklists/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,61 @@
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="i4h-GR-9g0">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="1Iu-zV-IAm">
<rect key="frame" x="0.0" y="179" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1Iu-zV-IAm" id="UYa-MV-TQv">
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="9Wn-xs-wbf">
<rect key="frame" x="543" y="6" width="51" height="31"/>
<connections>
<action selector="shouldRemindToggled:" destination="aBC-cr-znO" eventType="valueChanged" id="8n9-yO-KVh"/>
</connections>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Remind Me" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4GF-U1-Gk3">
<rect key="frame" x="15" y="11" width="86" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="9Wn-xs-wbf" secondAttribute="trailing" constant="8" id="3NT-xk-A2O"/>
<constraint firstAttribute="trailing" secondItem="9Wn-xs-wbf" secondAttribute="trailing" constant="8" id="8Ym-r4-NCh"/>
<constraint firstItem="9Wn-xs-wbf" firstAttribute="top" secondItem="UYa-MV-TQv" secondAttribute="top" constant="6" id="DMP-OL-JuQ"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" textLabel="k5K-uQ-3On" detailTextLabel="aUE-Ji-twU" style="IBUITableViewCellStyleValue1" id="qql-qf-fZL">
<rect key="frame" x="0.0" y="223" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="qql-qf-fZL" id="3ag-An-lzE">
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Due Date" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="k5K-uQ-3On">
<rect key="frame" x="15" y="12" width="68" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="aUE-Ji-twU">
<rect key="frame" x="543" y="12" width="42" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="aBC-cr-znO" id="cxi-IS-7DA"/>
Expand All @@ -150,11 +205,40 @@
</barButtonItem>
</navigationItem>
<connections>
<outlet property="datePicker" destination="Fa2-JO-Y5n" id="zf6-rx-P55"/>
<outlet property="datePickerCell" destination="Jlb-s4-fN3" id="UIh-4y-TGT"/>
<outlet property="doneBarButton" destination="wpi-Ha-khN" id="JaB-nU-0cL"/>
<outlet property="dueDateLabel" destination="aUE-Ji-twU" id="WJD-Hb-hgf"/>
<outlet property="shouldRemindSwitch" destination="9Wn-xs-wbf" id="gvC-lg-KHU"/>
<outlet property="textField" destination="aWI-1R-gxa" id="T92-Ed-nt0"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="AAL-Av-N4z" userLabel="First Responder" sceneMemberID="firstResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="Jlb-s4-fN3">
<rect key="frame" x="0.0" y="0.0" width="320" height="217"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Jlb-s4-fN3" id="AgQ-Iv-P1t">
<rect key="frame" x="0.0" y="0.0" width="320" height="216"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<datePicker contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" datePickerMode="dateAndTime" minuteInterval="1" translatesAutoresizingMaskIntoConstraints="NO" id="Fa2-JO-Y5n">
<rect key="frame" x="0.0" y="0.0" width="320" height="216"/>
<date key="date" timeIntervalSinceReferenceDate="483947214.86077601">
<!--2016-05-03 05:46:54 +0000-->
</date>
<connections>
<action selector="dateChanged:" destination="aBC-cr-znO" eventType="valueChanged" id="4bJ-gu-YiD"/>
</connections>
</datePicker>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Fa2-JO-Y5n" secondAttribute="bottom" id="DHA-v2-DFK"/>
<constraint firstItem="Fa2-JO-Y5n" firstAttribute="leading" secondItem="AgQ-Iv-P1t" secondAttribute="leading" id="eWy-Ht-yhb"/>
<constraint firstItem="Fa2-JO-Y5n" firstAttribute="top" secondItem="AgQ-Iv-P1t" secondAttribute="top" id="iMw-rW-2ky"/>
<constraint firstAttribute="trailing" secondItem="Fa2-JO-Y5n" secondAttribute="trailing" id="oI4-rX-xiQ"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
</objects>
<point key="canvasLocation" x="861" y="1634"/>
</scene>
Expand Down
45 changes: 44 additions & 1 deletion Checklists/ChecklistItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,28 @@
//

import Foundation
import UIKit

class ChecklistItem: NSObject, NSCoding {
var text: String
var checked: Bool
var itemID: Int
var dueDate = NSDate()
var shouldRemind = false

required init?(coder aDecoder: NSCoder) {
text = aDecoder.decodeObjectForKey("Text") as! String
checked = aDecoder.decodeBoolForKey("Checked")

itemID = aDecoder.decodeIntegerForKey("ItemID")
dueDate = aDecoder.decodeObjectForKey("DueDate") as! NSDate
shouldRemind = aDecoder.decodeBoolForKey("ShouldRemind")
super.init()
}

init(text: String, checked: Bool) {
self.text = text
self.checked = checked
itemID = DataModel.nextChecklistItemID()
}

func toggleChecked() {
Expand All @@ -31,5 +38,41 @@ class ChecklistItem: NSObject, NSCoding {
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(text, forKey: "Text")
aCoder.encodeBool(checked, forKey: "Checked")
aCoder.encodeInteger(itemID, forKey: "ItemID")
aCoder.encodeObject(dueDate, forKey: "DueDate")
aCoder.encodeBool(shouldRemind, forKey: "ShouldRemind")
}

func scheduleNotification() {
let existingNotification = notificationForThisItem()
if let notification = existingNotification {
UIApplication.sharedApplication().cancelLocalNotification(notification)
}
if shouldRemind && dueDate.compare(NSDate()) != .OrderedAscending {
let localNotification = UILocalNotification()
localNotification.fireDate = dueDate
localNotification.timeZone = NSTimeZone.defaultTimeZone()
localNotification.alertBody = "I am a local notification!"
localNotification.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}
}

func notificationForThisItem() -> UILocalNotification? {
let allNotifications = UIApplication.sharedApplication().scheduledLocalNotifications!

for notification in allNotifications {
if let number = notification.userInfo?["ItemID"] as? Int where number == itemID {
return notification
}
}
return nil
}

deinit {
if let notification = notificationForThisItem() {
print("Remove existing notification \(notification)")
UIApplication.sharedApplication().cancelLocalNotification(notification)
}
}
}
11 changes: 10 additions & 1 deletion Checklists/DataModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class DataModel {

func registerDefaults() {
let dictionary = ["ChecklistIndex": -1,
"FirstTime": true]
"FirstTime": true,
"ChecklistItemID": 0]

NSUserDefaults.standardUserDefaults().registerDefaults(dictionary)
}
Expand Down Expand Up @@ -72,4 +73,12 @@ class DataModel {
func sortChecklists() {
lists.sortInPlace({ checklist1, checklist2 in return checklist1.name.localizedCompare(checklist2.name) == .OrderedAscending })
}

class func nextChecklistItemID() -> Int {
let userDefaults = NSUserDefaults.standardUserDefaults()
let itemID = userDefaults.integerForKey("ChecklistItemID")
userDefaults.setInteger(itemID + 1, forKey: "ChecklistItemID")
userDefaults.synchronize()
return itemID
}
}
121 changes: 120 additions & 1 deletion Checklists/ItemDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ class ItemDetailViewController: UITableViewController, UITextFieldDelegate {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var doneBarButton: UIBarButtonItem!

@IBOutlet weak var dueDateLabel: UILabel!
@IBOutlet weak var shouldRemindSwitch: UISwitch!

@IBOutlet weak var datePickerCell: UITableViewCell!
@IBOutlet weak var datePicker: UIDatePicker!


var itemToEdit: ChecklistItem?
var dueDate = NSDate()
var datePickerVisible = false

weak var delegate: ItemDetailViewControllerDelegate?

Expand All @@ -30,16 +39,26 @@ class ItemDetailViewController: UITableViewController, UITextFieldDelegate {
title = "Edit Item"
textField.text = item.text
doneBarButton.enabled = true
shouldRemindSwitch.on = item.shouldRemind
dueDate = item.dueDate
}

updateDueDateLabel()
}

@IBAction func done() {

if let item = itemToEdit {
item.text = textField.text!
item.shouldRemind = shouldRemindSwitch.on
item.dueDate = dueDate
item.scheduleNotification()
delegate?.itemDetailViewController(self, didFinishEditingItem: item)
} else {
let item = ChecklistItem(text: textField.text!, checked: false)
item.shouldRemind = shouldRemindSwitch.on
item.dueDate = dueDate
item.scheduleNotification()
delegate?.itemDetailViewController(self, didFinishAddingItem: item)
}

Expand All @@ -49,13 +68,71 @@ class ItemDetailViewController: UITableViewController, UITextFieldDelegate {
delegate?.addItemViewControllerDidCancel(self)
}

@IBAction func dateChanged(datePicker: UIDatePicker) {
dueDate = datePicker.date
updateDueDateLabel()
}

@IBAction func shouldRemindToggled(switchControl: UISwitch) {
textField.resignFirstResponder()

if switchControl.on {
let notificationSettings = UIUserNotificationSettings(forTypes: [.Alert, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
}
}

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
textField.becomeFirstResponder()
}

override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
return nil
if indexPath.section == 1 && indexPath.row == 1 {
return indexPath
} else {
return nil
}
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 1 && indexPath.row == 2 {
return datePickerCell
} else {
return super.tableView(tableView, cellForRowAtIndexPath: indexPath)
}
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 1 && datePickerVisible {
return 3
} else {
return super.tableView(tableView, numberOfRowsInSection: section)
}
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.section == 1 && indexPath.row == 2 {
return 217
}else {
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
textField.resignFirstResponder()
if indexPath.section == 1 && indexPath.row == 1 {
datePickerVisible ? hideDatePicker() : showDatePicker()
}
}

override func tableView(tableView: UITableView, indentationLevelForRowAtIndexPath indexPath: NSIndexPath) -> Int {
var newIndexPath = indexPath
if indexPath.section == 1 && indexPath.row == 2 {
newIndexPath = NSIndexPath(forRow: 0, inSection: indexPath.section)
}
return super.tableView(tableView, indentationLevelForRowAtIndexPath: newIndexPath)
}

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
Expand All @@ -67,4 +144,46 @@ class ItemDetailViewController: UITableViewController, UITextFieldDelegate {

return true
}

func updateDueDateLabel() {
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
formatter.timeStyle = .ShortStyle
dueDateLabel.text = formatter.stringFromDate(dueDate)
}

func showDatePicker() {
datePickerVisible = true
let indexPathDateRow = NSIndexPath(forRow: 1, inSection: 1)
let indexPathDatePicker = NSIndexPath(forRow: 2, inSection: 1)

if let dateCell = tableView.cellForRowAtIndexPath(indexPathDateRow) {
dateCell.detailTextLabel!.textColor = dateCell.detailTextLabel!.tintColor
}

tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([indexPathDatePicker], withRowAnimation: .Fade)
tableView.reloadRowsAtIndexPaths([indexPathDateRow], withRowAnimation: .None)
tableView.endUpdates()

datePicker.setDate(dueDate, animated: false)
}

func hideDatePicker() {
if datePickerVisible {
datePickerVisible = false

let indexPathDateRow = NSIndexPath(forRow: 1, inSection: 1)
let IndexPathDatePicker = NSIndexPath(forRow: 2, inSection: 1)

if let cell = tableView.cellForRowAtIndexPath(indexPathDateRow) {
cell.detailTextLabel!.textColor = UIColor(white: 0, alpha: 0.5)
}

tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPathDateRow], withRowAnimation: .None)
tableView.deleteRowsAtIndexPaths([IndexPathDatePicker], withRowAnimation: .Fade)
tableView.endUpdates()
}
}
}

0 comments on commit db4213a

Please sign in to comment.