ドリル本のXcode 8対応について その1

Examplesについて

公式サポートで連絡する予定だけど、先行して進捗報告。

 

++、--は廃止されました。
関数の第1引数のラベルが無視されなくなりました。
inoutキーワードの位置が変わります。
Cスタイルのforループは廃止になりました。

UIApplicationMainの第2引数が変更されました。ていうか、CommandLine.unsafeArgvの型定義が追いついていないのかな。

UIApplicationMain(CommandLine.argc,
 UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(
  to: UnsafeMutablePointer<Int8>.self,
  capacity: Int(CommandLine.argc)),
 nil,
 NSStringFromClass(AppDelegate.self)
)
あと、クラスオブジェクト指定に.selfが必要になった。

 

AD
 京チカでは、KMAnnotationViewクラスを派生させた
KMTreasureAnnotationView

てのを用意して、こいつでチカチカさせてます。パトラッシュは
KMTreasureHunterAnnotationView

を使ってる。

$テン*シー*シー-icon
 京チカ

 ソースはギットハブに公開してて、下のリンク先でZIPてボタンをクリックすれば、特にギットハブのアカウントなしで取り出せるはず。

ギットハブ:京チカソース

 ギットハブは友達とワイワイやりながらXcodeでプロジェクト開発したい人にはお勧めの共有ウェブサービス。アカウントは無料なんで、この機会にゲットするのもいいと思いますYooo。
 ギットハブについては、次の次あたりから…

$テン*シー*シー-2

 KMTreasureAnnotationViewやKMTreasureHunterAnnotationViewで何をやってるかというとCAKeyframeAnimationを使った画像切り替え、いわゆるパラパラ漫画っす。
 こんな感じで何枚かの絵を0.3秒程度の間隔で切り替えてるわけですな。

テン*シー*シー-a1

 パトラッシュの場合、当初は歩くアニメーションもさせてたので、左右上下に4コマで歩くアニメーション画像を用意してもらってます。

$テン*シー*シー-a2

 最終的に没にしたんだけど、もったいないのでアバウト画面で歩かせることにしたお。

$テン*シー*シー-ani

 実は、この手のパラパラ漫画はCAKeyframeAnimationを使わなくてもUIImageViewでもできます。animationImagesプロパティにアニメーションさせたい画像群を設定して、startAnimatingメッセージを送るちゅーやり方です。UIImageViewは中で自分のCALayerインスタンスのcontenstプロパティに対してCAKeyframeAnimationしてるんじゃないかと思われ。

 不要なタッチイベントやUIResponderリンクがもれなくおまけに付いてくるけど、お手軽なUIImageViewを使うもよし、画面操作に特化して贅肉は無いけど、扱いはそのぶん手間なCALayerを使うもよし、どっちを選択するかは個人の好みってとこですね。

 ただし、今回チカチカやパトラッシュでやってるのは、1つの画像を用意して、どの領域を画面に表示するか指定する方法なんでUIImageViewでは実現できません。

 私はCALayerを使いました。

 てなわけで、今回はCALayerを使い、CAKeyframeAnimationでパラパラアニメするにはどうすればいいか~の巻~。
 マップとは直接関係ない話なんで新規にプロジェクト作ります。使うテンプレートはおなじみEmpty Application。いつものようにXcodeのFile→New→Project…メニューから1、2、3。

$テン*シー*シー-4

 設定は次のようにしました。Organization Nameには基本、自分の名前とか会社名、グループ名を指定ね。Company IdentifierはAppStoreに並べないならなんでもOK。com.selfはAppleが練習用でよく指定してる。本で使ったjp.eduでもOK。Class Prefixももちろんなんでもいいけど、今後の説明と一致させるには同じTTRを使いましょう。Core Dataは使わない。Automatic Reference Countingは当然使う。Unit testは含めない。

$テン*シー*シー-3

 この後出てくるSource Controlもとりあえずチェックつけといてください。

$テン*シー*シー-4

 プロジェクトのワークスペースウィンドウが開いたら、CALayerを扱うんでQuartzCoreフレームワークをプロジェクトに追加。

$テン*シー*シー-5

$テン*シー*シー-6

 これで準備ができたんで、あとはCAKeyframeAnimationを試すために1枚、CALayerを画面に貼付けます。TTRAppDelegate.mの-application:didFinishLaunchingWithOptions:メソッドでCALayerインスタンス作って、self.windowのlayerプロパティにaddSublayer:します。

$テン*シー*シー-7

#import <QuartzCore/QuartzCore.h>
#import "TTRAppDelegate.h"
・・・
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];

CALayer* animationLayer = [CALayer layer];
animationLayer.frame = self.window.bounds;
[self.window.layer addSublayer:animationLayer];


return YES;
}

 ただーし、これでRunさせても何も変化が無いんで、あらかじめ用意しておいたPNG画像ファイルをプロジェクトに登録してそいつを表示させましょう。画像のサイズは適当。きっちりしたい人はiPhoneの画面サイズと同じにすればいいでしょう。

