Swiftで簡単にレイヤード・アニメーションを定義できるライブラリ”Anima”を作りました
そろそろ世界的なiOS Application Engineerの祭典「WWDC」が間近ですね!
僕は今回始めてのWWDC and 海外となるので、非常に楽しみかつ緊張しております。英語通じるだろうか・・・。
現地や世界中のエンジニアからいろいろ開発事情などのお話を聞いて回りたいです。
前置きはこの程度にして、本題です。
今回私はSwift3/iOS9以降用ライブラリである Anima というライブラリを作成しました。
リリース直後に Github Trend 入りし、おかげさまでStarは300を超えました!🎉
Example
Animaは非常に簡単にCALayerのアニメーションを記述することができます。
例えば、上のような連続的、かつグルーピングされたアニメーションを記述する場合、以下のように記述することができます。
// "■" のグルーピングされたアニメーションを定義 let startAnimations: [AnimaType] = [.moveByY(-50), .rotateByZDegree(90)] let moveAnimations: [AnimaType] = [.moveByX(50), .rotateByZDegree(90)] let endAnimations: [AnimaType] = [.moveByY(-50), .rotateByZDegree(90)] // "■"のアニメーションを実行。各アニメーションの終了時にUILabelのフェードインアニメーションを開始する animaView.layer.anima .then(.opacity(1.0)) .then(group: startAnimations) .then(group: moveAnimations, options: labelAnimaOption(index: 0)) .then(group: moveAnimations, options: labelAnimaOption(index: 1)) .then(group: moveAnimations, options: labelAnimaOption(index: 2)) .then(group: moveAnimations, options: labelAnimaOption(index: 3)) .then(group: endAnimations, options: labelAnimaOption(index: 4)) .then(group: [.scaleBy(0.0), AnimaType.opacity(0.0)]) // 各"■"のアニメーションが終わった際にUILabelのアニメーションを実行するAnimaOptionを取得 func labelAnimaOption(index: Int) -> [AnimaOption] { let labelAnima = labels[index]?.layer.anima return [.completion({ labelAnima?.then(.opacity(1)).fire() })] }
CAAnimationクラスを用いてアニメーションする場合、各アニメーションに対してCAAnimation, CAGroupAnimationでアニメーションを定義し、CAAnimationDelegateやCATransactionを用いて終了時の処理を・・・と途方もないステップ数を踏まないと実装ができないので、それと比較してもかなり簡潔にアニメーションを記述できるかと思います!
Features
Animaは主に以下の機能を備えています。
- 連続的アニメーション
- グルーピングアニメーション
- 連続的アニメーションの一時停止・再生
- 移動(移動量指定/座標指定)、透明度、回転(x,y,z, 弧度法・度数法) 拡大縮小、その他CALayerのAnimatableなプロパティのアニメーション
- タイプセーフなAnimatable propertyのKeyPath指定・及び実行
- 各種オプション
- duration
- timingFunction ( bounce系を覗いた easings.net のほぼ全てのアニメーションとスプリングアニメーションを指定可能 )
- autoreverse
- repeat(count: Float)
- リピート回数
- .infinity指定可能
- completion
- 完了通知
移動アニメーション ( moveByX )
単純な移動アニメーションであれば以下のようにワンラインで記述することができます。
layer.anima.then(.moveByX(50)).fire()
基本的な動作はCALayerから取得するAnima
オブジェクトを起点とします。
Anima.then(_ animationType: AnimaType, options: [AnimaOption] = []) -> Self
に指定の AnimaTypeを指定します。
AnimaType.moveByX(CGFloat)
は指定した移動量分現在の位置からX座標を動かします。もし、指定した座標に動かす場合はAnimaType.moveTo(x: CGFloat, y: CGFloat)
を定義してください。
Animaは座標移動系のアニメーションを実行する際、内部的にはCALayer.position
を動かさず、CATransform
で動かしています。これは、Viewに紐付いているレイヤー、いわゆるLayer Based ViewのCALayerのpositionを更新した際、Viewの干渉により意図せずCALayer.position
がリセットされてしまうためです。
最終的にAnima.fire()
を実行したタイミングでアニメーションが実行されます!
連続的アニメーション
Anima.then()
はSelfを返却する為、チェーン状にアニメーションを記述することができます。
// 未指定時のアニメーション時間を指定(second) Anima.defaultDuration = 1 // X軸に50(1sec),Y軸に100アニメーション(3sec)する layer.then(.moveByX(50)) .then(.moveByY(100), options: [.duration(3)]) .fire()
各アニメーション毎に個別のオプションを割り当てる事ができます!
連続的アニメーションの遅延実行・一時停止・再開
連続的アニメーションは遅延実行が可能です。例えば上記の例を元に.moveByXを行った後3秒停止、その後moveByYを実行したい場合はAnima.then(waitFor t: TimeInterval)
を指定します。
// X軸に50(1sec),3秒停止、Y軸に100アニメーション(3sec)する layer.then(.moveByX(50)) .then(waitFor: 3) .then(.moveByY(100), options: [.duration(3)]) .fire()
また、ユーザの操作によりアニメーションを一時停止、再開する場合はそれぞれAnima.pause()
、Anima.resume()
が用意されています。
private var anima: Anima? override func viewDidLoad() { super.viewDidLoad() // 永遠に回転するアニメーションを定義 let anima = animaView.layer.anima anima .then(.rotateByZDegree(360), options: [.repeat(count: .infinity)]).fire() // メンバ変数に保持 self.anima = anima } @IBAction func buttonClicked(_ sender: UIButton) { // ボタンクリック時にAnima.statusを見て一時停止・再開する switch anima?.status { case .paused?, .willPaused?: anima?.resume() case .active?: anima?.pause() default: return } }
Animaは性質上Anima.pause()
を呼び出した際、直ちにアニメーションが停止する訳ではなく、チェーン状に記述した各アニメーションがそれぞれ実行完了になった際に次のアニメーションを実行することなく一時停止をする仕様となっています。
Animaはstatus
というプロパティを持っており、ここでアニメーションの実行状況が把握可能です。
各caseの状態は以下のとおりです。
- notFired = fire()呼び出し前
- active = アニメーション中
- willPaused = pause()を呼び出したが、まだ実行中のアニメーションが停止していない状態
- paused = 一時停止中
- completed = 全てのアニメーションが実行終了した状態
グルーピング・アニメーション
最初にお見せしたアニメーションのように、回転しつつ移動するなどのアニメーションも指定可能です。
同じタイミングでアニメーションを実行する場合、Anima.then(group: [AnimaType] options: ~)
を指定します。
let animations: [AnimaType] = [.moveByY(50), .rotateByZDegree(90)] layer.anima .then(group: animations) .fire()
独自アニメーション
AnimaはCALayerで用意されているanimatableなpropertyであればほぼ全て用意されている為、上記で紹介したAnimaType以外にもopacityやborderColor, READMEで紹介しているanchorPointやshadow, borderColorなどをアニメーションさせることができます。 ※何が用意されているかは AnimaTypeのソースを見てもらえれば確認できます
ただし、このままではCALayerのanimatable propertyしかアニメーションができません。
独自propertyをAnimaでアニメーション可能にする為に、AnimaType.original(keyPath: String, from: Any?, to: Any)
を用意しています。
例えばCALayerのサブクラスであるCAEmitterLayerのanimatable propertyであるemitterPositionをアニメーションする場合は以下のようになります。
let layer = CAEmitterLayer() layer.emitterPosition = CGPoint(x: 100.0, y:100.0) layer.anima .then(.original(keyPath: #keyPath(CAEmitterLayer.emitterPosition), from: layer.emitterPosition, to: CGPoint(x: 200.0, y:200.0))) .fire()
keyPathにanimatable propertyを指定し、開始値と終了値を指定すれば、独自のpropertyに対しアニメーションが可能です!
まとめ
正直もうCAAnimationを書くのが辛いというのもあり、今回Animaを作りました。 上記で紹介した以外にもExampleアプリで触って動かせるサンプルを作ったので、是非とも試してください!
$ pod try Anima
PRやissue(とstar)お待ちしております🙃
MacのMarkdownエディタ「Bear」が素晴らしい件について
12月に入ってから、 Evernoteがプライバシーポリシー改定 の件で炎上しました。 僕はもともとEvernoteを黎明期から使用して来ましたが、バージョンアップするたびに当時のシンプルなエディタから、こんな機能までいるのか?というほど高機能に進化し、個人的に手に余るなぁ・・・と感じていました。
またEvernoteのアップデートで無料プランでは同期できるデバイスが2台までとなってしまい、最近では Markdownでメモする事が多くなったので、こりゃいよいよEvernote離れかなと思い、Makrdownで書けるメモアプリを探していました。 その時、社内で「メチャクチャいいエディタがリリースされたぞ!」という話を聞き、早速インストールして使って見たところ、「お、中々いいぞ・・」という感じになったので、こちらで紹介します。 それがBearです。
Bearを使ってみて大体1ヶ月程度ですが ここがいいぞって所をまとめてみようかと思います。
快適なMarkdown
個人的にはこの点が一番気に入っています。 最近はシンプルなテキストエディタを使うことはほどんど無くなり、議事録や個人的にメモっておく時にはMarkdown形式でメモを取るようになりました。 自前でHTMLを書かなくても手軽にフォーマットされたテキストメモを取ることができるし、 コピペすればそのままGithubなどに転載できるのも大きな魅力です。
Markdownエディタは他にもMacDownやMOUなどを触ってきたのですが、 2ペインで左にMarkdown、右にフォーマットされた内容が表示されるようになっているのですが、正直入力エリアが画面の半分となってしまうのがストレスフルだったんですよね・・・。 その点Bearは入力エリア内でフォーマットされるので入力エリアを大きく活用することができます。
またGithubなどでサポートされているMarkdownのシンタックスはほぼほぼサポートされている(テーブルなどは未対応ですが、コミュニティでは今後対応するかもとの事でした)ので、 Markdown記法に慣れた人であればすんなり使いこなせます。
というか、正直EvernoteやConfluenceのようなリッチテキストは編集しにくいので触りたくないというのが正直な所です・・・。
月額料金が安い
Evernoteと比較すると非常にお安い。 EvernoteはEvernote プラスプランで月々360円、年額で3,100円で2台の端末制限解除、1GBのアップロード容量が与えられます。
その点Bearは月々150円、年額1,500円なのでかなり割安に感じます。
まぁEvernoteの情報はiCloud上ではなく、Evernoteサーバに保存される為この値段設定はしょうがないかなとは思いますが 個人的に端末制限を解除する為だけに月々漫画1冊分の料金を払うのはなぁ・・と感じEvernote プラスプランにアップグレードするのは躊躇していたので、月々缶コーヒー一本分ぐらいで済むBearの値段設定は有難かったです。
それでもまぁ月額課金体制かよ!買い切りにしてくれよ!とは思いましたが Bearに連続的なアップグレード・サポートをしてもらうのであれば、サブスクリプション形式でもしょうがないのかなとは思います(それでも買い切りにして欲しいですが)
テーマが変更できる
サブスクリプションを購入しなければなりませんが、Bearは画面のテーマを変更することができます。 しかも、Xcodeなどのテーマ変更機能ではあるあるなエディタ領域のみのテーマ変更ではなく、アプリ全体にテーマを適用することができます。 別にエディタだけ変更できればいいよ、とは思いますが、これがなかなか気持ちがいいんです。
デフォルトのテーマだと目に痛いんで、基本はToothpasteを使っています。 ただ、用意された9テーマしか使えない為、今後テーマのカスタマイズ機能が欲しい所です( Duskテーマ使いたい)
シンプルすぎず、高機能すぎず
BearはEvernoteに比べると、非常にシンプルです。 ノートをひとまとめにするノートブックの概念はBearにはありませんし、リマインダーや動画をメモ内に保存しておくことはできません。 僕はよく作った料理のレシピを管理する為に食べログ等のWebページをクリッピングしてEvernoteに保存していましたが、そのようなWebクリッピング機能もありません(WebページをMarkdownに変換して保存する機能は存在します)。
散々Evernoteとの比較をしておいてなんですが、Evernoteはあらゆる形式の情報を一元管理する為のツールであるがゆえ、上記のような豊富な機能を有しているように思えます。 その点Bearは基本的にMarkdownを書く・保存するという事に特化しています。カテゴリ分類もタグ付けのみで行います。
Bearを最初に開いたときもタグ一覧、メモ一覧、エディタ領域の3ペインで、Ctrl+3で1ペインにすることも可能です。見た目は非常にシンプル。 ですがMarkdownに必要な機能はほぼほぼ有しているので、メモを取る時にストレスに感じる事はありません。 コードシンタックスも豊富なので、スニペットを適当に保存しておくのにも便利です。 またHTMLやRTF形式へのエクスポート・インポート機能も備えている(有料版)ため、必要最低限の機能はBearに備わっています。
まとめ
上記の繰り返しになりますがそもそもEvernoteといわゆるメモアプリそれぞれに求められているもの・体現しようとしているものって方向性として別なのではないか、と思いました。
EvernoteはWeb・テキスト・写真・動画・その他ファイルなどなどタイプは関係なく、あらゆる情報ソースを一元的に管理するものであり、単純にテキストメモをとる、という使い方だけではありません。
Bearは非常にシンプルかつMarkdownエディタとしての機能に特化している為、普段Markdownを書くだけの自分にとっては非常に有用なアプリでした。 シンプルすぎるがゆえ、上記でもあげた不満点が無いことは無いですが、今後のアップデートにさらなる期待をしています。
Bear用Alfred workflow作りました
そんなわけで、Bear用Alfred workflowを作りました。
brというキーワードで、メモの新規作成、検索、また現在Chrome/Safariの最前面で開いているWebページをMarkdown化、Bearにインポートする機能を備えています。 ぜひ使ってみて下さい!
意図を理解していないのに「こんな◯◯は◯◯じゃない」と言うのをやめたい
先日、ふとテレビをつけてみると、陰陽師のドラマが放送されていた。 CGのあまりのチープさにずっとは見ていられなかったけども、市川染五郎演じる安倍晴明に少しの違和感を感じる・・・。 正直言ってしまうと、陰陽師自体には全く興味はないし、夢枕獏の小説がとりわけ好きという訳でもないけども、 それなのにもかかわらずつい、「こんな安倍晴明は陰陽師じゃない!」と言葉がついて出てしまう。
言った後は気分がいいが・・・
この「こんな◯◯は◯◯じゃない」って言葉、いろいろと物事を自分自身の中でつまらなくさせてしまう事が多い。 さっきの陰陽師の件では、言った後はとても気分がよくなる。 Youtubeで映画予告編を見て、「そうそうこれこれ!やっぱり陰陽師は野村萬斎だわ」と懐かしい気持ちになったり、 ドラマ版の粗探しをしてやっぱり昔の方がよかった的な懐古主義で悦に浸る。たいして好きでもないくせに。
そうなるともうフラットな気持ちでなんかドラマは見られなくなる。 もしかしたら面白かったかもしれない事でも、さわりだけ見て拒否反応を起こす。 そうなってしまってはもうおしまいだ!
フラットな気持ちで
会社のある先輩に、仕事・プライベート関わらず、上のように頭ごなしに否定しない人がいる。 最初に必ず「なるほどね」と受け取った後、それに対する良い所・悪い所を整理してくれる。 話していてとても心がすっとする。自分の言っている事がいかに一側面からしか物事をみていないかがわかるのだ。 こういう人が会議中に1人でもいてくれると、とても建設的な話ができると思う。
この先輩のようにすぐ「それは違う!」とならずに、冷静に物事を見る事が出来るってのはとても羨ましいし、できる人ってのはなかなか少ないように思う。
イイトコメガネ
人間ってのは結局、物事の悪い面を探すのが得意な生き物だ。 粗探しをして少しでも他人より上に立とうとする。優越感を得る。 しかしそれでは、自分にとってとても有意義であったかもしれない機会を、みすみす逃してしまう事になりかねない。それはとてもつまらない事だ。
UIViewの枠外に矢印を表示させる
UIViewに図形を描画する方法はいろんな方がまとめて下さり、安易に実装することができるけれども、 枠外に描画するサンプルが見当たらなかったので作ってみた。
こんなかんじ
やっていることは鼻くそほじるぐらい簡単で、 単にCALayerを作ってPathで矢印を描画し、矢印を表示させたいViewにaddしているだけ。
// インジケータをドローイング - (void)drawIndicator:(CGContextRef)ctx { CGContextBeginPath(ctx); if (self.direction == IndicatorDirectionRight) { CGContextMoveToPoint(ctx, 0, 0); CGContextAddLineToPoint(ctx, 1, 0.5); CGContextAddLineToPoint(ctx, 0.0, 1.0); CGContextAddLineToPoint(ctx, 0.0, 0.0); } else { CGContextMoveToPoint(ctx, 1.0, 0); CGContextAddLineToPoint(ctx, 0, 0.5); CGContextAddLineToPoint(ctx, 1.0, 1.0); CGContextAddLineToPoint(ctx, 1.0, 0.0); } CGContextClosePath(ctx); CGContextSetFillColorWithColor(ctx, self.indicatorColor.CGColor); CGContextFillPath(ctx); } - (void)drawInContext:(CGContextRef)ctx { // 座標変換 CGContextTranslateCTM(ctx, 0, 0); CGContextScaleCTM(ctx, self.bounds.size.width, self.bounds.size.height); [self drawIndicator:ctx]; }
今後使うことあるかな・・・
エンジニアリングがしたいのであって、工作がしたいのではない
社会人として4年が過ぎ、スマートフォンアプリプログラマとしての経歴も3年が過ぎようとしていた。 新卒の時にお世話になった人に引きつられ、新しい会社もガリガリとアプリを作ってなんとか食いつないできている。 ゲームはまだ作れないにしろ、iOS,Androidアプリはある程度のレベルであれば作れるようにはなり、業務で別段困ることはなくなってきている。
が、 最近エンジニアとしてやっていくことに、非常に漠然とした不安を感じていた。 いや、何に対しての不安なのかと言われるとよく分からない・・。 何となく、漠然と、このままでいいのかなぁという感じで、ただただ追われる業務をこなしつつまぁそんなもんなのかという気持ちになっていった。
自分がしている業務は工作かもしれない
そんな時にこの記事を読ませて頂いた。 幸い僕の勤務させて頂いている会社では5日でクビを言い渡されることもなく、 長年一緒に仕事させてもらっている。
つーかそもそも営業として新卒で前の会社で入社し、 あまりにも営業がポンコツだった為、プログラムを初めてかかせてもらってからこんなに長くおつきあいさせてもらっているのだから、 記事のような話を聞くと、ほんと恵まれてんなー俺は!と思う。
けどいろいろな話を聞いていると記事の人のような環境ってのがプログラマーにとっては普通で、 みんな当たり前に理系だし、当たり前に情報技術の基礎がしっかりしているし、 当たり前にCUIだし。 そういう人が作るモノと自分が作るモノとはやはりどこか違う。 エンジニアリングしてる感が凄いのだ。
エンジニアリングがしたい
自分はAndroidとiOSのアプリを業務でシコシコ作ってはいるけども、工作感がはんばない。 グーグルさんとアップルさんが用意したSDKと、 デザイナさんからもらったデザインと、 優秀な人が作ってくれたライブラリを組み合わせれば大体のものはできてしまう。
けど、開発するってそういうことなのかな〜となんかもやもやしている。 世の中に無いモノを実現していく力が欲しい。 そして、本音を言うと、 理系に負けたくない!
記事を読んで
文系プログラマが入れば5日でクビになるような環境は確かに存在し そして自分はまだそこで業務する能力が足りていないことも理解できた。 とりあえず、周りにいる理系の人たちの話を一つ一つ噛み砕いて、 真正面から話し合えるようになれるようにしていきたい。
2015年にやってみたい事を箇条書きであげてみる
一年の計は元旦にありと言うけども、結局目標もなにも決めずに既に2015年も10日程過ぎてしまった。
去年は会社も変えた事で(と言っても、先輩についてきただけなのであまり環境は変わってないけども)、なんだかドタバタした一年となってしまい、あんまりちゃんとした目標を立てて行動することができなかった。
それを言い訳にしてはいけないのだけども・・・。
とりあえず一年の計画を立てる為にも、 今年一年達成する/しないに関わらずやりたいことを箇条書きで以下にまとめてみる。
TOEICでスコア750点を超える
昨年度末やったTOEICのスコアが600だったけども、これじゃ全く使い物にならない。
最終的には800は超えたいなーという気持ちはあるけども、
とりあえず今年あたりには700を目標に、できれば750を超えておきたい!
TOEIC|コミュニケーション英語能力を測る世界共通のテスト
4冊ぐらい洋書を読む
前回のTOEICのうち、リーディングスコアがあんまりにもあんまりだったので、リーディング力を鍛えたい!
毎回毎回洋書は読みたいなとは思ってはいたけども、どの程度のレベルのものがいいのか分からず、手を出すことができなかった。
最近こちらの記事を見させて頂いて、Lexile指数の存在と、Amazonがその指数を元に適切な洋書をまとめてくれていることがわかった。
TOEICのスコアから、自分のLexile指数が815Lだったので、とりあえず日本語で読んだことのある星の王子様でも読んでみるかな・・
基本情報技術者試験に合格する ★
プログラマーだってのに、基本情報も持ってないってどうなんだ。
ずっと面倒くさくってとってなかったけども、そろそろ取っておきたい気持ちが・・。
試験日は一番早いので4月19日(日)。
IPA 独立行政法人 情報処理推進機構:試験実施案内:平成27年度春期試験について
Android技術者試験に合格する
メインはiOSではあるけども、最近はAndroidの案件に関わることも増えてきた。
3年ぐらい前はAndroidメインでやっていたけども、最近では Fragmentとかの新しい概念が増えてきたので、これを機に基礎からAndroidの技術を固めておきたい。
けど受験料が15,000円か・ ・高いなぁ・・
アプリケーション技術者認定試験 | Education
FP2級に合格する
FP3級は持っているけども、そろそろ2級を取得しておきたい。というか、そろそろ3級勉強した内容が抜けてきてしまった。
勉強しなおすのであれば、じゃあ2級でも取っておいちゃおうっていう軽い気持ち。
2015年度試験日程・科目・受検手数料 |一般社団法人 金融財政事情研究会
とりあえずこんなものかな・・
思いついたものから、逐一追加していく予定。