実は、地図に独自の注釈を表示させる準備は8割方完成してます。
 これまでの経緯で気づいてると思うけど、-mapView:viewForAnnotation:でMKPointAnnotationViewのかわりに独自のビューインスタンスを返してやればいいわけですよ。

 ただし(その2)で説明したように、UIView派生クラスじゃなくMKAnnotationView派生クラスにする必要があります。
 UITableViewCell同様にMKAnnotationViewをそのままインスタンス化して返してもいい。
 なので、試しにMKPointAnnotationViewをMKAnnotationViewで返してみます。
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id )annotation
{
・・・
MKAnnotationView* pinView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:Identifier];
if (pinView == nil) {
pinView = [[MKAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:Identifier];
・・・
}

 あと、MKPointAnnotationViewをMKAnnotationViewにすると、animatesDropなんてプロパティは無いって怒られます。あれはピンを落とすアニメーションでMKPointAnnotationView独自ぽい。

$テン*シー*シー-1

 animatesDropの行は削除の方向で。
・・・
if (pinView == nil) {
pinView = [[MKAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:Identifier];
pinView.animatesDrop = YES;
・・・
}

 これで、できたー完成えええってRunすると、まったく見えなくなる。

$テン*シー*シー-2

 MKPointAnnotationViewだと自動でやってくれてたんですがMKAnnotationViewの場合、画面上でどのくらいの矩形で表示させるかを指定する必要があるんですよ。
 一般的なframeプロパティを設定してもいいんですが、画面上の位置はたえずKMMapView側が調整するので次のような感じでboundsプロパティ側を指定する方が素直でしょうね。
pinView.bounds = CGRectMake(0, 0, 44, 44);

 ここらへんはドリル本のQ03で説明した話。
 で、これだけだと透明な注釈ができあがってしまうんで、背景色を無理矢理指定して位置を確認してみます。
pinView.backgroundColor = [UIColor redColor];

 これでRunさせると

$テン*シー*シー-3
失礼、かぶりました

 ちょっと、最初に自動的に追加する注釈の位置を移動させましょう。
 それと、コールアウトを出せるようにtitleプロパティも設定しときます。
- (void)viewDidLoad
{
・・・
MKPointAnnotation* pin = [[MKPointAnnotation alloc] init];
pin.coordinate = CLLocationCoordinate2DMake(35.0212466 + 0.005, 135.7555968);
pin.title = @"自動追加";
・・・
}

 これでRunさせると

$テン*シー*シー-4

 てな具合になる。
 backgroundColorプロパティを設定しない場合、透明な状態でタップでコールアウトが表示されるようにもできる。そこらへんは各自でやってみてください。

 さすがにこれだけだと辛いものがあるんで、MKAnnotationViewにはimageプロパティが用意されてます。UITableViewCellの場合はtitleLabelやimageViewと盛りだくさんだったけど、MKAnnotationViewはimageプロパティだけ。ちとさみしいがアイコンとして使うのが基本なんで妥当なところでしょう。
 とりあえずプロジェクトに画像を追加して、こいつを読み込んで貼付ける事にします。

テン*シー*シー-5
こんな感じのマスク付きPNG画像

 やり方はいろいろですが、私はPNGファイルをプロジェクトのフォルダに入れてから、プロジェクトナビゲーターエリアにドラッグ&ドロップしました。

テン*シー*シー-6

 追加されたファイル(drop.png)にはプロジェクトナビゲーターエリアの項目に「?」が付いてるはず。

テン*シー*シー-7

 変更履歴に加えるべきか判断つかないので「?」状態です。
 gitに対してこのファイルも履歴管理対象に加えてもらうよう伝えます(gitに伝えるのはコミット前ならいつでもいいけど)。
 プロジェクトナビゲーターエリアでdrop.pngを右クリック(またはcontrolキー押しながらクリック)でコンテキストメニュー出してSource Control→Addを選びます。

テン*シー*シー-8

 これでマークが「A」に変わる。

テン*シー*シー-9

 あとは、さっきの-mapView:viewForAnnotation:メソッドでpinViewのimageプロパティにdrop.pngを指定して作成したUIImageインスタンスを割り当てる。

- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id )annotation
{
・・・
pinView.backgroundColor = [UIColor redColor];

pinView.image = [UIImage imageNamed:@"drop"];

 Runするとこんな感じ。

$テン*シー*シー-10

 ま、ここらで全体をコミット。
 ちなみにプロジェクト全体をいっきにコミットする場合は、File→Source Control→Commit…メニューを選びます。コミット対象のファイル一覧が出て、変更点を比較できるようになってる他は単独ファイルのコミットと同じ。

$テン*シー*シー-11

 boundsを広げると画像も拡大される。注意するのはimageプロパティを設定すると、自動的にUIImageのsizeプロパティの大きさにboundsが設定される事。なのでboundsを設定するのはimageプロパティを設定した後にしないといけない。
pinView.image = [UIImage imageNamed:@"drop"];
pinView.bounds = CGRectMake(0, 0, 200, 200);

 Runするとこんな感じ。

$テン*シー*シー-12

 drop.pngをオリジナルのサイズで表示して、なおかつboundsを広げるにはcontentModeプロパティを設定すればいい。初期設定はUIViewContentModeScaleToFill(UIViewの矩形に合わせて画像を拡大縮小)です。
pinView.bounds = CGRectMake(0, 0, 200, 200);
pinView.contentMode = UIViewContentModeCenter;

 これだと矩形中央に画像を原寸表示となる。

テン*シー*シー-13

 backgroundColorを設定しなければ画像だけ表示され、タップに反応する矩形は200x200のままにもなる。

テン*シー*シー-14

 アイコンは小さめだけど、反応は良くしたいって場合に使う手です。

 ところで、ドリル本をやった人は気づいたかもしれないけど、画像が設定されるのはMKAnnotationViewのlayerのcontentsです。
#import <QuartzCore/QuartzCore.h>
・・・
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id )annotation
{
・・・
pinView.rightCalloutAccessoryView = rightButton;

printf("pinView.layer.contents = %p\nimage = %p\n",
pinView.layer.contents, pinView.image.CGImage);
return pinView;
}
pinView.annotation = annotation;
return pinView;
}

