【總結(jié)回顧】iOS Apprentice Tutorial 2:Checklists(七)

這是***【總結(jié)回顧】iOS Apprentice Tutorial 2:Checklists ***系列的第七篇文章,也是最后一篇文章了拯爽,前幾篇文章請見(一) 赵讯、(二)(三)痛阻、(四)(五)腮敌、(六)阱当。

本篇文章總結(jié)本書的第章( Improving the user experienceExtra feature: local notifications)中的重點內(nèi)容糜工,主要是講述如何實現(xiàn)一些提升用戶體驗的功能弊添,例如記錄每個清單里未完成項目的數(shù)量和清單中項目總數(shù),每次添加新的清單后能夠給清單自動排序捌木,可給清單增加小圖標(biāo)油坝,讓界面更好看,以及適配所有的機型。從205頁到269頁(最后一頁)澈圈。

67. 優(yōu)化用戶體驗之顯示未完成數(shù)量

  func countUncheckedItems() -> Int {
    var count = 0
    for item in items where !item.checked {
      count += 1
    }
    return count
  }

此方法返回值就是此清單中沒有完成的數(shù)量彬檀。當(dāng)然此方法也可以有另外一種寫法:

func countUncheckedItems() -> Int {
    var count = 0
    for item in items  {
      if !item.checked {
            count += 1
        }
    }
    return count
  }

然后在AllListsViewController.swift中加入下列方法,保持顯示數(shù)據(jù)的同步:

  override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    tableView.reloadData()
  }

當(dāng)然接下來還可以實現(xiàn)一些小功能瞬女,比如全部完成的時候文案顯示全部完成凤覆,清單里還沒有添加項目的時候顯示沒有項目等等。

68. 優(yōu)化用戶體驗之自動排序

  func sortChecklists() {
    lists.sortInPlace({ checklist1, checklist2 in
      return checklist1.name.localizedStandardCompare(checklist2.name) == .OrderedAscending
    })
  }

這個方法的好處是拆魏,如果用戶是用中文的,排序按照拼音的順序排序慈俯,如果是英文渤刃,則按照英文從a到z的順序來。用戶使用的語言不對贴膘,相應(yīng)的順序也是不一樣的卖子。

真正的排序公式是:

checklist1.name.localizedStandardCompare(checklist2.name) == .OrderedAscending

如果你想按其他方式排序,只需要改動這一行代碼即可刑峡。

當(dāng)然洋闽,還要在下載讀取plist文件(也就是在loadChecklists()方法里)的時候,調(diào)用這一方法

69. 優(yōu)化用戶體驗之增加選擇圖片

允許用戶給每個list選擇一個圖標(biāo)突梦,實際效果如下:

當(dāng)然了诫舅,創(chuàng)建編輯list的時候,也需要增加圖標(biāo)這個選項:

將設(shè)計好的圖片放入Xcode宫患,在 Checklist.swift 文件里增加iconName變量刊懈,聲明,初始化娃闲,NSCdoing 的兩個協(xié)議方法虚汛,都要加上。然后到AllListsViewController皇帮、ListDetailViewController里把對應(yīng)的圖片顯示出來(一個是所有清單列表的界面卷哩,一個是增加或者編輯清單的界面)。在增加或編輯清單界面中點擊第二行cell属拾,跳轉(zhuǎn)到之后選擇圖標(biāo)界面将谊,Segue的Identifier是“PickIcon”,這個界面(IconPickerViewController.swift)需要先創(chuàng)建捌年。

當(dāng)用戶沒有選擇圖標(biāo)時瓢娜,實際上顯示的是一張完全透明的圖片,這樣的效果就是下圖中右側(cè)的情況:


新建選擇圖片的swift文件:IconPickerViewController.swift礼预,storyboard中拖入一個tableview Controller眠砾,關(guān)聯(lián),代碼如下。

import UIKit

protocol IconPickerViewControllerDelegate: class {
  func iconPicker(picker: IconPickerViewController, didPickIcon iconName: String)
}

class IconPickerViewController: UITableViewController {
  weak var delegate: IconPickerViewControllerDelegate?

  let icons = [
    "No Icon",
    "Appointments",
    "Birthdays",
    "Chores",
    "Drinks",
    "Folder",
    "Groceries",
    "Inbox",
    "Photos",
    "Trips" ]

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return icons.count
  }
  
  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("IconCell", forIndexPath: indexPath)
    
    let iconName = icons[indexPath.row]
    cell.textLabel!.text = iconName
    cell.imageView!.image = UIImage(named: iconName)
    
    return cell
  }
  
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if let delegate = delegate {
      let iconName = icons[indexPath.row]
      delegate.iconPicker(self, didPickIcon: iconName)
    }
  }
}


