UIViewに配置された特定のUI部品を検索する では、UI部品のクラスによって検索を行った。
本記事では、自由な検索条件を設定できるよう改良した結果を紹介する。
UILabelのインスタンスを検索するコード
改良前
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関数
func retrieveUIView(parentView: UIView, var arrayAppendTo array: [UIView]?, viewShouldAppend: (UIView) -> Bool) -> [UIView]
- parentsViewのsubviews以下を再帰的に検索する
- 指定された検索条件に合致した要素をarrayに追加し、このarrayを返す。
- 検索条件はviewShouldAppend関数で決定する。引数のviewを配列に追加すべきであればtrue、そうでなければfalseを返すものとする。
引数
- parentView: UIView
- 検索対象のUI部品
- arrayAppendTo array: [UIView]
- 検索条件にマッチしたUI部品を格納する配列
- viewShouldAppend: (UIView) -> Bool
- 検索条件(配列に格納するかどうか)を決定する関数。
戻り値
検索条件に合致したUIViewの配列
改良後
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というクロージャの記述法により関数の宣言を省いている。
呼び出し例2
let views = retrieveUIView(self.view, arrayAppendTo: labels) { view in
// UILabel以外はtrueを返す
!view.dynamicType.isKindObClass(UILabel)
}
Trailing Closure
関数が最後の引数に関数またはクロージャを取る場合、クロージャを引数リストの後に記述することが出来る。
in
の前に引数リストと戻り値の型を記述する。
関数でいえばviewが仮引数にあたり、 in
以降は関数の本文となる。
型推論により引数の型、戻り値の型は省略できる。戻り値がVoid型の場合は-> "戻り値の型"
も省略できる。
また、本文が1文のみの場合は return
も省略できる。
検索条件を設定するクロージャのサンプル
全ての子ビュー
{ _ in true }
クロージャの引数を利用しない場合、仮引数は_(アンダースコア)とすることが出来る。
あるクラスのインスタンスであるか
UIViewはNSObjectのサブクラスであるから、NSObjectプロトコルを実装する。故にisMemberOfClass関数が利用出来る。
{ view in view.isMemberOfClass(目的のクラス名) }
複数のクラスを対象にするならばswitch文を使うべきか。
{ 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関数が利用できる。
{ view in view.isKindOfClass(目的のクラス名) }
幅が100より大きいビュー
{ view in view.frame.width >= 100 }
ビューの中心が画面の上半分にあるビュー
{ view in
let centerY = UIScreen.mainScreen().bounds.height / 2
return view.center.y > centerY
}