UICollectionViewCell をタップしたときにセルの色を変える

UICollectionView のセルをタップしたときに何のリアクションもないと寂しいので、セルの色を変える方法を調べました。

UICollectionViewDelegate を継承した Class に以下のメソッドを追加することで、セルをタップしている間は色を変え、離したら元の色に戻るという挙動になります。

    func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath) {
        let cell = collectionView.cellForItemAtIndexPath(indexPath)!
        cell.backgroundColor = UIColor.clearColor() // タップしているときの色にする
    }

    func collectionView(collectionView: UICollectionView, didUnhighlightItemAtIndexPath indexPath: NSIndexPath) {
        let cell = collectionView.cellForItemAtIndexPath(indexPath)!
        cell.backgroundColor = UIColor.darkGrayColor()  // 元の色にする
    }

MVP と MVVM

MVP と MVVM の違いがよくわからなかったのですが、 調べているうちに違いがなんとなく分かった気がしたので、自分の理解での処理の流れ書いてみます。

MVP

2 種類ある

Passive View

  1. View で入力がある。
  2. View が入力があったことを知る。
  3. 受け取った入力に対する処理を Presenter へ委譲する。
  4. Presenter が処理を行い、 Model を操作する。
  5. Model の値に変更があれば、 その結果を View に渡す。

Supervising Controller

  1. View で入力がある。
  2. View が入力があったことを知る。
  3. 受け取った入力に対する処理を Presenter へ委譲する。
  4. Presenter が処理を行い、 Model を操作する。
  5. Model の値に変更があれば、その変更が View に反映される。

MVVC

  1. View で入力がある。
  2. View が入力があったことを知る。
  3. 受け取った入力に対する処理を ViewModel へ委譲する。
  4. ViewModel が処理を行い、 Model を操作する。
  5. ViewModel で Model の状態を保持しておき、変更があれば View に反映される。

違いは、 5 番目の処理の、 Model の変更をどうやって View へ知らせるかという部分だけ、というように理解しております。

Swift で早期リターン

早期リターンを使うと、コードが読みやすくなります。

例えば、 nil をとりうる値 hoge があって、 nil のときは処理を終了させたいというメソッドの場合、Ruby ではこんな感じになります。

# 早期リターンなし
def test
  if hoge 
    # hoge が nil でないときの処理
  else
    return
  end
end

# 早期リターンありその 1
def test
  if hoge == nil
    return
  end

  # hoge が nil でないときの処理
end

# 早期リターンその 2
def test
  return if hoge == nil

  # hoge が nil でないときの処理
end

hogenil のときは、 return if hoge == nil 以降の処理が実行されないということがすぐに分かります。

Swift の場合、 こんな感じでかけるかと思います。

// 早期リターンなし
func test() -> Void {
  if hoge != nil {
    // hoge が nil でないときの処理
  } else {
    // hoge が nil のときの処理
  }
}

// 早期リターンあり
func test() -> Void {
  if hoge == nil {
    return
  }

  // hoge が nil でないときの処理
}

Swift2 からは guard というのがあり、これを使うこともできそうです。

func test() -> Void {
  guard hoge != nil {
    return
  }

  // hoge が nil でないときの処理
}

guard を使うことで、早期リターンをしているんだなということがわかりやすくなりそうです。

また、 guard ではアンラップもしてくれるようなので、 hoge の値を使いたいというときにも便利そうです。

// guard なしその 1
func test() -> Void {
  if let unwrappedHoge = hoge {
    print(unwrappedHoge)
  } else {
    return
  }
}

// guard なしその 2
func test() -> Void {
  if hoge == nil {
    return
  }

  print(hoge!)
}
  
// guard あり
func test() -> Void {
  guard let unwrappedHoge = hoge else {
    return
  }

  print(unwrappedHoge)
}

Rails のルーティング

ルーティングを追加するとき、毎回調べているので少しずつまとめていこうかと思います。 等価な書き方が色々あるので絶対これというわけではありません。

scope

URI Pattern にのみ追加されます。

Rails.application.routes.draw do
  scope :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
       Prefix Verb   URI Pattern                      Controller#Action
profile_index GET    /user/profile(.:format)          profile#index
              POST   /user/profile(.:format)          profile#create
  new_profile GET    /user/profile/new(.:format)      profile#new
 edit_profile GET    /user/profile/:id/edit(.:format) profile#edit
      profile GET    /user/profile/:id(.:format)      profile#show
              PATCH  /user/profile/:id(.:format)      profile#update
              PUT    /user/profile/:id(.:format)      profile#update
              DELETE /user/profile/:id(.:format)      profile#destroy

