2Dアニメーションのライブラリを試作した話の続きです。UIViewのサブクラスという形を選択し、いろいろなViewと組み合わせて使えることを想定して作りました。
いよいよソースコードの紹介なのですが、クラス本体の紹介の前に、データ定義を紹介します。指定する項目が多すぎて、かなり長いSwiftコードになってしまいました。
最初に作ったデータ定義は、たった1つの構造体で、多くの項目が含まれていました。テストコードを作るのが書きづらく感じ、すぐに分割することにしました。
まず2つに分けようと思ったのですが、上手に分ける基準が見付かりません。分割の試行錯誤を重ねているうちに、3つに分けるのが適切ではないかと思うようになりました。表示する要素(文字列や画像など)に関する情報、アニメーションの共通情報(時間や繰り返し回数など)、2Dアニメーションで指定する情報(位置や回転や透明度など)の3つです。
3つに分けたのですが、構造体名の番号はゼロから始まります。3つめが2Dアニメーションの指定情報で、2番を割り当てられるからです。また、3Dアニメーションで指定する情報の構造体を追加したとき、3番が割り当てられるのも良い点だと考えました。
データ定義の構造体を紹介する前に、構造体の中に含まれる項目のうち、列挙型に適したものを選んで、最初に定義しました。次のように、3つあります。
// UI部品の種類
enum ZavType: Int {
case ZtView = 0
case ZtImg = 1
case ZtText = 2
}
// アニメーションの種類
enum ZavAnimate: Int {
case Za2D = 0 // 2Dアニメ
case Za3D = 1 // 3Dアニメ
case ZaEtc = 2 // 特殊アニメ(画像処理など)
}
// 動作速度変化(Timing Functions)の種類
enum ZavTF: Int {
case ZpLinear = 0
case ZpIn = 1
case ZpOut = 2
case ZpInOut = 3
}
最初は、アニメーションとして動かす表示要素の種類です。汎用的に使うUIVew、画像のUIImage、文字列のStringの3つだけです。UIImageもStringも、最終的にはUIViewの形で生成するのですが、もとの形のままでライブラリへ渡せたほうが使いやすいので、このように作ってみました。
2つめのアニメーションの種類は、今のところ、3種類を考えています。2Dと3Dとその他です。その他は、画像処理を加えながらのアニメーションを考えていますが、細かな内容はまだ未定です。3種類のうち、最初は2Dだけを作りました。
3つめの動作速度変化の種類は、iOSのAPIに用意されている設定項目「Timing Functions」です。時間の変化に比例して動くLinearのほかに、動き出しがゆっくりのIn、終わりがゆっくりのOutなどがあります。この項目が指定できると、アニメーションに速度の変化が付けられて、表現力が向上します。必要だと感じて入れました。
準備が終わったので、いよいよデータ定義の構造体です。3つあるので、順番に取り上げます。
最初の構造体は、表示する要素(文字列や画像など)の関する情報です。
struct ZavDef0 {
var sType: ZavType
var sAnim: ZavAnimate
var sObj: AnyObject? = nil
var sText: String = "dummy"
var sTSize: CGFloat = 24
var sTColor: UIColor = UIColor.blackColor()
var sFuncI: ((UIView) -> Void)? = nil // 表示部品に対する初期化時の加工処理:処理せずはnil
init(sType:ZavType, sAnim:ZavAnimate, sObj:AnyObject?, sFuncI:((UIView) -> Void)?) {
self.sType = sType
self.sAnim = sAnim
self.sObj = sObj
self.sFuncI = sFuncI
}
init(sType:ZavType, sAnim:ZavAnimate, sObj:AnyObject?) {
self.sType = sType
self.sAnim = sAnim
self.sObj = sObj
}
init(sAnim:ZavAnimate, sTSize:CGFloat, sText:String, sTColor:UIColor, sFuncI:((UIView) -> Void)?) {
self.sType = .ZtText
self.sAnim = sAnim
self.sText = sText
self.sTSize = sTSize
self.sTColor = sTColor
self.sFuncI = sFuncI
}
init(sAnim:ZavAnimate, sTSize:CGFloat, sText:String, sTColor:UIColor) {
self.sType = .ZtText
self.sAnim = sAnim
self.sText = sText
self.sTSize = sTSize
self.sTColor = sTColor
}
}
最初の2項目は、列挙型で定義した内容なので、説明不要でしょう。
続くsObjは、UIViewまたはUIImageを受け取るために使います。文字列では使いません。文字列のときにnilとなるので、Optional型にして、デフォルト値としてnilを入れてあります。
続く3項目は、文字列の場合の指定です。文字列、文字サイズ、文字色が含まれます。
最後は、ライブラリ内で生成した表示部品に対して、何かの加工を加えるために用意しました。文字列またはUIImageを指定したときも、ライブラリの内部ではUIViewの形で生成します。この生成されたUIViewに対して加工を加えるため、関数のデータ型を「((UIView) -> Void)?」としました。Optional型を選んだのは、nilのときは機能オフにしたいからです。
構造体の中身の後には、初期化の処理を並べてあります。最初の初期化は、画像またはUIViewで使います。関係する項目だけしか含まれてないので、余計なダミー値を指定する必要はありません。2つ目の初期化は、関数を外したバージョンで、関数なしのときに使います。関数なしの場合がほとんどなので、こちらを多く使うでしょう。3つめと4つめの初期化は、文字列の場合に使います。こちらも関数の有無で2つ用意しました。
今まで作ったデータ定義の構造体では、デフォルトで生成される初期化を使っていました。理由は、初期化の処理を書かなくて済むからです。しかし今回は、指定する項目数が多いので、減らす目的で初期化を多く付けてみました。実際に使ってみると、今回のほうが、格段に使いやすいです。過去に作ったライブラリでも、同じような初期化を追加したくなりました。後で追加しようと思っています。
続いては、2番目のデータ定義の構造体です。アニメーションの共通情報(時間や繰り返し回数など)を指定するために使います。
// 共通動作定義:各種アニメーションに共通する項目
struct ZavDef1 {
var sRepeat: Float = 1 // 繰り返し回数
var sReverse: Bool = false // アニメ終了後に逆再生
var sTimeDo: NSTimeInterval // アニメの実行時間(秒)
var sDelay: NSTimeInterval // アニメ開始までの遅延(秒)
var sHid1: Bool = false // アニメ開始前に隠す
var sHid2: Bool = false // アニメ終了後に隠す
var sFunc1: ((UIView) -> Void)? = nil // アニメ開始時に実行する処理:処理せずはnil
var sFunc2: ((UIView) -> Void)? = nil // アニメ終了時に実行する処理:処理せずはnil
init(sRepeat:Float, sReverse:Bool, sTimeDo:NSTimeInterval, sDelay:NSTimeInterval, sHid1:Bool, sHid2:Bool, sFunc1:((UIView) -> Void)?, sFunc2:((UIView) -> Void)?) {
self.sRepeat = sRepeat
self.sReverse = sReverse
self.sTimeDo = sTimeDo
self.sDelay = sDelay
self.sHid1 = sHid1
self.sHid2 = sHid2
self.sFunc1 = sFunc1
self.sFunc2 = sFunc2
}
init(sRepeat:Float, sReverse:Bool, sTimeDo:NSTimeInterval, sDelay:NSTimeInterval, sHid1:Bool, sHid2:Bool) {
self.sRepeat = sRepeat
self.sReverse = sReverse
self.sTimeDo = sTimeDo
self.sDelay = sDelay
self.sHid1 = sHid1
self.sHid2 = sHid2
}
}
それぞれの項目の役割はコメントとして付けてあるので、簡単に理解できると思います。補足が必要な項目だけ、追加した理由を中心に解説します。
アニメの開始前と終了後に隠す設定は、複雑な動きを実現するために用意しました。それぞれに表示部品に指定できるアニメーションは、回転や異動などを組み合わせても、1種類の動きしか指定できません。もし途中から違う動きをさせたい場合、もっと複雑な指定方法を組み込むことになります。そうせずに、複雑な動きを実現する方法として、アニメーションの前後に消す指定を用意しました。
複数の動きを連続して見せたいときは、次のように指定します。まず最初の動きを指定して、決まった時間だけ動かします。当然、終了時には隠す設定です。この続きは、別なアニメーション指定を追加して実現します。同じ表示部品を使ったアニメーション指定を、終了した時間に開始します。こちらは、開始前に隠す設定です。すると、2つのアニメーションは繋がって再生され、1つの表示部品が、連続した違う動きをしているように見えます。同様に指定すれば、3つ以上の動きを連続して指定できるというわけです。これが、アニメーションの前後に隠す設定を付けた理由です。
最後の項目である2つの関数は、アニメーションの開始と終了時に実行します。表示部品が渡されるので、表示状態を変更することもできます。また、アプリ側の処理を起動するのにも使えます。
最後は、3番目のデータ定義の構造体です。2Dアニメーションで指定する情報(位置や回転や透明度など)を入れています。
// アニメ定義:2Dアニメーションの指定項目
struct ZavDef2 {
var sX1: CGFloat = 0.0 // 位置
var sX2: CGFloat = 0.0
var sY1: CGFloat = 0.0
var sY2: CGFloat = 0.0
var sZoom1: CGFloat = 1.0 // 拡大縮小
var sZoom2: CGFloat = 1.0
var sRoll1: CGFloat = 0.0 // 回転
var sRoll2: CGFloat = 0.0
var sOpa1: Float = 1.0 // 透明度
var sOpa2: Float = 1.0
var sTF: ZavTF = .ZpLinear // 動作速度変化
init(sX1:CGFloat, sX2:CGFloat,sY1:CGFloat, sY2:CGFloat, sZoom1:CGFloat, sZoom2:CGFloat, sRoll1:CGFloat, sRoll2:CGFloat, sOpa1:Float, sOpa2:Float, sTF:ZavTF) {
self.sX1 = sX1
self.sX2 = sX2
self.sY1 = sY1
self.sY2 = sY2
self.sZoom1 = sZoom1
self.sZoom2 = sZoom2
self.sRoll1 = sRoll1
self.sRoll2 = sRoll2
self.sOpa1 = sOpa1
self.sOpa2 = sOpa2
self.sTF = sTF
}
init(sX1:CGFloat, sX2:CGFloat,sY1:CGFloat, sY2:CGFloat, sTF:ZavTF) {
self.sX1 = sX1
self.sX2 = sX2
self.sY1 = sY1
self.sY2 = sY2
self.sTF = sTF
}
init(sX1:CGFloat, sX2:CGFloat,sY1:CGFloat, sY2:CGFloat, sZoom1:CGFloat, sZoom2:CGFloat, sTF:ZavTF) {
self.sX1 = sX1
self.sX2 = sX2
self.sY1 = sY1
self.sY2 = sY2
self.sZoom1 = sZoom1
self.sZoom2 = sZoom2
self.sTF = sTF
}
init(sX1:CGFloat, sX2:CGFloat,sY1:CGFloat, sY2:CGFloat, sRoll1:CGFloat, sRoll2:CGFloat, sTF:ZavTF) {
self.sX1 = sX1
self.sX2 = sX2
self.sY1 = sY1
self.sY2 = sY2
self.sRoll1 = sRoll1
self.sRoll2 = sRoll2
self.sTF = sTF
}
init(sX1:CGFloat, sX2:CGFloat,sY1:CGFloat, sY2:CGFloat, sOpa1:Float, sOpa2:Float, sTF:ZavTF) {
self.sX1 = sX1
self.sX2 = sX2
self.sY1 = sY1
self.sY2 = sY2
self.sOpa1 = sOpa1
self.sOpa2 = sOpa2
self.sTF = sTF
}
}
表示位置、拡大縮小、回転位置、透明度の4つの項目は、それぞれ開始時と終了時の値を指定します。
ちなみに今回の構造体では、開始が1で終了が2と、変数名を統一してあります。最後に1桁の数字を加える方法なら、変数名を邪魔しない形で、同じ変数名に違いを付けられます。
最後の項目となる動作速度変化だけは、開始と終了という2つの値を持たないので、1桁の数字は付きません。この項目は、2番目の構造体に入れようとも考えたのですが、表示部品の細かな動きを指定する項目なので、3番目の構造体に入れました。
この構造体でも、複数の初期化を用意してあります。単純な動きを指定する際、最小限の項目だけ記述すれば済むようにとの配慮です。まだまだ加えられるので、実際に使ってみて、使う機会の多い組み合わせを、後から追加すると思います。
データ定義の構造体を紹介するだけでも、かなり長くなってしまいました。ここで一旦、区切ります。続きは次回の投稿にて。
(使用開発ツール:Xcode 6.4, SDK iOS 8.4)