然后回到增加或者編輯清單界面的view controller里補上IconPickerViewController.swift里創(chuàng)建的delegate的有關(guān)的代碼褒颈,別忘了在類的開頭寫上:IconPickerViewControllerDelegate柒巫。

下面代碼都是因為增加了圖標(biāo)而做出了相應(yīng)修改的代碼:

  override func viewDidLoad() {
    super.viewDidLoad()
    
    if let checklist = checklistToEdit {
      title = "Edit Checklist"
      textField.text = checklist.name
      doneBarButton.enabled = true
      iconName = checklist.iconName
    }
    
    iconImageView.image = UIImage(named: iconName)
  }
  
  @IBAction func done() {
    if let checklist = checklistToEdit {
      checklist.name = textField.text!
      checklist.iconName = iconName
      delegate?.listDetailViewController(self, didFinishEditingChecklist: checklist)
    } else {
      let checklist = Checklist(name: textField.text!, iconName: iconName)
      delegate?.listDetailViewController(self, didFinishAddingChecklist: checklist)
    }
  }
  
  override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
    if indexPath.section == 1 {
      return indexPath
    } else {
      return nil
    }
  }
  
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "PickIcon" {
      let controller = segue.destinationViewController as! IconPickerViewController
      controller.delegate = self
    }
  }
  
  func iconPicker(picker: IconPickerViewController, didPickIcon iconName: String) {
    self.iconName = iconName
    iconImageView.image = UIImage(named: iconName)
    navigationController?.popViewControllerAnimated(true)
  }
}

上面的代碼有好多行,貌似是一項巨大工程谷丸,其實主要是設(shè)計的方法有些多堡掏,所以容易落下某個代碼沒有改∨偬郏總結(jié)起來泉唁,就三件事情:

  • 增加一個新的 view controller 對象。
  • 在Storyboard中設(shè)計界面(還需要做一些自動布局約束的工作)
  • 然后用 segue 和 delegate 將這個新創(chuàng)建的 view controller 對象連接到 增加編輯清單 界面揩慕。

70. 優(yōu)化用戶體驗之優(yōu)化界面+適配機型

作者在優(yōu)化外表的時候走了一個快捷方式亭畜,使用 tint color。tint color 能改變哪些地方呢迎卤?見下圖:

而且更改 tint color 的方法非常簡單拴鸵,無需在Storyboard中一個一個界面的改,只要改掉 Global Tint 即可蜗搔。如下圖:

這樣全局的 tint color 都是你想要的顏色啦多簡單劲藐,一步到位

還有一個改進的地方:啟動的時候,顯示App的一部分樟凄,這樣給用戶一種錯覺聘芜,App很快就啟動起來了。其實現(xiàn)國外的App這樣的設(shè)計比較多不同,國內(nèi)的App都不這樣做了厉膀,都會弄個廣告啊,或者圖片啊什么的二拐。不過服鹅,也了解一下如何實現(xiàn)吧:

至于書中說的適配所有機型,其實只修改了 TextField 控件百新,只要這個控件在不同的機型下顯示有些問題企软。

71. 函數(shù)式編程

近年來,函數(shù)式編程日趨流行饭望。使用函數(shù)式編程有好處仗哨,可以縮短代碼量。不過對于新手來說铅辞,可能閱讀代碼的時候會有些不習(xí)慣厌漂,不過慢慢習(xí)慣了就好了。
比如:

  func countUncheckedItems() -> Int {
    var count = 0
    for item in items where !item.checked {
      count += 1
    }
    return count
  }

用函數(shù)式編程寫出來就是

func countUncheckedItems() -> Int {
    return items.reduce(0) { cnt, item in cnt + (item.checked ? 0 : 1) }
  }

reduce()這個方法斟珊,每次看到一個item就執(zhí)行一遍{}里的代碼苇倡。cnt變量一開始的值是0,每次根據(jù)item的情況來加1或0.

72. Convenience Initializer

  convenience init(name: String) {
    self.init(name: name, iconName: "No Icon")
  }
  
  init(name: String, iconName: String) {
    self.name = name
    self.iconName = iconName
    super.init()
  }