scope module:

Controller#Action にのみ追加されます。

Rails.application.routes.draw do
  scope module: :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
       Prefix Verb   URI Pattern                 Controller#Action
profile_index GET    /profile(.:format)          user/profile#index
              POST   /profile(.:format)          user/profile#create
  new_profile GET    /profile/new(.:format)      user/profile#new
 edit_profile GET    /profile/:id/edit(.:format) user/profile#edit
      profile GET    /profile/:id(.:format)      user/profile#show
              PATCH  /profile/:id(.:format)      user/profile#update
              PUT    /profile/:id(.:format)      user/profile#update
              DELETE /profile/:id(.:format)      user/profile#destroy

scope as:

Prefix にのみ追加されます。

Rails.application.routes.draw do
  scope as: :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
            Prefix Verb   URI Pattern                 Controller#Action
user_profile_index GET    /profile(.:format)          profile#index
                   POST   /profile(.:format)          profile#create
  new_user_profile GET    /profile/new(.:format)      profile#new
 edit_user_profile GET    /profile/:id/edit(.:format) profile#edit
      user_profile GET    /profile/:id(.:format)      profile#show
                   PATCH  /profile/:id(.:format)      profile#update
                   PUT    /profile/:id(.:format)      profile#update
                   DELETE /profile/:id(.:format)      profile#destroy

scope と module: と as:

Prefix と URI Pattern と Controller#Action 全てに追加されます。

Rails.application.routes.draw do
  scope :user, module: :user, as: :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
            Prefix Verb   URI Pattern                      Controller#Action
user_profile_index GET    /user/profile(.:format)          user/profile#index
                   POST   /user/profile(.:format)          user/profile#create
  new_user_profile GET    /user/profile/new(.:format)      user/profile#new
 edit_user_profile GET    /user/profile/:id/edit(.:format) user/profile#edit
      user_profile GET    /user/profile/:id(.:format)      user/profile#show
                   PATCH  /user/profile/:id(.:format)      user/profile#update
                   PUT    /user/profile/:id(.:format)      user/profile#update
                   DELETE /user/profile/:id(.:format)      user/profile#destroy

namespace

Prefix と URI Pattern と Controller#Action 全てに追加されます。
( scope と module: と as: ) と同じ結果になります。

Rails.application.routes.draw do
  namespace :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
            Prefix Verb   URI Pattern                      Controller#Action
user_profile_index GET    /user/profile(.:format)          user/profile#index
                   POST   /user/profile(.:format)          user/profile#create
  new_user_profile GET    /user/profile/new(.:format)      user/profile#new
 edit_user_profile GET    /user/profile/:id/edit(.:format) user/profile#edit
      user_profile GET    /user/profile/:id(.:format)      user/profile#show
                   PATCH  /user/profile/:id(.:format)      user/profile#update
                   PUT    /user/profile/:id(.:format)      user/profile#update
                   DELETE /user/profile/:id(.:format)      user/profile#destroy

Git で 変更を一時的によける

あるブランチで色々変更をしていて、中途半端だけど、急遽他のブランチで作業をしなければならなくなった時に、 git stash というコマンドが便利です。

変更を加える

➜  sample git:(hoge) touch hoge.txt
➜  sample git:(hoge) ✗ git add .
➜  sample git:(hoge) ✗ git commit -m "hoge.txt の作成"
➜  sample git:(hoge) hoge.txt の編集
➜  sample git:(hoge) ✗ git status
On branch hoge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   hoge.txt

no changes added to commit (use "git add" and/or "git commit -a")

hoge.txt が作成されている hoge ブランチで hoge.txt を編集します。
git status でワークツリーに変更が加わっていることが確認できています。

変更をよける

ここで git stash を実行して、変更を一時的によけてみます。

➜  sample git:(hoge) ✗ git stash
Saved working directory and index state WIP on hoge: 2e894e0 hoge.txt の作成
HEAD is now at 2e894e0 hoge.txt の作成
➜  sample git:(hoge) git status
On branch hoge
nothing to commit, working directory clean

すると、git status を実行しても先ほどの hoge.txt の変更は表示されません。

一時的によけた変更は、 git stash show で確認できます。

➜  sample git:(hoge) git stash show

 hoge.txt | 1 +
 1 file changed, 1 insertion(+)

他のブランチで変更を加える