$テン*シー*シー-8
おりゃああ、ドラッグ&ドロップじゃあ

 プロジェクトに登録するか確認されるので、以下のようにして登録。

$テン*シー*シー-9

 これでPNG画像ファイルがプロジェクトに登録される。

$テン*シー*シー-10

 登録したPNG画像ファイルは、UIImageのコンビニエンスコンストラクタ+imageNamed:でファイル名指定してUIImageインスタンスとして読み込めるんで、こいつを、さっき作ったCALayerインスタンス(animationLayer)のcontentsプロパティに指定してやります。

・・・
CALayer* animationLayer = [CALayer layer];
animationLayer.frame = self.window.bounds;
[self.window.layer addSublayer:animationLayer];
animationLayer.contents = (id)[UIImage imageNamed:@"1.png"].CGImage;
・・・

 気をつけるのは、contentsプロパティに指定するのは、UIImageインスタンスじゃなくCGImageRefでないといけない事。で、CGImageRefはUIImageインスタンスのCGImageプロパティから取り出せるようになっているので、上のような記述になるわけです。こいつは
UIImage* image = [UIImage imageNamed:@"1.png"];
CGImageRef imageRef = image.CGImage;
animationLayer.contents = (__bridge id)imageRef;

をいっきに1行で記述した状態。
 contentsプロパティはid型なのでCGImageRefを直接指定すると型が違うと怒られます。CGImageRef自体はToll-free bridgeの仕組みで、そのまま指定して問題ないんですが、コンパイラは一応、大丈夫かと注意してくるわけです。
 Toll-free bridgeってなんすか?な人は、木下誠さんが書かれた、ダイナミックObjective-C:Toll-free bridgeの記事を読んでみましょう。

 そのためid型として扱ってOKという意思表示をキャスト変換で明示する必要があり
animationLayer.contents = (id)imageRef;

とする必要があるんですが、ARC管理下だともう一つ、ややこしい所有権ポリシーの自動化がからんできて、contentsプロパティとして所有する前に、そっちのCGImageRefをリリースしなくていいのか確認されます。imageNamed:で作成したUIImageインスタンスや、そのimageのCGImageプロパティから取り出すCGImageRefは所有権を持っていません。
 なので逆にリリースしちゃまずいので__bridge idとして単なる移動を指定します。
animationLayer.contents = (__bridge id)imageRef;

 もしCGImageRefに所有権がある状態で、管理はcontentsプロパティに任せて自分は手放したいなら
animationLayer.contents = CFBridgingRelease(imageRef);

 とします。
animationLayer.contents = (id)image.CGImage;

 と書いた場合、コンパイラはimage.CGImageから取り出されたCGImageRefには所有権がない状態を理解できるみたいで、__bridgeつけなくても文句いいません。
 ここらへんはARCのドキュメントで勉強しましょう。

Transitioning to ARC Release Notes

 Runするとこんな感じ。

$テン*シー*シー-11
化物語のエンデングじゃないから、岬洋子ちゃんだから。ゴワッパー5だから

 で、このCALayerインスタンスに
CAKeyframeAnimation

のインスタンスをaddAnimation:forKey:するわけですよ。それでアニメーションがスタートする。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
・・・
animationLayer.contents = (id)[UIImage imageNamed:@"1.png"].CGImage;
CAKeyframeAnimation * animation = [self animationContents];
animation.duration= 5;
animation.repeatCount = HUGE_VALF;
[animationLayer addAnimation:animation forKey:@"bakemono"];

return YES;
}

