A Day In The Life

とあるプログラマの備忘録

【正誤表】プロの力が身につく iPhone/iPadアプリケーション開発の教科書 Swift対応版

「プロの力が身につく iPhone/iPadアプリケーション開発の教科書」をお買い上げの皆様、ありがとうございます。
読者様からご指摘を頂き、誤りがありましたので訂正させていただきます。

2-3テーブルビューとコレクションビュー

148ページ、プログラム2つ目の下から3行目

訂正前

override func tableView(tableView: UITableView,
  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath)
      as UITableViewCell
    cell.textLabel.text = self.groups[indexPath.section][indexPath.row]
    return cell
}

訂正後

override func tableView(tableView: UITableView,
  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath)
      as UITableViewCell
    cell.textLabel?.text = self.groups[indexPath.section][indexPath.row]
    return cell
}

加えてサンプルプログラムにも修正が必要でしたので修正しました。GitHub に push 済みです。

CollectionViewサンプルプログラム

ViewController クラス viewDidLoad メソッド、本の内容に訂正はありません。GitHub に push 済みです。
修正前

override func viewDidLoad() {
  super.viewDidLoad()      
  self.collectionView.contentInset = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
}

修正後

override func viewDidLoad() {
  super.viewDidLoad()      
  self.collectionView?.contentInset = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
}
Navigationサンプルプログラム

ViewController クラス tableView:cellForRowAtIndexPath: メソッド、本の内容に訂正はありません。GitHub に push 済みです。
修正前

override func tableView(tableView: UITableView,
  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath)
      as UITableViewCell
    cell.textLabel.text = self.groups[indexPath.section][indexPath.row]
    return cell
}

修正後

override func tableView(tableView: UITableView,
  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath)
      as UITableViewCell
    cell.textLabel?.text = self.groups[indexPath.section][indexPath.row]
    return cell
}

3-3データをフラッシュドライブに保存する

191ページ、プログラム2つ目の7行目(4/3追加)

訂正前

@IBAction func respondToArchiveButton() {
  // ファイルパスの取得
  let paths = NSSearchPathForDirectoriesInDomains(
    .DocumentDirectory,
    .UserDomainMask, true) as [String]
  // 保存するファイルの名前
  let filePath = String(paths[0]) + "data.dat"
  // 保存するデータ、氏名と住所
  let array = ["山田太郎", "104-0061", "東京都", "中央区", "銀座1丁目"]
  // アーカイブしてdata.datというファイル名で保存する
  let successful = NSKeyedArchiver.archiveRootObject(array, toFile: filePath)
  if successful {
    println("データの保存に成功しました。")
  }
}

訂正後

@IBAction func respondToArchiveButton() {
  // ファイルパスの取得
  let paths = NSSearchPathForDirectoriesInDomains(
    .DocumentDirectory,
    .UserDomainMask, true) as [String]
  // 保存するファイルの名前
  let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
  // 保存するデータ、氏名と住所
  let array = ["山田太郎", "104-0061", "東京都", "中央区", "銀座1丁目"]
  // アーカイブしてdata.datというファイル名で保存する
  let successful = NSKeyedArchiver.archiveRootObject(array, toFile: filePath)
  if successful {
    println("データの保存に成功しました。")
  }
}
192ページ、プログラム1つ目の3行目(4/3追加)

訂正前

    .DocumentDirectory,
    .UserDomainMask, true) as Array<String>
  let filePath = String(paths[0]) + "data.dat"
  if let array = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as? Array<String> {
    for str in array {
      println(str)
    }
  } else {
    println("データがありません")
  }
}

訂正後

    .DocumentDirectory,
    .UserDomainMask, true) as Array<String>
  let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
  if let array = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as? Array<String> {
    for str in array {
      println(str)
    }
  } else {
    println("データがありません")
  }
}
197ページ、プログラム1つ目の下から8行目(4/3追加)