そして、fuga ブランチへ checkout して fuga.txt を作成し、これをコミットします。

➜  sample git:(hoge) git checkout fuga
Switched to branch 'fuga'
➜  sample git:(fuga) touch fuga.txt
➜  sample git:(fuga) ✗ git add .
➜  sample git:(fuga) ✗ git commit -m "fuga.txt の作成"
[fuga 199f6b5] fuga.txt の作成
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 fuga.txt

git show でコミット内容を確認してみても、hoge ブランチで行った作業の内容はコミットされていないことが確認できます。

➜  sample git:(fuga) git show

commit 199f6b526fc2b6612e31b8e913df2fe2ae4f0d42
Author: fuga <fuga@fuga.fuga>
Date:   Mon May 23 23:11:46 2016 +0900

    fuga.txt の作成

diff --git a/fuga.txt b/fuga.txt
new file mode 100644
index 0000000..e69de29

変更を戻す

次に、再び hoge ブランチへ checkout します。
先ほど一時的によけた変更を元に戻すには git stash pop を実行します。

➜  sample git:(fuga) git checkout hoge
Switched to branch 'hoge'
➜  sample git:(hoge) git stash pop
On branch hoge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   hoge.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (8e370d4ff891b72ee304fca8bca60b7371663fff)

念のため git status を実行してみると、変更が元に戻っていることが確認できます。

➜  sample git:(hoge) ✗ git status
On branch hoge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   hoge.txt

no changes added to commit (use "git add" and/or "git commit -a")

TableViewController と Storyboard でテーブルをつくる (Swift)

ViewController でテーブルを作成する記事はよく見かけたのですが、 TableViewController を使う方法はあまりなかったのでメモ。

まだまだ学び始めたばかりでよくわかっていないことだらけなので、手順だけ。

ざっくり書くと、

  1. storyboard で TableViewController を追加
  2. 追加した TableViewController の cell に要素を追加
  3. TableViewController に対応させるファイル ( TableViewController.swift ) を作成
  4. cell に対応させるファイル ( TableViewCell.swift ) を作成
  5. cell と TableViewCell.swift を対応させる
  6. TableViewController と TableViewController.swift を対応させる
  7. TableViewController.swift を実装する

という流れです。

storyboard で TableViewController を追加

storyboard を選択し、右下のライブラリの検索窓に tableviewcontroller 等と入力して、 TableViewController をドラッグアンドドロップ

f:id:komaji504:20160516211526p:plain

追加した TableViewController の cell に要素を追加

先ほどと同様に、cell に追加したい要素をライブラリで検索します。
要素を cell の上にドラッグアンドドロップで配置します。

f:id:komaji504:20160516231901p:plain

TableViewController に対応するファイル ( TableViewController.swift ) を作成

command + N 等でファイル作成画面を開きます。
Cocoa Touch Class を選択します。

f:id:komaji504:20160516231929p:plain

次画面で、 Subclass of を UITableViewController にします。
Class 名は自由に変更して構いません。

f:id:komaji504:20160516231937p:plain

そのままファイルを作成します。

f:id:komaji504:20160516231952p:plain

cell に対応させるファイル ( TableViewCell.swift ) を作成

先ほどと同様に、 Cocoa Touch Class を選択します。

次画面で、 Subclass of を UITableViewCell にします。
Class 名は自由に変更して構いません。

そのままファイルを作成します。

f:id:komaji504:20160516232112p:plain

cell と TableViewCell.swift を対応させる

アイデンティティインスペクタから Custom Class の Class を TableViewCell にします。

f:id:komaji504:20160516234353p:plain

次に、属性インスペクタ から Identifier を任意の値に設定します。この値はファイル名と対応していなくて構いません。

f:id:komaji504:20160516234409p:plain

TableViewController と TableViewController.swift を対応させる

先ほどと同様に、 アイデンティティインスペクタから Custom Class の Class を TableViewController にします。

f:id:komaji504:20160516232210p:plain

ちなみに、ここまでの作業だけで storyboard で cell に追加した要素が表示されるようになるかと思うかもしれませんが、まだ不十分です。
Build には成功しますが、以下のよう cell には何も表示されないので、後述の実装が必須となります。

f:id:komaji504:20160516232241p:plain

UITableViewController.swift を実装する

たくさんコードが書いてありますが、変更する箇所は以下の関数 3 つです。

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 0
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return 0
}

