【总结回顾】iOS Apprentice Tutorial 2:Checklists(六)

这是***【总结回顾】iOS Apprentice Tutorial 2:Checklists ***系列的第六篇文章,前几篇文章请见(一)(二)(三)(四)(五)

本篇文章总结本书的第九、十章( Putting to-do items into the checklistsUsing NSUserDefaults to rememberstuff)中的重点内容,主要优化了存储方法,以及如何记录用户最后使用的界面的位置等等,从173页到204页。

数据结构从下图中看,可能会更容易理解:

60. 存储方法优化

之前用户的每一次点击之后都要保存,这样写起来不仅繁琐,而且有时候会忘记某个点击效果,就没法保存了。

所以我们这次使用的方法是,在App被 terminate 之前保存数据,在 AppDelegation 文件里,有两个方法就能够涵盖所有需要保存的情况:

func applicationDidEnterBackground(application: UIApplication)

func applicationWillTerminate(application: UIApplication)

实际上代码如下

  ...  
  
  let dataModel = DataModel()

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    
    let navigationController = window!.rootViewController as! UINavigationController
    let controller = navigationController.viewControllers[0] as! AllListsViewController
    controller.dataModel = dataModel
    
    return true
  }
  
  ...

  func applicationDidEnterBackground(application: UIApplication) {
    saveData()
  }

  func applicationWillTerminate(application: UIApplication) {
    saveData()
  }

  func saveData() {
    dataModel.saveChecklists()
  }


注意代码中使用的是let controller = navigationController.viewControllers[0] as! AllListsViewController,不是之前出现的topViewController,因为后者只是当前正在展示在界面上的 view controller。

而 DataModel 的代码为:

import Foundation

class DataModel {
  //声明变量并赋值
  var lists = [Checklist]()
    
    
  //初始化方法。(注意初始化方法里没有super.init(),因为DataModel没有父类superclass)
  init() {
    loadChecklists()
  }
  
  //找路径
  func documentsDirectory() -> String {
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    return paths[0]
  }
  
  //新建文件
  func dataFilePath() -> String {
    return (documentsDirectory() as NSString).stringByAppendingPathComponent("Checklists.plist")
  }
  
  //存数据
  func saveChecklists() {
    let data = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWithMutableData: data)
    archiver.encodeObject(lists, forKey: "Checklists")
    archiver.finishEncoding()
    data.writeToFile(dataFilePath(), atomically: true)
  }
  
  //取数据
  func loadChecklists() {
    let path = dataFilePath()
    if NSFileManager.defaultManager().fileExistsAtPath(path) {
      if let data = NSData(contentsOfFile: path) {
        let unarchiver = NSKeyedUnarchiver(forReadingWithData: data)
        lists = unarchiver.decodeObjectForKey("Checklists") as! [Checklist]
        unarchiver.finishDecoding()
      }
    }
  }
}

Checklist 的代码如下:

import UIKit

class Checklist: NSObject, NSCoding {
  var name = ""
  var items = [ChecklistItem]()
  
  init(name: String) {
    self.name = name
    super.init()
  }

  required init?(coder aDecoder: NSCoder) {
    name = aDecoder.decodeObjectForKey("Name") as! String
    items = aDecoder.decodeObjectForKey("Items") as! [ChecklistItem]
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(name, forKey: "Name")
    aCoder.encodeObject(items, forKey: "Items")
  }
}

ChecklistItem 的代码如下:

import Foundation

class ChecklistItem: NSObject, NSCoding {
  var text = ""
  var checked = false

  override init() {
    super.init()
  }

  required init?(coder aDecoder: NSCoder) {
    text = aDecoder.decodeObjectForKey("Text") as! String
    checked = aDecoder.decodeBoolForKey("Checked")
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(text, forKey: "Text")
    aCoder.encodeBool(checked, forKey: "Checked")
  }
  
  func toggleChecked() {
    checked = !checked
  }
}

61. var 和 let 的区别

Swift 中,value types(值类型)reference types(引用类型)有显著的区别,在let使用方面也有一些不同。

对值类型而言,var后面是值,可以变。let后面也是值,不能变。

我们在 class 类定义的 objects,都是引用类型。对引用类型而言,引用类型的变量或者常量实际上没有包含一个真正的对象,只包含了这个对象的引用。如果用let声明引用类型常量,那么此常量不能再指向其他的对象了,但是此常量对象本身是可以变化的。

如果还没搞定什么时候用let什么时候用var,作者给出了一个小方法:全都用let,然后编译器告诉你某个应该用var声明,再去改成var。

如果是 optional,必须用 var
如果是 optional,必须用 var
如果是 optional,必须用 var

63. NSUserDefaults记录用户看到了哪里

需求如下:记录用户看到了哪个页面,下次打开App的时候,还在上次离开的地方。

我们使用 NSUserDefaults 来记录这个数据。NSUserDefaults 有些像是 词典Dictionary,每个键对应一个值。NSUserDefaults 只能记录小量的数据,大的数据是不能使用NSUserDefaults的。

为了实现需求,我们需要做三件事情:

(1)

当segue从主界面到清单详细内容界面时,记录下所选清单的index,存到NSUserDefaults里。

写入值的方法为:

NSUserDefaults.standardUserDefaults().setInter(indexPath.row, forKeys: "ChecklistIndex")

(2)

当用户点击返回按钮返回到主界面时,需要去掉存到NSUserDefaults里的index值。可以赋值-1,表示在主界面。(注意:NSUserDefaults里不能使用optional)