訂正前

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToArchiveButton() {
    // 山田太郎、花子オブジェクト(住所が同じ夫婦という設定)
    let address1 = Address(zipCode: "104-0061",
      state: "東京都", city: "中央区", other: "銀座1丁目")
    let taroYamada = Person(name: "山田太郎", address: address1)
    let hanakoYamada = Person(name: "山田花子", address: address1)
    // 田中次郎オブジェクト
    let address2 = Address(zipCode: "604-8126",
      state: "京都府", city: "京都市", other: "中京区")
    let jiroTanaka = Person(name: "田中次郎", address: address2)
    // 保存するデータを配列にまとめる
    let people = [taroYamada, hanakoYamada, jiroTanaka]
    // 保存するファイルの設定
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]) + "data.dat"
    // アーカイブしてファイルに保存
    let successful = NSKeyedArchiver.archiveRootObject(people, toFile: filePath)
    if successful {
      println("データの保存に成功しました。")
    }
  }
}

訂正後

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToArchiveButton() {
    // 山田太郎、花子オブジェクト(住所が同じ夫婦という設定)
    let address1 = Address(zipCode: "104-0061",
      state: "東京都", city: "中央区", other: "銀座1丁目")
    let taroYamada = Person(name: "山田太郎", address: address1)
    let hanakoYamada = Person(name: "山田花子", address: address1)
    // 田中次郎オブジェクト
    let address2 = Address(zipCode: "604-8126",
      state: "京都府", city: "京都市", other: "中京区")
    let jiroTanaka = Person(name: "田中次郎", address: address2)
    // 保存するデータを配列にまとめる
    let people = [taroYamada, hanakoYamada, jiroTanaka]
    // 保存するファイルの設定
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
    // アーカイブしてファイルに保存
    let successful = NSKeyedArchiver.archiveRootObject(people, toFile: filePath)
    if successful {
      println("データの保存に成功しました。")
    }
  }
}
198ページ、プログラム1つ目の10行目(4/3追加)

訂正前

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToUnarchiveButton() {
    // 保存するファイルの設定
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]) + "data.dat"
    // アンアーカイブする
    let array = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as Array<Person>
    for person in array {
      println(person.name)
      println(person.address.zipCode)
      println(person.address.state)
      println(person.address.city)
      println(person.address.other)
    }
  }
}

訂正後

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToUnarchiveButton() {
    // 保存するファイルの設定
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
    // アンアーカイブする
    let array = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as Array<Person>
    for person in array {
      println(person.name)
      println(person.address.zipCode)
      println(person.address.state)
      println(person.address.city)
      println(person.address.other)
    }
  }
}
202ページ、プログラム1つ目の10行目(4/3追加)

訂正前

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToSaveButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]) + "data.plist"
    // 都道府県データ(NSArray型にキャストする)
    let array = ["北海道", "青森県", "岩手県", "秋田県", "宮城県", "山形県"] as NSArray
    let successful = array.writeToFile(filePath, atomically: false)
    if successful {
      println("データの保存に成功しました。")
    }
  }
}

訂正後

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToSaveButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
    // 都道府県データ(NSArray型にキャストする)
    let array = ["北海道",
                      "青森県",
                      "岩手県", 
                      "秋田県",
                      "宮城県",
                      "山形県"] as NSArray
    let successful = array.writeToFile(filePath, atomically: false)
    if successful {
      println("データの保存に成功しました。")
    }
  }
}
203ページ、プログラム1つ目の下から8行目(4/3追加)

訂正前

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToLoadButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]) + "data.plist"
    // データをプロパティリストから読み込む
    let array = NSArray(contentsOfFile: filePath)!
    for data in array {
      println(data)
    }
  }
}

訂正後

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToLoadButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
    // データをプロパティリストから読み込む
    let array = NSArray(contentsOfFile: filePath)!
    for data in array {
      println(data)
    }
  }
}
205ページ、プログラム1つ目の10行目(4/3追加)