/*
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)

    // Configure the cell...

    return cell
}
/*

この 3 つを以下のように書き換えるだけです。

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 5 // 任意の値に変更する ( 1 セクションあたりの行数 )
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return 1 // 任意の値に変更する ( セクション数 )
}

// コメントアウトを外す
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) // reuseIdentifier を cell に設定した Identifier に書き換える

    // Configure the cell...

    return cell
}

以上で作業は終わりです。

確認する

Build すると追加した要素が表示されていることを確認できます。
レイアウトは調整していないので汚いです。

f:id:komaji504:20160516234517p:plain

モデルの属性に自作のバリデーションを追加する

モデルの属性のバリデーションをしたいときに、バリデーションヘルパーの既存バリデーションだけでは足りないことがあると思います。

そのため、Rails ではバリデーションを自作することができます。

その一つの方法としてバリデーションメソッドがあります。

バリデーションメソッド

バリデーション用のメソッドを定義して、オブジェクトの保存時にそのメソッドによりバリデーションする方法です。

例えば、 name 属性を持つ Account モデルに対して、 name の prefix として hoge_ があるというバリデーションをしたい (?) 場合には、以下のようにします。

class Account < ActiveRecord::Base
  validate :prefix_hoge # 保存時に prefix_hoge によりバリデーションをする

  # バリデーションメソッド
  def prefix_hoge
    if name !~ /^hoge\_/ # バリデーションの条件
      errors.add(:name, " は hoge_ から始まるようにしましょう!") # エラーメッセージ
    end
  end
end

これにより、 prefix として hoge_ のない name を持つ Account のオブジェクトを保存しようとするとエラーになります。

# hoge_ あり
irb(main):001:0> Account.first.update(name: 'hoge_test')
  Account Load (0.3ms)  SELECT  `accounts`.* FROM `accounts`  ORDER BY `accounts`.`id` ASC LIMIT 1
   (0.2ms)  BEGIN
  SQL (0.3ms)  UPDATE `accounts` SET `name` = 'hoge_test', `updated_at` = '2016-05-13 14:59:13' WHERE `accounts`.`id` = 1
   (0.6ms)  COMMIT
=> true

# hoge_ なし
irb(main):002:0> Account.first.update(name: 'test')
  Account Load (0.3ms)  SELECT  `accounts`.* FROM `accounts`  ORDER BY `accounts`.`id` ASC LIMIT 1
   (0.1ms)  BEGIN
   (0.2ms)  ROLLBACK
=> false

ですが、 複数の属性に対してバリデーションヘルパーとバリデーションメソッドの両方を利用すると、以下のように同一の属性のバリデーションの記述が分散してしまうことがあります。

class Account < ActiveRecord::Base
  validates  :name,
    presence: true,
    length: { maximum: 10 }
    
  validates :email,
    presence: true

  validate :prefix_hoge

  validate :email_validation

バリデーションを自作する他の方法として、カスタムバリデータがあります。 こちらを使えば上記の点を防ぐことができます。

カスタムバリデータ

先ほどと同様のバリデーションを追加します。
カスタムバリデータは、 バリデーションを自作するためのクラスを継承したクラスを作ります。
クラスには ActiveModel::Validator と ActiveModel::EachValidator の二種類があるのですが、後者の方が使いやすいと思うのでそちらについてのみ書きます。

クラスをパスの通っている場所に作成します。

# lib/validators/prefix_hoge_validator.rb

class PrefixHogeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value) # バリデーションメソッド
    if value !~ /^hoge\_/
      record.errors.add(attribute, " は hoge_ から始まるようにしましょう!")
    end
  end
end

バリデーションメソッドは必ず validate_each とします。

validate_each の引数の record に保存対象のオブジェクトが、 attribute にバリデーション対象の属性が、 value に保存する値 が渡ってきます。

これを Account モデルで利用する際には、バリデーションヘルパーのように記述することができます。

class Account < ActiveRecord::Base
  validates :name,
    prefix_hoge: true

これで先ほどと同様のバリデーションをすることができます。

カスタムバリデータであれば、複数の属性に対してバリデーションヘルパーとバリデーションメソッドの両方を利用したとしても、以下のように対象の属性の記述が一箇所にまとまり見やすくなります。

class Account < ActiveRecord::Base
  validates  :name,
    presence: true,
    length: { maximum: 10 },
    prefix_hoge: true    

  validates :email,
    presence: true
    email_validation: true

とはいえ、毎回カスタムバリデータを使うのではなく、それぞれのメリットデメリットを見極めて使うと良さそうです。