- (CAKeyframeAnimation*)animationContents
{
CAKeyframeAnimation * animation
= [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.values = @[
(id)[UIImage imageNamed:@"1.png"].CGImage,
(id)[UIImage imageNamed:@"2.png"].CGImage,
(id)[UIImage imageNamed:@"3.png"].CGImage,
(id)[UIImage imageNamed:@"2.png"].CGImage
];
return animation;
}
ちなみに、当然のように後方定義の-animationContentsメソッドを[self animationContents]として呼び出してるわけだが、こいつは最近のXcodeからできるようになりました。もう利用する前方で定義したり、宣言する必要はなくなった。

 CAKeyframeAnimationインスタンスは(その5)の時にはCALayerのtransformプロパティをアニメーションさせるのに使ってたわけですが、transformプロパティに限らず、アニメーション可能なCALayerのプロパティはすべて指定可能です。
 Appleのドキュメント「アニメーションのタイプとタイミング」だとpositionプロパティを指定してピョコピョコ動かしたりもしてる。

日本語ドキュメント

 どのプロパティを対象にするかは、CAKeyframeAnimationインスタンスを作る際にanimationWithKeyPath:に、(その5)で説明したキー値コーディングと呼ばれる記述法で指定します。今回ならアニメーションの対象はcontensプロパティなので@"contents"を指定すればいいわけです。
CAKeyframeAnimation * animation =[CAKeyframeAnimation
animationWithKeyPath:@"contents"];

 で、例えば4つのPNG画像をパラパラ漫画させるには、作成したCAKeyframeAnimationインスタンスのvaluesプロパティに、各画像を表示順に格納させたNSArrayインスタンスを指定してやります。
animation.values = @[
(id)[UIImage imageNamed:@"1.png"].CGImage,
(id)[UIImage imageNamed:@"2.png"].CGImage,
(id)[UIImage imageNamed:@"3.png"].CGImage,
(id)[UIImage imageNamed:@"2.png"].CGImage
];

 UIImageインスタンスじゃなくCGImageRefを指定している事に注意ね。@[…]が何なのかわからない人は(その6)を読みましょう。
 ちなみに
[UIImage imageNamed:@"2.png"].CGImage

を2回やってますが、別のCGImageRefにはならず同じCGImageRefになります。imageNamed:で返されるUIImageインスタンスはシステムにキャッシュされるので問題はない、はず。
 気になる人は
UIImage* img = [UIImage imageNamed:@"2.png"];
animation.values = @[
(id)[UIImage imageNamed:@"1.png"].CGImage,
(id)img.CGImage,
(id)[UIImage imageNamed:@"3.png"].CGImage,
(id)img.CGImage
];

とかしてください。
CGImageRef iref = img.CGImage;

までやっちゃうと
animation.values = @[
(id)[UIImage imageNamed:@"1.png"].CGImage,
(__bridge id)iref,
(id)[UIImage imageNamed:@"3.png"].CGImage,
(__bridge id)iref
];

と書く必要が出てきます。ま、でもこの書き方が一番CPUのロスはないですな。
animation.duration= 5;
animation.repeatCount = HUGE_VALF;

は、5秒間、永遠に繰り返しの指定。
 Runしてみるとフェードイン・アウトしながら画面が切り替わっていくはず。メリハリをつけたいなら、(その5)でやったようにkeyTimesプロパティを指定して、各画面の切り替えタイミングを制御する事もできる。
animation.values = @[
(id)[UIImage imageNamed:@"1.png"].CGImage,
(id)[UIImage imageNamed:@"2.png"].CGImage,
(id)[UIImage imageNamed:@"3.png"].CGImage,
(id)[UIImage imageNamed:@"2.png"].CGImage
];

animation.keyTimes = @[
@0.0,
@0.5,
@0.75,
@1.0
];


return animation;
}

 フェードイン・アウトする理由はCALayerのcontentsプロパティ変更時の暗黙のアニメーションがフェードイン・アウトだから。
 フェードイン・アウトさせたくない場合はCAKeyframeAnimationインスタンスのcalculationModeプロパティにkCAAnimationDiscreteを指定してやります。
animation.calculationMode = kCAAnimationDiscrete;

 この指定は、1枚の画面の表示領域指定の切り替えでアニメーションさせる時には必須の指定になるわけだが、だいぶ長くなったので以下次回!


------------
サンプルプロジェクト:animation.zip
AD
 というわけで、iOSアプリで地図を出そう(その1)で話した「京都まゆまろ杯」応募アプリ完成しました。
 いや~、あせったわ。23日に申請して3月1日までレビュー待ちのままピクリとも動かないんだもん。リリース時期を確実にコントロールしたいなら、2週間前には申請しないと駄目やね。
 まあ「京都まゆまろ杯」も無事エントリーできたし、やれやれですな。確定申告の準備しなきゃ。

$テン*シー*シー-icon
 京チカ

 探せぇ~、ご主人様を~

 地図ぐりぐり動かして、京都のおすすめスポット情報を探し出すアプリっす。
 隠したスポットは91個。
 無料アプリなんで、ぜひ使ってください。手に入れたウンチクを京都旅行中に披露して、友達にうざがられましょう。
 ソースはGitHubで公開中!

ソース

$テン*シー*シー-help

 チカチカをタップするとクイズが出題。
 正解するとスポットの名前がわかるワン。

$テン*シー*シー-3

 正解だと、スポットに付いてくるキーワードをもつ別のスポットも光りだす。

$テン*シー*シー-4

 レーダー波照射!

 京都現地では2km四方を一気に捜索するレーダーが使えるんで、こいつで一気にスポット検索も可能。

$テン*シー*シー-1

 キーワードや見つけたスポットを案内するスポットモードを装備。
 京都に着いた時は、周辺のスポットに立ち寄ってね~。

$テン*シー*シー-2

 iPadでiOS 5使ってる人はハングしちゃいます。
 メンゴ。ただ今、修正版を申請中。

 破トラッシュ知ってるかい、京阪三条駅のそばには土下座像ってのがあるんだよ、元祖どげせんだね。
AD