Creative Coding in Swift / C4フレームワークで描画とアニメーション
こんぬづは、そろそろ実家の歳の離れた弟に会いたくなってきた田中です。
さて自分ごとで恐縮ですが、まずは宣伝、今度登壇します。
東京では初登壇です!
try! Swift登壇者の@cjwirthさんや、try! Swift主催の@k_katsumiさんもスピーカーなので、ガクブルですが頑張ります!
はい、宣伝はぼちぼちにして今日はSwiftのフレームワークの紹介をしていきますよ。
C4
今日ご紹介しますのは、SwiftでCreative CodingをするオープンソースフレームワークのC4。 C4はUIKitとCoreAnimationを元に作られています。 opneFrameworksやProcessingなどの他のCreative Coding Frameworkに慣れた人にとってはとても簡単で、初心者でも扱いやすいということがコンセプトになっているようです。
Tutorialも多く用意されていて、中にはSlackのロゴアニメーションや、Skypeのローディングなどが紹介されていて、実践的です。
インストール
公式ページを見ると、インストール方法がカンフーマスターで例えられていくつか紹介されています。 中国武術をやっている身としてはとても親近感。
フレームワークをダウンロードしてきてプロジェクトに追加する基本的なやり方は、低難度で親しみやすさダントツのJC(ジャッキー・チェン。女子中学生ではない)から、最高難度がターミナルから直接git叩いてインストールしてくれって書いてあるブルース・リー。
僕は今回iOS開発者ならお馴染みの、Cocoapodsを使ってインストールしました。
use_frameworks! pod 'C4', '~> 1.0'
上記をPodfileに記述してpod install。
サンプル
今回サンプルとしてこんなものをつくってみました。
どこかで見覚えのある九つの色、のついた円を順々にアニメーションさせて、最後に消えていくオレンジの円は他の円と違うアニメーションを加えました。
以下がコードの全文です。 なかなか長くなっていますが、繰り返し書いているためです。 パターンとしては多くありません。
import UIKit import C4 class ViewController: CanvasController { override func setup() { // MARK: - Circles let orange = createCircle() let gray = createCircle() let blue = createCircle() let green = createCircle() let red = createCircle() let yellow = createCircle() let pink = createCircle() let lightblue = createCircle() let purple = createCircle() // MARK: - Colors let orangeColor = Color(red: 226.0/255.0, green: 122.0/255.0, blue: 4.0/255.0, alpha: 1) orange.fillColor = orangeColor orange.strokeColor = orangeColor let grayColor = Color(red: 125.0/255.0, green: 125.0/255.0, blue: 125.0/255.0, alpha: 1) gray.fillColor = grayColor gray.strokeColor = grayColor let blueColor = Color(red: 2.0/255.0, green: 104.0/255.0, blue: 183.0/255.0, alpha: 1.0) blue.fillColor = blueColor blue.strokeColor = blueColor let redColor = Color(red: 235.0/255.0, green: 66.0/255.0, blue: 44.0/255.0, alpha: 1.0) red.fillColor = redColor red.strokeColor = redColor let greenColor = Color(red: 4.0/255.0, green: 164.0/255.0, blue: 105.0/255.0, alpha: 1.0) green.fillColor = greenColor green.strokeColor = greenColor let yellowColor = Color(red: 241.0/255.0, green: 172.0/255.0, blue: 0.0/255.0, alpha: 1.0) yellow.fillColor = yellowColor yellow.strokeColor = yellowColor let pinkColor = Color(red: 232.0/255.0, green: 65.0/255.0, blue: 143.0/255.0, alpha: 1.0) pink.fillColor = pinkColor pink.strokeColor = pinkColor let lightblueColor = Color(red: 0.0/255.0, green: 143.0/255.0, blue: 215.0/255.0, alpha: 1.0) lightblue.fillColor = lightblueColor lightblue.strokeColor = lightblueColor let purpleColor = Color(red: 113.0/255.0, green: 79.0/255.0, blue: 156.0/255.0, alpha: 1.0) purple.fillColor = purpleColor purple.strokeColor = purpleColor // MARK: - Positions orange.center = canvas.center gray.center = Point(CGPoint(x: canvas.center.x - 100, y: canvas.center.y)) blue.center = Point(CGPoint(x: canvas.center.x + 100, y: canvas.center.y)) red.center = Point(CGPoint(x: canvas.center.x, y: canvas.center.y - 120)) green.center = Point(CGPoint(x: canvas.center.x - 100, y: canvas.center.y - 120)) yellow.center = Point(CGPoint(x: canvas.center.x + 100, y: canvas.center.y - 120)) pink.center = Point(CGPoint(x: canvas.center.x, y: canvas.center.y + 120)) lightblue.center = Point(CGPoint(x: canvas.center.x - 100, y: canvas.center.y + 120)) purple.center = Point(CGPoint(x: canvas.center.x + 100, y: canvas.center.y + 120)) // MARK: - Add circles canvas.add(orange) canvas.add(gray) canvas.add(blue) canvas.add(red) canvas.add(green) canvas.add(yellow) canvas.add(pink) canvas.add(lightblue) canvas.add(purple) // MARK: - Animations let scale = Transform.makeScale(100, 100) let fadeOutColor = Color(red: 0, green: 0, blue: 0, alpha: 0) let orangeAnim = ViewAnimation(duration: 1.0) { orange.transform = scale orange.fillColor = fadeOutColor } let grayAnim = ViewAnimation(duration: 0.7) { gray.transform = scale gray.fillColor = fadeOutColor } let blueAnim = ViewAnimation(duration: 0.7) { blue.transform = scale blue.fillColor = fadeOutColor } let redAnim = ViewAnimation(duration: 0.7) { red.transform = scale red.fillColor = fadeOutColor } let greenAnim = ViewAnimation(duration: 0.7) { green.transform = scale green.fillColor = fadeOutColor } let yellowAnim = ViewAnimation(duration: 0.7) { yellow.transform = scale yellow.fillColor = fadeOutColor } let pinkAnim = ViewAnimation(duration: 0.7) { pink.transform = scale pink.fillColor = fadeOutColor } let lightblueAnim = ViewAnimation(duration: 0.7) { lightblue.transform = scale lightblue.fillColor = fadeOutColor } let purpleAnim = ViewAnimation(duration: 0.7) { purple.transform = scale purple.fillColor = fadeOutColor } let orangeShrink = ViewAnimation(duration: 0.5) { orange.transform = Transform.makeScale(0.3, 0.3) } orangeShrink.curve = .EaseIn let sequence = ViewAnimationSequence(animations: [ pinkAnim, lightblueAnim, purpleAnim, redAnim, greenAnim, yellowAnim, grayAnim, blueAnim, orangeShrink, orangeAnim ]) wait(1.0) { sequence.animate() } } private func createCircle() -> Circle { let rect = Rect(0, 0, 50, 50) return Circle(frame: rect) } }
解説
C4の導入
まずC4を使うために
import C4
と記述して、C4をインポートします。
ミソとなるのはC4を扱うためにはUIViewControllerを継承して作られたCanvasViewControllerというクラスを扱うこと。基本的としてはこのクラスを使って、定義してあるsetup()の中でアニメーションや図形を扱うとよいでしょう。
class viewController: CanvasViewController { override func setup() { } }
CanvasViewControllerの定義を見に行くとsetup()が定義されており、このsetup()はUIViewControllerのviewDidLoad()の中で呼び出されていることがわかります。
public override func viewDidLoad() { canvas.backgroundColor = C4Grey ShapeLayer.disableActions = true self.setup() ShapeLayer.disableActions = false }
図形の生成
tutorialを見るとわかるのですが、基本図形となるようなものはC4の中で定義されています。
今回はCircleを生成して、生成したインスタンスのサイズ、ポジション、色などを設定してcanvasにaddしています。 基本的なUIKitの使い方ととても似ているのでとっかかりやすいですね!
let circle = Circle(frame: Rect(0, 0, 50, 50)) // 図形の中の色 circle.fillColor = Color(red: 226.0/255.0, green: 122.0/255.0, blue: 4.0/255.0, alpha: 1) // 図形の枠線の色 circle.strokeColor = Color(red: 226.0/255.0, green: 122.0/255.0, blue: 4.0/255.0, alpha: 1) canvas.add(circle)
アニメーション
単一のアニメーションの生成は以下のようにします。
// アニメーションの生成。0.5秒の間にclodureの中の処理を行う let animation = ViewAnimation(duration: 0.5) { // circleのサイズを0.3倍にする circle.transform = Transform.makeScale(0.3, 0.3) // alphaを0としているので、フェードアウトする circle.fillColor = Color(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) } // アニメーション開始時を早くする animation.curve = .EaseIn // アニメーションを繰り返し行う animation.repeats = true // アニメーションが終了したら逆再生を行う animation.autoreverses = true // アニメーションの再生 animation.animate()
円形のサイズを広げていくようなアニメーションはリッチ(派手)なUIなんかでよく実装されているのを見ますが、自前で書こうとするとviewを二枚重ねてmaskを設定してゴニョゴニョ...となってなかなか面倒くさかったりします。CircleをmakeScaleして広げる上記はなかなか便利です。
アニメーションを連続させる
アニメーションAが終わったらアニメーションBを再生して、次にアニメーションCをつなげて...と順次実行していきたいというパターンはよくあると思います。
自前で実装しようとすると、completionHandlerで終了を判定してネストを深くしていくか、もしくは非同期処理をメソッドチェーンで書けるようなライブラリを別途導入することも考えますがここら辺は面倒だったりします。
安心してください、便利クラスが用意されてますよ。
// 初期化時のanimationsの引数の型は[ViewAnimation]。 let sequence = ViewAnimationSequence(animations: [animationA, animationB, animationC]) // シークエンスの再生 sequence.animate()
サンプルコードで使用した部分は大体このくらいです。
注意
Colorクラスの初期化時にrgbを指定するinitializerは二つ用意されています。
init(red: Double, green: Double, blue: Double, alpha: Double) init(red: Int, green: Int, blue: Int, alpha: Double)
実装はそれぞれ以下のようになっています。
public init(red: Double, green: Double, blue: Double, alpha: Double) { colorSpace = CGColorSpaceCreateDeviceRGB()! internalColor = CGColorCreate(colorSpace, [CGFloat(red), CGFloat(green), CGFloat(blue), CGFloat(alpha)])! }
public convenience init(red: Int, green: Int, blue: Int, alpha: Double) { self.init(red: Double(red) / 255.0, green: Double(green) / 255.0, blue: Double(blue) / 255.0, alpha: alpha) }
Doubleを引数としている方はそのままの値なのですが、Intを引数としている方は/255.0されています。
そのため、Doubleを引数とする場合は/255.0を自分で記述し、Intを引数とする場合は記述しなくても良いというところが少しハマりポイントかと思われますので、注意してみてください。
まとめ
アプリにおいてはアニメーションを少しつけてあげるだけでも、ユーザーにとって楽しいアプリになったりします。
C4を使ってより良いUXを獲得していきましょう。?ファイトだよ!?