長々と脱線したけど、ようやく今回で「または私は如何にして心配するのを止めて…」で公開したサンプルの説明も終わりっす。
↓これね、忘れた?
サンプル:
http://tetera.jp/xcc/book-sample/RPG.zip
「CALayerで完璧」でのUIFont.preferredFontの説明から、「ダイナミックな文字」、「アンラップしてチン♪」、「ダイナミックなレイアウト」と脱線して、今ここなわけですが、まあ脱線中の解説で、numberOfLinesプロパティの意味とかもわかったよね?
5は適当な数字だったわけだ。0でお任せが一番簡単かな。
↓イマココ
・・・
// セリフ部分(文字)
let message = UILabel(frame:frame.insetBy(dx: 30, dy: 30))
message.font = UIFont.preferredFont(
forTextStyle:UIFontTextStyle.title1)
message.numberOfLines = 5
・・・
で、残すは、セリフ付き前景部分をタップに反応させる部分の説明ってことになります。
それが次に示す部分で、タップを見張るオブジェクトUITapGestureRecognizerを作成して、そいつをbaseView(セリフ付き前景部分)に登録してます。
UITapGestureRecognizer
これでbaseViewがタップされると、tapメソッドが呼び出されるようになる。
・・・ // セリフ付き前景 let baseView = UIView(frame: ・・・ ・・・ // タップに反応するようにする baseView.addGestureRecognizer( UITapGestureRecognizer(target: self, action: #selector(tap))) ・・・ } // 場面の段階を1つ進め、それに合わせ、セリフや状況を更新する。 func tap() { ・・・ }
タップを見張るオブジェクトの作成:
UITapGestureRecognizer(target: self, action: #selector(tap))
baseViewへの登録:
baseView.addGestureRecognizer(・・・)
てことなんですが、UITapGestureRecognizer(タップを見張るオブジェクト)の作成時の引数を見て、あれ、これって「ダイナミックな文字」でやった通知そっくりじゃね?と思った人もいるんじゃないでしょうか。
基本、通知と同じで、タップ時に連携するオブジェクトと呼び出すメソッドを指定してます。
ただし、こっちは連携の関係が、通知センタとオブジェクト間じゃなく、オブジェクトとオブジェクト間ってことになる。
で、Appleでは、この2つの仕組みを区別する意味で、通知センタを使うやり方を「通知」、そうでないものを「ターゲットアクションデザインパターン」て呼んでます。
通知センタを間に挟むと、やり取りする相手が不定になる点を区別してるんじゃないかと思われ。
とにかくこれで、UITapGestureRecognizerオブジェクトはタップを検出するとself(ViewControllerね)のtapメソッドが呼び出すようになるわけです。
で、このUITapGestureRecognizerオブジェクトを引数に指定して、UIView派生オブジェクトにaddGestureRecognizerメッセージを送ることで、そのUIView派生オブジェクトが表示してる画面に対するタップを見張るようになるんですな。
baseView.addGestureRecognizer(・・・) ← これ
ジェスチャーを見張るオブジェクトは、このaddGestureRecognizerメッセージを使ってUIViewにどんどん追加できます。
追加するオブジェクトはUIGestureRecognizerを派生したオブジェクトなら何んでもOK。
タップを見張るUITapGestureRecognizerの他に、ドラッグやスワイプ、ピンチジェスチャ(Safariの画面ズーム時なんかに2本の指を縮めたり広げたりするやつね)を見張るオブジェクトなんかもあります。
ちなみに、UIViewのようなiPhone側が提供するクラスには、Appleが決めた命名規則があって、add〜って名前は、引数のオブジェクトが追加登録できる場合に使われるみたいっす(addSubviewとか)。ここら辺はSwiftの決まりじゃないので守らなくてもエラーにはならないけど、わかりやすいので自分のクラス定義なんかでも積極的に使っていきましょう。
作成時にaction:引数に指定できるメソッドは、今回のtapメソッドのような引数なしか、ジェスチャを検知したUIGestureRecognizer派生オブジェクトを受け取るという、2種類のいずれか。
↓こんな感じ
func 好きな名前()
func 好きな名前(_ gestureRecognizer:UIPanGestureRecognizer)
注意)型の部分は、派生クラスの型にするのが一般的。UIPanGestureRecognizerはドラッグを検出するUIGestureRecognizer派生クラス。
ドラッグなんかではドラッグ開始位置や現在の指の位置なんかを知るために、引数付きのメソッドにして、受け取ったUIGestureRecognizer派生オブジェクトから情報を取り出すことになります。
今回はタップで無条件実行なので引数なしを指定した。
メソッドを指定する#selector()の使い方がわからん人は「ダイナミックな文字」を読み直すように。
それと、今回はUITapGestureRecognizerがself(ViewController)より先に消えることはないので、通知の時のように登録解除の必要はないです。
今回のUITapGestureRecognizerオブジェクトが消えるのは、登録先のUIViewオブジェクト(セリフ付き前景部分)が消える時で、そのUIViewオブジェクトが消えるのはself.viewが消える時で、self.viewはViewControllerが消す消さないを決めてるので、ViewControllerが消える前にUITapGestureRecognizerが消えることはない。
で、いよいよ残すはtapメソッドでの処理
var imageView:UIImageView! // 人物用 var message:UILabel! // セリフ・説明用 // セリフ・説明 let script = [・・・ var scriptIndex = 0 // 現在の場面の段階 ・・・ override func viewDidLoad() { ・・・ // スタート self.message.text = script[scriptIndex] scriptIndex += 1 } func tap() { if scriptIndex < script.count { message.text = script[scriptIndex] scriptIndex += 1 if scriptIndex == script.count { UIView.animate(withDuration: 2, animations: { self.imageView.alpha = 1 }) } } }
てわけだけど、ま〜「色々と脱線」の配列の説明で大体わかるよね。
scriptIndexプロパティは、次に表示すべき文字列が、文字列配列(scriptプロパティ)の何番目の要素かを示してます。
なので、tapメソッドでは、最初のifで、scriptの要素数とscriptIndexを比較、小さいならまだ表示してない文字列要素があるってことで、その文字列をscriptから取り出してメッセージ画面であるmessageのtextプロパティに設定してることになる。
tapメソッド内では、scriptIndex、script共に、めんどくさいのでselfつけるのを省略してます。
これで、scriptIndexが示す番号の文字列は表示済みになったので、scriptIndexの値を1つ大きくして、次の番号に更新。
func tap() { if scriptIndex < script.count { message.text = script[scriptIndex] scriptIndex += 1 ←次の番号に更新 ・・・ } }
で、2番目のifで、表示した文字列が最後の要素だったかを確認し、最後の要素だったら、イメージ画面(imageViewプロパティ)をアニメーションで表示させる。
func tap() { ・・・ ↓ 要素数と一致するなら、設定したのは 最後の要素だったということ if scriptIndex == script.count { UIView.animate(withDuration: 2, animations: { self.imageView.alpha = 1 }) } } }
この時に使ってるのがUIViewのalphaプロパティで、こいつはUIColorのalphaプロパティ同様、透明度を指定してます。UIViewの場合は画面の透明度となり、0.0(透明)〜1.0(完全に不透明)の間を指定できて、0.1、0.2、0.3、...といった具合に時間をかけて徐々に値を変更してやれば、今回のようにじわじわと画像が現れるとこになるわけですよ。
UIViewのアニメーション指定
で、この「時間をかけて徐々に値を変更」を依頼してるのがUIViewクラスオブジェクトへのanimateメッセージと、そのanimations:引数に指定してる {・・・} 部分な訳ですよ。
UIView.animate(withDuration: 2, animations: {・・・} )
withDuration:引数の方は、何秒かけてアニメーションするかの指定です。
でもってanimateメッセージでは、animations:引数に渡された処理で行われているUIViewのプロパティへの設定は、アニメーションでおこなうって約束になってるんですわ。この処理を記述してるのが {・・・} という部分。今回なら
{
self.imageView.alpha = 1
}
てことで、これで、イメージ画面(imageViewプロパティ)のalphaプロパティの値を、現在の値から1に、2秒間かけて変化させてくださいってことになるんですな。
タップ時のimageViewのalphaプロパティ値は、viewDidLoadメソッドでの設定なので
imageView.alpha = 0(透明)
であり、これが2秒間かけて、だんだんと1(完全に不透明)に変化することで、今回のような画像がじわっと浮き出る効果が得られるわけっす。
クロージャ
animations:引数に指定した {・・・} てのはクロージャと呼ばれるもので、任意の処理を引数として渡す時に使います。
クロージャは自分のメソッドではなく、独立した処理なので、自分のプロパティを省略形式で書くことは不可能。なのでselfが必須となります。
{
self.imageView.alpha = 1 ←self.は必須
}
で、一応こんな風にself.と書くことで、自分のプロパティを指定できるんだけど、これは暗黙に次のような処理が行われていることを知識として覚えておきましょう。
クロージャ側の見えない定数α = self ←暗黙の処理
{
self.imageView.alpha = 1 ←selfと書かれていることろはαと解釈される
}
ま〜、ここら辺の知識は、今はいいけど、アプリのきめ細かい制御をやり始めると必要になります。興味がある人は「オーナーシップ クロージャ swift」なんかで検索してみましょう。
アニメーション対応プロパティ
UIViewのプロパティへの設定がアニメーションになると言ったけど、実際にアニメーションになるのは対応してるプロパティだけです。
今まで出てきたのだと、alphaの他にframeやcenter、masksToBounds、backgroundColorなんかが対応してます。対応してるかどうかはクイックヘルプで調べることが可能。
ちなみに複数の処理を書くことで同時にアニメーションさせることも可能。
サンプル:
http://tetera.jp/xcc/book-sample/animation.zip
勘のいい人は気付いてると思うけど、こいつも結局UIViewが持つCALayerへの間接的な設定となってます。
実際にアニメーションを管理するのはCAAnimationオブジェクトであり、アニメーションの起動はCALayerオブジェクトへのaddAnimationによる明示的なCAAnimationオブジェクトの追加、もしくはプロパティ値の変更による暗黙のアニメーションってことになります。
CAAnimationについて詳しく知りたい人はAppleの日本語約ドキュメント集ページに置いてある
を読みましょう。
とにかくこれで、「または私は如何にして心配するのを止めて…」で公開したサンプルの解説は終了。
サンプルで使ってる配列scriptの要素を、文字列じゃなく、「アンラップしてチン♪」で紹介した辞書にすると、もうちょっと発展しそうなところで、また次回!