てやれば確認できます。これでRunさせるとコンソールには
pinView.layer.contents = 0x1bb802c0
image = 0x1bb802c0

と表示される。
 実はこれくらいの処理はQuartzCoreフレームワーク加えなくても実行できるんですな。
 ついでに次のような関数を用意して、RunさせてMKAnnotationViewの構成をみてみると…
// UIView構造
static void printViewStruct(UIView* view)
{
printf("view class = %s\n", [NSStringFromClass([view class]) UTF8String]);
printf("subviews\n");
for (UIView* v in view.subviews) {
printViewStruct(v);
}
}

// CALayer構造
static void printLayerStruct(CALayer* layer)
{
printf("layer class %s\n", [NSStringFromClass([layer class]) UTF8String]);
printf("sublayer\n");
for (CALayer* l in layer.sublayers) {
printLayerStruct(l);
}
}
・・・
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id )annotation
{
・・・
printf("pinView.layer.contents = %p\nimage = %p\n",
pinView.layer.contents, pinView.image.CGImage);

printViewStruct(pinView);
printLayerStruct(pinView.layer);
・・・

 コンソールには
view class = MKAnnotationView
subviews
layer class CALayer
sublayer

と出力されます。ほんとシンプルにUIViewのみってわけですね。

 NSStringFromClass関数はドリル本のQ01で紹介したやつね。これもイントロスペクションなわけだ。
 printViewStruct関数から同じprintViewStruct関数を呼んでるけど、これは再帰呼び出しと言って、ローカル変数や引数がスタック領域に作られる(入門本やブートキャンプその2で説明したやつね)のを利用した技法です。関数内でstaticやグローバル変数を使ってると、こういう再帰呼び出しはうまく機能しません。再帰呼び出しが可能な関数をリエントラント(reentrant、再入可能)な関数といいます。
 興味がわいた人は「再帰呼び出し C言語」あたりで検索してみてください。クイックソートあたりがよく例題に使われます。単純なバブルソートで数万単位の数字の並べ替えをやってた人間が、クイックソート学習して「アルゴリズムって大事ぃいいい」と心に刻むわけだ。

 2007年11月、当時民主党の大統領候補だったバラク・オバマが、グーグル社長の「100万の32bit整数を効果的にソートするには?」という問いに「バブルソートは間違いだね」と答えたという…
 うーむ、オバマ、あなどれん男よ。


 シンプルすぎて、alphaプロパティ設定しちゃうとコールアウトまで影響受けちゃうYoooo。
pinView.contentMode = UIViewContentModeCenter;
pinView.bounds = CGRectMake(0, 0, 200, 200);

pinView.alpha = 0.5;


テン*シー*シー-15

アニメーションするとコールアウトまで影響受けちゃうYoooo。
pinView.alpha = 0.5;
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionRepeat
|UIViewAnimationOptionAllowUserInteraction
|UIViewAnimationOptionAutoreverse
animations:^{
pinView.transform = CGAffineTransformMakeScale(1.5, 1);
}
completion:^(BOOL finished) {

}];

 こいつはドリル本で紹介したUIView +animateWithDuration:animations:の詳細版メッセージ。繰り返しや、アニメーション中のユーザータップを受け付けるよう指定してる。CGAffineTransformMakeScaleで横1.5倍拡大を指定してるので1.0~1.5で横が伸び縮みする。

テン*シー*シー-16

 ここらへんをどうするかは、以下次回!

------------
サンプルプロジェクト:KyotoMap-4.zip