首先要知道用户点击了返回按钮,然后才能赋值。如何才能知道用户点击了返回按钮呢?方法如下:

让主界面view controller 成为 navigation controller 的delegate,这样主界面就能够监听用户点击返回按钮事件了。类的开头写上delegate的名字UINavigationControllerDelegate,在viewDidAppear()写上navigationController?.delegate = self

然后可以实现delegate中的方法了:

  func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
    if viewController === self {
      NSUserDefaults.standardUserDefaults().setInter(indexPath.row, forKeys: "ChecklistIndex")
    }
  }

任何时候只要navigation controller要滑到新的页面时,这个方法都会被调用。
调用条件很重要,附上原文:

This method is called whenever the navigation controller will slide to a new screen.

(3)

如果App在启动后NSUserDefaults的值不是-1,需要自动跳转到NSUserDefaults里记录的页面。

首先要从NSUserDefaults取出值,才能知道跳转到哪个界面,读取值的方法为:

let index = NSUserDefaults.standardUserDefaults().integerForKey("ChecklistIndex")

跳转界面的方法为:

  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    navigationController?.delegate = self
    
    let index = dataModel.indexOfSelectedChecklist
    if index >= 0 && index < dataModel.lists.count {
      let checklist = dataModel.lists[index]
      performSegueWithIdentifier("ShowChecklist", sender: checklist)
    }
  }

每次在view controller可见后,UIKit都会自动调用 viewDidAppear 方法。

为什么使用 viewDidAppear 方法而不是 viewDidLoad ?这里非常

64. Equal 或者 identical 的异同

===三个等号:检查三个等号两边的对象是否为同一个对象
==两个等号:在检查两个等号两边的变量是否有相同的值

65. Defensive programming 防御性/防错性程序设计

可是在用户第一次运行App的时候,NSUserDefaults里还没有值,这时候运行App,就会报错。因为NSUserDefaults的取值方法integerForKey()如果没有找到key对应的值,就会返回数字零,可是在这里,0是index的值,index 0 那行用户还没有创建呢。自然就崩溃了。

所以,我们要给NSUserDefaults设置一个初始值,在DataModel里写就好:

  func registerDefaults() {
    let dictionary = [ "ChecklistIndex": -1]

    NSUserDefaults.standardUserDefaults().registerDefaults(dictionary)
  }

然后在DataModel初始化方法里调用上面这方法就可以了。其实NSUserDefaults的读取也可以放到DataModel里,毕竟是MVC里的M嘛。

读取NSUserDefaults可以用Swift最新的一个语法:

  var indexOfSelectedChecklist: Int {
    get {
      return NSUserDefaults.standardUserDefaults().integerForKey("ChecklistIndex")
    }
    set {
      NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "ChecklistIndex")
      NSUserDefaults.standardUserDefaults().synchronize()
    }
  }

NSUserDefaults.standardUserDefaults().synchronize()的作用是:每当indexOfSelectedChecklist的值发生改变的时候,强制NSUserDefaults保存数值,保证NSUserDefaults和.plist文件处于同步状态。

当计算机尝试读取indexOfSelectedChecklist的值的时候,get闭包里的代码会被执行,当尝试给indexOfSelectedChecklist赋予新值的时候,set闭包里的代码就会执行。这样#63中的方法可以更新如下:

  let index = dataModel.indexOfSelectedChecklist
  dataModel.indexOfSelectedChecklist = indexPath.row
  dataModel.indexOfSelectedChecklist = -1

第二个Defensive programming例子,看if后面跟的条件:

  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    navigationController?.delegate = self
    
    let index = dataModel.indexOfSelectedChecklist
  
    if index >= 0 && index < dataModel.lists.count {
      let checklist = dataModel.lists[index]
      performSegueWithIdentifier("ShowChecklist", sender: checklist)
    }
  }
  

66. 创建默认的清单数据

如果App下载后第一次打开就有默认数据,有一个名为list的清单,同时App还会自动跳转到这个名为list的清单里面,可以直接添加内容(items)。如何实现需求呢?

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

    NSUserDefaults.standardUserDefaults().registerDefaults(dictionary)
  }
  

  func handleFirstTime() {
    let userDefaults = NSUserDefaults.standardUserDefaults()
    let firstTime = userDefaults.boolForKey("FirstTime")
    if firstTime {
      let checklist = Checklist(name: "List")
      lists.append(checklist)
      indexOfSelectedChecklist = 0
      userDefaults.setBool(false, forKey: "FirstTime")
      userDefaults.synchronize()
    }
  }

  init() {
    loadChecklists()
    registerDefaults()
    handleFirstTime()
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,830评论 0 6
  • 因为要结局swift3.0中引用snapKit的问题,看到一篇介绍Xcode8,swift3变化的文章,觉得很详细...
    uniapp阅读 4,412评论 0 12
  • 1.badgeVaule气泡提示 2.git终端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夹内容...
    i得深刻方得S阅读 4,652评论 1 9
  • 多少年后,我仰望星空,仰望我们曾经一起仰望的那片星空,泪流满面… Part ~1(旁观者) 多...
    AlphaBoy阅读 562评论 2 2
  • 秋天的修辞 披着一股凉意 在秋虫的低吟声里 压扁了音域 美丽的秋色 遁入草丛 延至泥土的胸怀 美丽的秋天 硕果...
    梦双眸阅读 339评论 2 6