好吧,這個地方我實際上沒太看懂旨椒,先復(fù)制一下原文晓褪,等哪天理解了,再總結(jié)一下综慎。

Instead of super.init() it now calls self.init(name, iconName). Because it farms out its work to another init method, init(name) is now known as a convenience initializer. It does the same thing as init(name, iconName) but saves you from having to type iconName: "No Icon" whenever you want to use it.

init(name, iconName) has become the so-called designated initializer for Checklist. It is the primary way to create new Checklist objects, while init(name) exists only for the convenience of lazy developers... such as you and me. :-)

73. 本地提醒功能(local notifications)

首先要了解的一點是涣仿,這是 local notifications,不是開發(fā)時常常用到的 push notifications示惊,push notifications可以讓你的 App 接收外部的事件好港,比如新聞推送某只球隊進了世界杯。

local notifications 更有點像是鬧鐘米罚,用戶在使用 App 的時候媚狰,設(shè)置了一個時間點,到點就會提醒阔拳。提醒的前提是,應(yīng)用已經(jīng)在后臺運行类嗤,或者應(yīng)用沒有啟動糊肠,如果應(yīng)用正在使用,本地通知是不會顯示的遗锣。這時候需要用其他方法另作處理货裹。

在 iOS8 之后,只有在獲得用戶的允許后才能發(fā)送 local notifications精偿,如果用戶拒絕弧圆,到點也不會出現(xiàn)提醒信息。獲取用戶許可的方法在后面會說笔咽。

(1)擴展 data model

增加了提醒意味著數(shù)據(jù)也要增加相對應(yīng)的屬性搔预,因為每個清單里的一個item里有提醒,所以要做ChecklistItem文件里增加屬性叶组。不過 UILocalNotification對象是無法存入plist文件中的拯田,需要找替代方案,比如數(shù)字標(biāo)識符:numeric primary甩十。

  var dueDate = NSDate()
  var shouldRemind = false
  var itemID: Int

接著在 NSCoding 的2個協(xié)議方法增加相應(yīng)的代碼船庇。在Objective-C語言中,Int侣监,F(xiàn)loat和Bool是原始類型鸭轮,所以會看到shouldRemindcheckeditemID的方法有別與非原始類型的對象橄霉。

  required init?(coder aDecoder: NSCoder) {
    text = aDecoder.decodeObjectForKey("Text") as! String
    checked = aDecoder.decodeBoolForKey("Checked")
    dueDate = aDecoder.decodeObjectForKey("DueDate") as! NSDate
    shouldRemind = aDecoder.decodeBoolForKey("ShouldRemind")
    itemID = aDecoder.decodeIntegerForKey("ItemID")
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(text, forKey: "Text")
    aCoder.encodeBool(checked, forKey: "Checked")
    aCoder.encodeObject(dueDate, forKey: "DueDate")
    aCoder.encodeBool(shouldRemind, forKey: "ShouldRemind")
    aCoder.encodeInteger(itemID, forKey: "ItemID")
  }

不要忘了更新初始化方法窃爷,注意代碼中DataModel是大寫開頭的。

  override init() {

    //注意這里是大寫開頭的DataModel
    itemID = DataModel.nextChecklistItemID()
    super.init()
  }

在初始化方法里寫這么一行代碼是什么意思呢?不管什么時候只要App創(chuàng)建了一個新的 ChecklistItem 對象后吞鸭,都讓 DataModel 對象生成一個新的item ID寺董。

之所以可以大寫開頭,是因為我們要在DataModel類中創(chuàng)建一個類方法(方法前面有個class刻剥,詳細(xì)內(nèi)容見#74):

  class func nextChecklistItemID() -> Int {
    let userDefaults = NSUserDefaults.standardUserDefaults()
    //讀取出來值
    let itemID = userDefaults.integerForKey("ChecklistItemID")
    //寫入新值
    userDefaults.setInteger(itemID + 1, forKey: "ChecklistItemID")
    //保存同步
    userDefaults.synchronize()
    return itemID
  }

NSUserDefaults里面沒有ChecklistItemID這個鍵啊遮咖,是的,沒有造虏,所以需要加入:

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

    NSUserDefaults.standardUserDefaults().registerDefaults(dictionary)
  }
  

(2)搭建界面

先上效果圖:


提醒時間功能