訂正前

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToSaveButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]) + "data.plist"
    // 山田太郎、花子オブジェクト(住所が同じ夫婦という設定)
    let address1 = Address(zipCode: "104-0061",
      state: "東京都", city: "中央区", other: "銀座1丁目")
    let taroYamada = Person(name: "山田太郎", address: address1)
    let hanakoYamada = Person(name: "山田花子", address: address1)
    // 田中次郎オブジェクト
    let address2 = Address(zipCode: "604-8126",
      state: "京都府", city: "京都市", other: "中京区")
    let jiroTanaka = Person(name: "田中次郎", address: address2)
    let archivedTaroYamada = NSKeyedArchiver.archivedDataWithRootObject(taroYamada)
    let archivedHanakoYamada = NSKeyedArchiver.archivedDataWithRootObject(hanakoYamada)

訂正後

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToSaveButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
    // 山田太郎、花子オブジェクト(住所が同じ夫婦という設定)
    let address1 = Address(zipCode: "104-0061",
      state: "東京都", city: "中央区", other: "銀座1丁目")
    let taroYamada = Person(name: "山田太郎", address: address1)
    let hanakoYamada = Person(name: "山田花子", address: address1)
    // 田中次郎オブジェクト
    let address2 = Address(zipCode: "604-8126",
      state: "京都府", city: "京都市", other: "中京区")
    let jiroTanaka = Person(name: "田中次郎", address: address2)
    let archivedTaroYamada = NSKeyedArchiver.archivedDataWithRootObject(taroYamada)
    let archivedHanakoYamada = NSKeyedArchiver.archivedDataWithRootObject(hanakoYamada)
206ページ、プログラム2つ目の10行目(4/3追加)

訂正前

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToLoadButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]) + "data.plist"
    // データをプロパティリストから読み込む
    let array = NSArray(contentsOfFile: filePath)!
    for data in array {
      // 読み込んだオブジェクトをアンアーカイブする
      let person = NSKeyedUnarchiver.unarchiveObjectWithData(data as NSData) as Person
      println(person.name)
      println(person.address.zipCode)
      println(person.address.state)
      println(person.address.city)
      println(person.address.other)
    }
  }
}

訂正後

class ViewController: UIViewController {
  :
  : 省略
  :
  @IBAction func respondToLoadButtonClick() {
    // 保存するファイルの設定(拡張子はplist)
    let paths = NSSearchPathForDirectoriesInDomains(
      .DocumentDirectory,
      .UserDomainMask, true) as [String]
    let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")
    // データをプロパティリストから読み込む
    let array = NSArray(contentsOfFile: filePath)!
    for data in array {
      // 読み込んだオブジェクトをアンアーカイブする
      let person = NSKeyedUnarchiver.unarchiveObjectWithData(data as NSData) as Person
      println(person.name)
      println(person.address.zipCode)
      println(person.address.state)
      println(person.address.city)
      println(person.address.other)
    }
  }
}
補足

191ページから206ページにかけての訂正はすべてファイルパスの文字列連結をしている以下のプログラムが実機で実行時エラーになるのが原因です。

// 実行時エラー
let filePath = String(paths[0]) + "data.plist"

以下のようにファイル名の前にスラッシュを付けるだけで修正できますが、

let filePath = String(paths[0]) + "/data.dat"

URL の操作には以下のように stringByAppendingPathComponent メソッドを使ったほうがより安全です。

let filePath = String(paths[0]).stringByAppendingPathComponent("data.plist")

Swift 1.2の変更への対応(2015/5/25追記)

iOS が8.3にバージョンアップしたのに伴い Xcode が6.3に Swift が1.2にバージョンアップされました。Swift 1.2ではシンタックスエラーとなりビルドできなくなるコードがあります。本書で記載しているコードだけでなく、サンプルの自動生成コードにも変更が必要な箇所が発生しました。2015年5月25日にサンプルプログラムのコンパイルエラーを修正したバージョンをGitHubにアップしました。iOS8.3で開発されている読者様はGitHubのサンプルコードページより最新版をダウンロードしてください。また Swift 1.2への対応箇所を以下の記事にまとめましたのでそちらも参考にしてください。

今後、間違いを見つけ次第この記事に追記していきます。

間違いを指摘してくださいました読者様ありがとうございました。今後も本書の間違いや訂正があればこの記事に追記して行きます。訂正があったことをこの場をお借りしてお詫びします。引き続き本書をよろしくお願いします。