"UIViewに配置された特定のUI部品を検索する"の改良


[UIViewに配置された特定のUI部品を検索する](http://qiita.com/MIN/items/707a8df84902a8ff0fc3) では、UI部品のクラスによって検索を行った。
本記事では、自由な検索条件を設定できるよう改良した結果を紹介する。

##UILabelのインスタンスを検索するコード


```swift:改良前
import UIKit

class FirstViewController: UIViewController {
    
    var array:[AnyObject]!
    
    override func viewDidLoad() {
        super.viewDidLoad()        
        array = []

        // FirstView内のUILabelを検索する
        retrieveUIView(UILabel.self, parentView: self.view)
        
        println("UILabelの数: \(array.count)")
    }

    // parentViewのsubviews以下を検索
    // aClassのインスタンスがあればarrayへ追加
    func retrieveUIView(aClass: AnyClass, parentView: UIView) {
        for childView in parentView.subviews {
            
            // childViewのクラス名を出力
            // println("クラス: \(childView.dynamicType.description())")
            
            // childViewがaClassのインスタンスならばarrayに追加
            if (childView as NSObject).dynamicType.isEqual(aClass) {
                array.append(childView)
            }
            
            // 子のビューについて再帰
            retrieveUIView(aClass, parentView: childView as UIView)
        }
    }
}

```

### 新しいretrieveUIView関数

```swift:
func retrieveUIView(parentView: UIView, var arrayAppendTo array: [UIView]?, viewShouldAppend: (UIView) -> Bool) -> [UIView]
```

- parentsViewのsubviews以下を再帰的に検索する
- 指定された検索条件に合致した要素をarrayに追加し、このarrayを返す。
- 検索条件はviewShouldAppend関数で決定する。引数のviewを配列に追加すべきであればtrue、そうでなければfalseを返すものとする。

#### 引数
<dl>
	<dt>parentView: UIView</dt>
	<dd>検索対象のUI部品</dd>
	<dt>arrayAppendTo array: [UIView]</dt>
	<dd>検索条件にマッチしたUI部品を格納する配列</dd>
	<dt>viewShouldAppend: (UIView) -> Bool</dt>
	<dd>検索条件(配列に格納するかどうか)を決定する関数。</dd>
</dl>

#### 戻り値
検索条件に合致したUIViewの配列

```swift:改良後
import UIKit

class FirstViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 引数のUIViewがUILabelであるか否かを返す
        func isUILabel(view: UIView) -> Bool { return view.dynamicType.isKindOfClass(UILabel) }
        // retrieveUIView関数の呼び出し例1
        let labels = retrieveUIView(self.view, arrayAppendTo: nil, viewShouldAppend: isUILabel)
        println("UILabelの数: \(labels.count)")

        // retrieveUIView関数の呼び出し例2
        let views = retrieveUIView(self.view, arrayAppendTo: labels) { view in
            // UILabel以外はtrueを返す
            !view.dynamicType.isKindOfClass(UILabel)
        }
        
        println("子ビューの数: \(views.count)")
    }

    // retrieveUIView関数の定義
    func retrieveUIView(parentView: UIView, var arrayAppendTo array: [UIView]?, viewShouldAppend: (UIView) -> Bool) -> [UIView] {
        
        if (array == nil) { array = [] }
        
        for childView in parentView.subviews {
            viewShouldAppend(childView as UIView) ? array!.append(childView as UIView) : ()
            array = retrieveUIView(childView as UIView, arrayAppendTo: array, viewShouldAppend)
        }
        
        return array!
    }   
}

```



### retrieveUIView関数の呼び出し

呼び出し例2では、Trailing Closureというクロージャの記述法により関数の宣言を省いている。

```swift:呼び出し例2
let views = retrieveUIView(self.view, arrayAppendTo: labels) { view in
    // UILabel以外はtrueを返す
    !view.dynamicType.isKindObClass(UILabel)
}
```

####Trailing Closure
関数が最後の引数に関数またはクロージャを取る場合、クロージャを引数リストの後に記述することが出来る。

`in` の前に引数リストと戻り値の型を記述する。
関数でいえばviewが仮引数にあたり、 `in` 以降は関数の本文となる。
型推論により引数の型、戻り値の型は省略できる。戻り値がVoid型の場合は`-> "戻り値の型"` も省略できる。
また、本文が１文のみの場合は `return` も省略できる。

## 検索条件を設定するクロージャのサンプル

### 全ての子ビュー

```swift:
{ _ in true }
```

クロージャの引数を利用しない場合、仮引数は_(アンダースコア)とすることが出来る。

### あるクラスのインスタンスであるか
UIViewはNSObjectのサブクラスであるから、NSObjectプロトコルを実装する。故にisMemberOfClass関数が利用出来る。

```swift
{ view in view.isMemberOfClass(目的のクラス名) }
```

複数のクラスを対象にするならばswitch文を使うべきか。

```swift
{ view in
  switch view {
  case let x where x.isMemberOfClass(目的のクラス名1):
      return true
  case let x where x.isMemberOfClass(目的のクラス名2):
      return true
  default:
      return false
  }
}
```

### あるクラス、またはそのサブクラスのインスタンスであるか

isMemberOfClass関数と同様に、NSObjectプロトコルのisKindOfClass関数が利用できる。

```swift
{ view in view.isKindOfClass(目的のクラス名) }
```

### 幅が100より大きいビュー

```swift
{ view in view.frame.width >= 100 }
```

### ビューの中心が画面の上半分にあるビュー

```swift
{ view in
    let centerY = UIScreen.mainScreen().bounds.height / 2
    return view.center.y > centerY
}
```