這個直接去控件庫里拖動相對應(yīng)的控件即可御吞,沒啥可說的,別忘了自動布局的約束漓藕。而下圖這個就有些問題了陶珠,作者提供的方法非常新穎,我使用之后發(fā)現(xiàn)有個不好的地方享钞,就是擴展性不夠好揍诽,沒法擴展到多個時間選擇器上,比如這里只要提醒時間栗竖,要是我再加入開始時間和結(jié)束時間暑脆,不好實現(xiàn)同樣的效果,擴展性不夠狐肢。


時間選擇器

步驟如下:
步驟一添吗,把cell拖到下圖中箭頭所在的位置:

拖拽之后會出現(xiàn)這樣的效果:


cell高度設(shè)為217,拖入一個DatePicker控件份名,效果如下:

(3)對應(yīng)的Controller里編寫代碼:

還要創(chuàng)建Outlet連接:

  @IBOutlet weak var shouldRemindSwitch: UISwitch!
  @IBOutlet weak var dueDateLabel: UILabel!
  @IBOutlet weak var datePickerCell: UITableViewCell!
  @IBOutlet weak var datePicker: UIDatePicker!

聲明新的變量:

  var dueDate = NSDate()
  var datePickerVisible = false

更新viewDidLoad()中的方法:編輯狀態(tài)下2個屬性要加入+更新提醒日期的Label內(nèi)容(updateDueLabel()):

  override func viewDidLoad() {
    super.viewDidLoad()

    if let item = itemToEdit {
      title = "Edit Item"
      textField.text = item.text
      doneBarButton.enabled = true
      //新增加的兩行代碼:
      shouldRemindSwitch.on = item.shouldRemind
      dueDate = item.dueDate
    }
    //需要創(chuàng)建的方法:
    updateDueDateLabel()
  }

updateDueLabel()方法代碼:

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

NSDateFormatter可以改變時間顯示的格式碟联。
@IBAction func done()方法里也要增加新增的2個屬性。

下面的代碼都是有關(guān)如何顯示時間選擇器cell的:
看完這些方法僵腺,你就會理解我為什么說作者提供的這個方法非常麻煩了鲤孵。

  1. cellForRowAtIndexPath
  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)
    }
  }

2)numberOfRowsInSection

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

3)heightForRowAtIndexPath

  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)
    }
  }

4)didSelectRowAtIndexPath

  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    textField.resignFirstResponder()

    if indexPath.section == 1 && indexPath.row == 1 {
      if !datePickerVisible {
        showDatePicker()
      } else {
        hideDatePicker()
      }
    }
  }

5) willSelectRowAtIndexPath

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

6)indentationLevelForRowAtIndexPath

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

到此結(jié)束了。但這樣只能讓cell一直出現(xiàn)辰如,時間選擇器會一直在這里出現(xiàn)的裤纹,這可不是我們想要的效果。我們需要點擊一下顯示丧没,再點擊一下隱藏鹰椒,那么好吧,繼續(xù)寫代碼吧~

下面這些代碼有關(guān)顯示隱藏時間選擇器

1)顯示時間選擇器

  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)
  }

2)隱藏時間選擇器

  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()
    }
  }

優(yōu)化用戶體驗
啟動鍵盤后要隱藏日期選擇器:

  func textFieldDidBeginEditing(textField: UITextField) {
    hideDatePicker()
  }

日期選擇器中的時間每次發(fā)生改變后都更新Label內(nèi)容:

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

(4)如何以及什么時候安排通知(schedule the notifications)

好吧呕童,終于開始寫有關(guān)通知的代碼了漆际,上面那么多東西,都是鋪墊鋪墊鋪墊夺饲。奸汇。施符。
先創(chuàng)建一個方法,用來創(chuàng)建通知:

func scheduleNotification() {

}

這個方法寫入ChecklistItem.swift這個文件里擂找。

關(guān)于通知的時間戳吝,要注意,過去的時間點是無法提醒的贯涎,但是用戶設(shè)置了過去的時間怎么辦呢听哭?

dueDate.compare(NSDate()) = .OrderedAscending

.OrderedAscending表示 dueDate 的時間在前,現(xiàn)在時間NSDate()在后塘雳,也就是說陆盘,提醒時間發(fā)生在過去。只有將時間設(shè)置到將來才可以提醒败明,所以設(shè)置提醒的條件為:!=隘马,當(dāng)然了,還要用戶開啟提醒功能:

if shouldRemind && dueDate.compare(NSDate()) != .OrderedAscending {
}

補充一下妻顶,NSComparisonResult 的結(jié)果酸员,也就是 A.compare(B) 結(jié)果有三種:

  • .OrderedAscending,也是我們正在使用的讳嘱,A發(fā)生在B之前(A在過去沸呐,B在將來)。
  • .OrderedSame呢燥,表示兩個時間一致,A和B時間是同一個時間點寓娩。
  • .OrderedDescending叛氨,表示A發(fā)生在B之后,也就是先經(jīng)過B時間點棘伴,然后才是A時間點寞埠。

當(dāng)然了,每創(chuàng)建一個新的 Item 或者編輯一個 Item 的時候焊夸,都要檢查一下是否要創(chuàng)建通知仁连,所以:

  @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()
      item.text = textField.text!
      item.checked = false
      item.shouldRemind = shouldRemindSwitch.on
      item.dueDate = dueDate
      item.scheduleNotification()
      delegate?.itemDetailViewController(self, didFinishAddingItem: item)
    }
  }

現(xiàn)在可以創(chuàng)建通知了,創(chuàng)建本地通知有7行非常關(guān)鍵的代碼:

  • 創(chuàng)建本地通知
  • 創(chuàng)建提醒時間
  • 創(chuàng)建時間提醒的時區(qū)
  • 創(chuàng)建通知顯示的文案或內(nèi)容
  • 創(chuàng)建通知使用的聲音
  • 通知提醒的是哪個內(nèi)容(哪個item)
  • 將創(chuàng)建好的本地通知排入時間表

用代碼表示也就是:

      let localNotification = UILocalNotification()
      localNotification.fireDate = dueDate
      localNotification.timeZone = NSTimeZone.defaultTimeZone()
      localNotification.alertBody = text
      localNotification.soundName = UILocalNotificationDefaultSoundName
      localNotification.userInfo = ["ItemID": itemID]
      UIApplication.sharedApplication().scheduleLocalNotification(localNotification)

注意第一行代碼我們實際上創(chuàng)建的是一個UILocalNotification對象阱穗,要使用這個對象饭冬,需要先 import UIKit。

當(dāng)然我們要獲取用戶的許可才能設(shè)置通知揪阶,處理方法就是昌抠,當(dāng)用戶將switch按鈕調(diào)整到on的時候,向用戶獲取許可鲁僚,Ctrl拖拽創(chuàng)建Action連接:

  @IBAction func shouldRemindToggled(sender: UISwitch) {
    //隱藏鍵盤
    textField.resignFirstResponder()
    
    if sender.on {
      let notificationSettings = UIUserNotificationSettings(forTypes: [.Alert , .Sound], categories: nil)
      UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
    }
  }

當(dāng)我們設(shè)置時間的時炊苫,會記錄到秒裁厅,比如提醒時間是10:16:54,實際上我們在設(shè)置時間的時候侨艾,時間選擇器里只顯示到了分执虹,但是提醒的時候,卻會連秒也考慮到里面唠梨。如果想提高用戶體驗袋励,去掉秒,直接在0秒的時候就提醒姻成,那么插龄,這是另外一個話題了。如何解決呢科展?作者也沒說均牢。

通知可能發(fā)生變化的五種情形:

  • 當(dāng)用戶新建一個 ChecklistItem 對象后,將 ShouldRemind 轉(zhuǎn)換按鈕調(diào)整到了開上才睹,需要安排一個新的通知(相當(dāng)于新建通知)徘跪。
  • 當(dāng)用戶改變提醒日期后,舊的通知需要取消琅攘,然后更換上新的通知日期垮庐。
  • 當(dāng)用戶將 ShouldRemind 轉(zhuǎn)換按鈕調(diào)整到關(guān)的狀態(tài)后,當(dāng)前的通知需要取消掉坞琴。
  • 當(dāng)用戶刪除當(dāng)前的 ChecklistItem 后哨查,該Item下的通知需要被取消。
  • 當(dāng)用戶刪除一整個 Checklist 后剧辐,里面所有的Item下的通知都需要被取消寒亥。

我們上面的各種步驟已經(jīng)完成了新建通知,那么編輯和刪除如何進行呢荧关?

先說編輯溉奕,當(dāng)我們編輯一個item的時候,先看是否存在一個通知忍啤,如果存在加勤,取消通知即可,然后新建同波。每次編輯鳄梅,都相當(dāng)于刪除舊的,重新創(chuàng)建新的通知未檩,如果用戶在編輯的時候沒有修改通知卫枝,頂多就是新建了一個和之前一模一樣的通知而已。

  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
  }

判斷此item是否有通知讹挎。然后放入創(chuàng)建通知的方法里:

  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 = text
      localNotification.soundName = UILocalNotificationDefaultSoundName
      localNotification.userInfo = ["ItemID": itemID]
      
      UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
      
     
    }
  }

最后剩下的就是刪除了校赤,即通知可能發(fā)生變化的五種情形的最后兩種情緒吆玖,用一個方法即可解決:

  deinit {
    if let notification = notificationForThisItem() {
 
      UIApplication.sharedApplication().cancelLocalNotification(notification)
    }
  }

當(dāng)年刪除單個的ChecklistItem或者一個整個Checklist時,都會調(diào)用上面方法马篮。

74. Class method vs. instance method(類方法和實例方法)

class func nextChecklistItemID()

class 這個關(guān)鍵詞意味著你可以直接調(diào)用該方法沾乘,不用創(chuàng)建一個 DataMode 的對象的引用。

之前用的都是 instance method浑测,只使用于類中某些實例翅阵。那什么時候用類方法什么時候用引用方法呢?哪個方法用的代碼更少就用哪個方法啦迁央,畢竟在這個App里掷匠,就這么一個地方用到了DataModel,如果以后你需要更新App岖圈,增加更多功能讹语,很有可能就需要使用創(chuàng)建引用類型,然后使用引用方法蜂科。

更多詳細(xì)的介紹會在下一本書中講解顽决,所以留著點疑問去看下一本書吧~

結(jié)束了

終于總結(jié)完畢了,終于看完了导匣,本來以為一個周就能搞定才菠,結(jié)果來來回回拖了這么久才完成,我也是小瞧了這本書贡定,本來以為是100米短跑赋访,跑完5000米之后才發(fā)現(xiàn),前面還有一個馬拉松缓待。蚓耽。。這就是我總結(jié)這本書的感受命斧,再也不敢隨意估算時間了,以后自己估計時間了之后再乘以3嘱兼,如果完全沒做過国葬,乘以7,就是實際時間了芹壕。

照著書敲三遍代碼汇四,和總結(jié)一遍知識點,完全不是一個量級上的工作踢涌,雖然中間數(shù)次想放棄通孽,好歹也走到了今天。有了這本書的知識睁壁,我完全可以開發(fā)一個無網(wǎng)絡(luò)交互的本地App了背苦,最起碼數(shù)據(jù)持久化可以搞定了互捌。要是哪天App上線了,我要在這里更新行剂,多說幾句~~

能看完的都是秕噪,我很佩服,總之厚宰,看到錯別字或者錯誤的知識點腌巾,還請指正~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市铲觉,隨后出現(xiàn)的幾起案子澈蝙,更是在濱河造成了極大的恐慌,老刑警劉巖撵幽,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灯荧,死亡現(xiàn)場離奇詭異,居然都是意外死亡并齐,警方通過查閱死者的電腦和手機漏麦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來况褪,“玉大人撕贞,你說我怎么就攤上這事〔舛猓” “怎么了捏膨?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長食侮。 經(jīng)常有香客問我号涯,道長,這世上最難降的妖魔是什么锯七? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任链快,我火速辦了婚禮,結(jié)果婚禮上眉尸,老公的妹妹穿的比我還像新娘域蜗。我一直安慰自己,他們只是感情好噪猾,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布霉祸。 她就那樣靜靜地躺著,像睡著了一般袱蜡。 火紅的嫁衣襯著肌膚如雪丝蹭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天坪蚁,我揣著相機與錄音奔穿,去河邊找鬼镜沽。 笑死,一個胖子當(dāng)著我的面吹牛巫橄,可吹牛的內(nèi)容都是我干的淘邻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼湘换,長吁一口氣:“原來是場噩夢啊……” “哼宾舅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起彩倚,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤筹我,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后帆离,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔬蕊,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年哥谷,在試婚紗的時候發(fā)現(xiàn)自己被綠了岸夯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡们妥,死狀恐怖猜扮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情监婶,我是刑警寧澤旅赢,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站惑惶,受9級特大地震影響煮盼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜带污,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一僵控、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鱼冀,春花似錦报破、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽理卑。三九已至翘紊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藐唠,已是汗流浹背帆疟。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工鹉究, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踪宠。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓自赔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柳琢。 傳聞我的和親對象是個殘疾皇子绍妨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容