SimpleCap - Selection History Expose [6] 座標系変換(flippedからnon-flippedへ)

2010年10月31日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: SimpleCap - Selection History Expose [5] Core Animation で表示

今回は座標系変換について。


flippedから non-flippedへ


SimpleCap のメインとなるビューの CaptureView は、NSViewのサブクラスなので通常は座標系は左下原点だが、フリップ(flipped)させているので左上原点となっている。

一方、Core Animation(CALayer)の座標系は左下が原点となっている。

この為、CaptureView の座標を使って CALayer へ描画する際は flippedな座標系から、non-flippedな座標系への変換が必要になる。


変換


ここでは矩形(NSRect or CGRect)を変換対象と考えてみる。NSRect flippedRect から nonFlippedRect へ変換する場合、スクリーンが1つの場合は簡単でこんな感じで求められる。
NSRect screenFrame = [[NSScreen mainScreen] frame];
nonFlippedRect = flippedRect;
nonFlippedRect.origin.y = screenFrame.size.height - flippedRect.origin.y - flippedRect.size.height;

やっかいなのはマルチスクリーンの場合。上記コードのmainScreen以外も考慮に入れる必要がある。

以前調査したようにスクリーンの配置の仕方によって各スクリーンが置かれる座標が異なる。

[参考情報] (旧) Cocoaの日々: β版バグ修正 - マルチスクリーン

今回は下記のケースはこの時の「b. SimpleCapのメインビューのローカル座標系」から「a. スクリーン座標系」への変換に該当する。

まず X座標については変換は不要。flippedの有無は Y座標のみ関係していて、それを除けば基本的に同じ座標系だから。

Y座標についてはサブ画面の位置によって次の2つのケースを考える。
[a] サブ画面が上に載るケース
[b] サブ画面が下に載るケース

変換の考え方は原点を中心に考えるとわかりやすい。
[参考情報] (旧) Cocoaの日々: β版バグ修正 - マルチスクリーン(その2)

そのアプローチで考えた数式は次の通り。
cx1 = lx1;
 cy1 = sy2 - ly1 - lh1;
・スクリーン座標系上での全画面範囲(左下)-(右上):(sx1, sy1)-(sx2, sy2)
・fliipedな座標系内の NSRect:(lx1, ly1),(lw1, lh1)
・(non-flippedな)変換後の座標:(cx1,cy1)

スクリーン1つの場合と似ているが、その時のスクリーンの高さを起点にしていたのに対してマルチスクリーンの場合はそこにスクリーン全体の座標で補正している点。


確認


シングルスクリーンでは問題なし。マルチスクリーンは手元に無いため未確認...。

SimpleCap - Selection History Expose [5] Core Animation で表示

2010年10月30日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: SimpleCap - Selection History Expose [4] 仮表示と選択

前回は履歴の矩形を NSView に直接描画していた。今回はこれを Core Animation に切り替え、各矩形がアニメーションできるようにお膳立てする。


カスタムレイヤー


CALayer をサブクラス化して履歴矩形1つを表すレイヤークラスを用意した。
@interface SelectionHistoryLayer : CATextLayer {

}
+ (SelectionHistoryLayer*)layerWithFrame:(NSRect)frame;

@end
初期化コード
- (id)initWithFrame:(NSRect)frame
{
 if (self = [super init]) {
  self.frame = NSRectToCGRect(frame); // TODO: convert coordination

  CGColorRef colorRef;
  
  colorRef= CGColorCreateGenericGray(0.0f, 0.3f);
  self.backgroundColor = colorRef;
  CGColorRelease(colorRef);
  
  colorRef= CGColorCreateGenericGray(0.0f, 0.5f);
  self.borderColor = colorRef;
  CGColorRelease(colorRef);
  
  self.cornerRadius = 7.0f;
  self.layoutManager = [CAConstraintLayoutManager layoutManager];
  
  // text
  CATextLayer* textLayer = [CATextLayer layer];
  colorRef= CGColorCreateGenericGray(1.0f, 1.0f);
  textLayer.string = [NSString stringWithFormat:@"%d x %d",
       (int)frame.size.width, (int)frame.size.height];
  textLayer.font = @"Lucida-Grande";
  textLayer.fontSize = 13.0;
  textLayer.foregroundColor = colorRef;
  [self addSublayer:textLayer];
  CGColorRelease(colorRef);
  [textLayer addConstraint:[CAConstraint
          constraintWithAttribute:kCAConstraintMidX
          relativeTo:@"superlayer"
          attribute:kCAConstraintMidX]];
        [textLayer addConstraint:[CAConstraint
          constraintWithAttribute:kCAConstraintMidY
          relativeTo:@"superlayer"
          attribute:kCAConstraintMidY]];
 }
 return self;
}
背景色を黒の半透明にして角を丸くする。さらに CATextLayer を追加してサイズを表示する。この時 addConstraint を使うと CATextLayer を中心位置にレイアウトすることができる。

表示はこんな感じ。


履歴矩形の表示


カスタムレイヤーを SimpleCap に組み込んで使う。こんな感じ。
- (void)setupSelectionHistoryLayers
{
 CaptureView *view = [_capture_controller view];
 
 // clean up
 [view.layer removeFromSuperlayer];
 
 // [1] background layer
 // TODO:multi screen
 CALayer* backgroundLayer = [CALayer layer];
 backgroundLayer.frame = NSRectToCGRect([view frame]);
 CGColorRef backgroundColorRef = CGColorCreateGenericGray(0.0f, 0.25f);
 backgroundLayer.backgroundColor = backgroundColorRef;
 CGColorRelease(backgroundColorRef);
 
 view.layer = backgroundLayer;

 // [2] history selection layers
 BOOL currentHasExisted = NO;
 for (NSValue* historyValue in
   [[SelectionHistory sharedSelectionHistory] array]) {
  
  NSRect historyRect = [historyValue rectValue];
  SelectionHistoryLayer* layer =
   [SelectionHistoryLayer layerWithFrame:historyRect];  
  [backgroundLayer addSublayer:layer];
  
  if (NSEqualRects(_rect, historyRect)) {
   currentHasExisted = YES;
  }
 }

 // [3] current selection layer
 if (!currentHasExisted) {
  SelectionHistoryLayer* currentLayer =
   [SelectionHistoryLayer layerWithFrame:_rect];
  [backgroundLayer addSublayer:currentLayer];
 }
 
}
背景用に1つ backgroundLayer を用意してそこへカスタムレイヤーを追加する。[3]で現在表示中の範囲選択も表示しておく。履歴とダブらないようなチェックも入れてある。

実行するとこんな感じ。
おーなんかいい感じだ。

CALayer は毎回作り直している。無駄な気もするが軽いクラスとの触れ込みなので今は気にせずこのままいこう。


文字の明滅


フィルタとアニメーションを組み合わせると文字(レイヤー)を明滅させる効果をつけられる。
コードはこんな感じ。
CIFilter *filter = [CIFilter filterWithName:@"CIBloom"];
  [filter setDefaults];
  [filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"];
  [filter setName:@"pulseFilter"];
  [textLayer setFilters:[NSArray arrayWithObject:filter]];
  
  CABasicAnimation* pulseAnimation = [CABasicAnimation animation];
  pulseAnimation.keyPath = @"filters.pulseFilter.inputIntensity";
  pulseAnimation.fromValue = [NSNumber numberWithFloat: 0.0];
  pulseAnimation.toValue = [NSNumber numberWithFloat: 2.0];
  pulseAnimation.duration = 1.0;
  pulseAnimation.repeatCount = 1e100f;
  pulseAnimation.autoreverses = YES;
  pulseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  [textLayer addAnimation:pulseAnimation forKey:@"pulseAnimation"];

実行例(静止画なのでわかりずらいが文字の周りが明滅している)


参考情報


【特集】Leopardのアニメーションを簡単実装! Core Animationを使いこなす (1) アニメーションプログラミングを身に付けるには"とにかく動かせ" | エンタープライズ | マイコミジャーナル
わかりやすくてとても参考になった。

Core Animationプログラミングガイド: レイヤスタイルプロパティ
レイヤに適用できる視覚効果がわかる。

Core Animationプログラミングガイド: サンプル:Core Animation Menuアプリケーション
Core Animation を使ったサンプルアプリ。レイアウトの取り方や、フィルタのかけかたなどが参考になった。文字の明滅はここからコードを拝借した。


- - - -
SimpleCap内ではflippedしているために座標系がCALayerと異なる。この変換が必要。

SimpleCap - Selection History Expose [4] 仮表示と選択

2010年10月29日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: SimpleCap - Selection History Expose [3] モデル実装

モデルができたのでいよいよ表示にとりかかる。なお修正を加えるクラスは SelectionHandler と呼ぶ範囲選択を処理するクラス。GoFでいう Commandパターン辺りに該当するのだろうか。
interface SelectionHandler : HandlerBase  {
 NSRect _rect;
           : 
 }
HandlerBase は NSObject の派生クラス(なので NSView 系統ではない)。


Selection History Expose表示ボタン


まずは範囲選択にボタンを追加する。自作のボタンバークラスへボタンの追加処理を加える。

実行するとこんな感じ。アイコンは後でちゃんと作る。

[参考情報] cocoaの日々 ThinButton - Google 検索


仮表示


まずはとりあえず履歴領域を白く塗りつぶしてみよう。
- (void)drawRect:(NSRect)rect {
    :
  case STATE_HISTORY:
   [self drawBackground:rect];
   [self drawAllHistorySelection];
   break;
:
先程のボタンが押されたら半透明の黒で塗りつぶし(drawBackgroud)、その後履歴の描画にとりかかる(drawAllHistorySelection)。

履歴の描画に加えて現在表示中の領域も表示するようにした。こうすると履歴がひとつもない場合でも最低は1つ表示されるので特別な処理(履歴表示を禁じるとかメッセージを出すとか)が不要になる。
- (void)drawAllHistorySelections
{
 NSLog(@"TODO: %s", __PRETTY_FUNCTION__);
 // [1] draw current selection
 [self drawHistorySelection:_rect];
 
 // [2] draw history selection
 for (NSValue* historyValue in
   [[SelectionHistory sharedSelectionHistory] array]) {
  
  [self drawHistorySelection:[historyValue rectValue]];
 }
}
仮描画のコード。白く塗りつぶすだけ。
- (void)drawHistorySelection:(NSRect)rect
{
 [[NSColor whiteColor] set];
 NSRectFill(rect);
}

さて実行してみよう。
出た。


履歴選択


白い部分をクリックした時にその履歴の領域(位置+サイズ)を採用するように修正する。まずはクリック判定用のメソッドを書く。
- (NSRect)rectOfhistorySelectionsAt:(NSPoint)cp
{
 NSRect rect = NSZeroRect;
 for (NSValue* historyValue in
   [[SelectionHistory sharedSelectionHistory] array]) {
  NSRect historyRect = [historyValue rectValue];
  if (NSPointInRect(cp, historyRect)) {
   rect = historyRect;
   break;
  }
 }
 return rect;
}
戻り値にクリックされた領域を返す。該当領域がない場合は NSZeroRect が返る。
これを使って mouseDown: を実装する。
- (void)mouseDown:(NSEvent *)theEvent
{
    :
   case STATE_HISTORY:
   historyRect = [self rectOfhistorySelectionsAt:cp];
   if (!NSIsEmptyRect(historyRect)) {
    [self setFrame:historyRect];
   }
   [self changeState:STATE_RUBBERBAND];
   return;
   // ** not reached **
    :
白い部分がクリックされた場合は現在の範囲選択の位置とサイズを変更した後、元の範囲選択画面へ戻る。それ以外をクリックした場合は何もせずに戻る。

位置とサイズの変更はアンドゥ対象となるため setFrame: メソッドを用意し、そこで必要な手当を行っておく。
-(void)setFrame:(NSRect)frame
{
 CaptureView* view = [_capture_controller view];
 NSUndoManager* undoManager = [view undoManager];
 [[undoManager prepareWithInvocationTarget:self]
  setRubberBandFrame:_rect];
 [undoManager setActionName:NSLocalizedString(@"UndoResizeSelection", @"")];
 [self setRubberBandFrame:frame];
}


実行結果


初期状態

履歴を表示し適当な領域を選択すると...

出た。アンドゥもちゃんと働く。って静止画じゃわかりずらいが..




考察


履歴の表示は重なったり小さくて見にくいケースが想定されるので工夫が必要。また(200x200のような)サイズ表示があるとよさそうだ。それと選択された時に点滅するとか何らかのアニメーションを起こしてユーザにフィードバックすることも必要だな。


- - - -
次回も引き続き表示。

SimpleCap - Selection History Expose [3] モデル実装

2010年10月28日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: SimpleCap - Selection History Expose [2] 仕様

まずモデルから実装する。


モデルクラス - SelectionHistory


前回定義した仕様でモデルに関係するところをピックアップすると次の通り。
  • 範囲選択でキャプチャ実行した時の位置と大きさを履歴として記録する
  • 同じ大きさが履歴にある場合は位置のみ更新する(常に履歴内のすべてのサイズは異なる)
  • 過去の履歴は最大10件までとする
  • 履歴は次回起動時にも利用できる
  • 履歴件数はプリファレンスで変更できる
  • 履歴はプリファレンスで削除できる

モデルはこのロジックを実装すればいい。

こんな感じで書いてみた。
インターフェイス
@interface SelectionHistory : NSObject {

 NSMutableArray* historyArray_;
}
+ (SelectionHistory*)sharedSelectionHistory;
- (void)addHistoryRect:(NSRect)frame;
- (NSArray*)array;
- (NSRect)rectAtIndex:(int)index;
- (void)clearAll;

@end

シングルトンとして実装し +shareSelectionHistory でインスタンスを取得する。
static SelectionHistory* selectionHistory_;

+ (SelectionHistory*)sharedSelectionHistory
{
 if (selectionHistory_ == nil) {
  selectionHistory_ = [[SelectionHistory alloc] init];
 }
 return selectionHistory_;
}

NSRect を受け取り追加する。
- (void)addHistoryRect:(NSRect)rect
{
 NSInteger index;
 for (index=0; index < [historyArray_ count]; index++) {
  NSValue* historyValue = [historyArray_ objectAtIndex:index];
  NSRect historyRect = [historyValue rectValue];
  if (NSEqualSizes(historyRect.size, rect.size)) {
   break;
  }
 }
 
 if (index < [historyArray_ count]) {
  // found it
  [historyArray_ removeObjectAtIndex:index];
 }
 
 [historyArray_ addObject:[NSValue valueWithRect:rect]];
 
 while ([historyArray_ count] >
  [[UserDefaults valueForKey:UDKEY_SELECTION_HISTORY_MAX] intValue]) {
  [historyArray_ removeObjectAtIndex:0];
 }

 [self save];
}
追加にあたっては履歴中に同じサイズのものがある場合には、それを削除し新たに追加しなおす。つまり同サイズは最近のものだけを残すようにする。また履歴の最大数を越えた場合は履歴の数を調整する。

追加の最後に NSUserDefaults へ保存する。
- (void)save
{
 NSMutableArray* rectArray = [NSMutableArray array];
 for (NSValue* rectValue in historyArray_) {
  NSString* rectString = NSStringFromRect([rectValue rectValue]);
  [rectArray addObject:rectString];
 }
 [UserDefaults setValue:rectArray forKey:UDKEY_SELECTION_HISTORY];
 [UserDefaults save];
}


キャプチャ時に履歴追加


モデルができたのでキャプチャ時に記録するようコードを追加した。
:
  case TAG_RECORD:
   [_capture_controller setContinouslyFlag:NO];
   [_capture_controller saveImage:[self capture] imageFrame:_rect];
   [[SelectionHistory sharedSelectionHistory] addHistoryRect:_rect];
   [_capture_controller exit];
   break;
    :


試しに実行して試してみよう。履歴の中身をデバッグコンソールへ出力した。
NSRect: {{155, 180}, {400, 250}}
NSRect: {{155, 180}, {298, 343}}
NSRect: {{776, 71}, {210, 128}}

よさそうだ。

- - - -
次回は表示。

SimpleCap - Selection History Expose [2] 仕様

2010年10月27日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Blogger: Cocoaの日々 - 投稿 "SimpleCap - Selection History Expose [2] 仕様" を編集

Selection History Expose開発について。


今回は仕様決め。


仕様


こんな感じ。
  • 範囲選択内のボタンから起動する(その他の起動も用意する。ダブルクリックなど)
  • 範囲選択でキャプチャ実行した時の位置と大きさを履歴として記録する
  • 同じ大きさが履歴にある場合は位置のみ更新する(常に履歴内のすべてのサイズは異なる)
  • 過去の履歴は最大10件までとする
  • 履歴は次回起動時にも利用できる
  • 履歴件数はプリファレンスで変更できる
  • 履歴はプリファレンスで削除できる
  • マルチスクリーンに対応する(後日...)
  • 表示する時には視覚効果(アニメーション)を使う

開発アプローチ


こんな感じで進めていく。

  1. モデル作成
  2. キャプチャ時追加処理
  3. 表示処理
  4. プリファレンス設定


次から実装に入る。

SimpleCap - Selection History Expose [1] アイディア

2010年10月26日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

Back to Mac
というわけで(?)久々に SimpleCap の開発を再開。

Selection History Expose


Selection History Expose(仮称〜範囲選択履歴エクスポゼ)、とは Mac OS X の Exposeよろしく過去に使った範囲を画面に表示するというもの。

こんなイメージ。
ブログの画像作成に範囲選択をよく使うのだが、同じ範囲を何度も使いまわすことが多い。範囲はプリセット設定できるし、Cmd+Z で Undo できるのだが使い勝手はあまりよくない。また以前から過去の履歴を使いたいとの要望を受けていたし、実際初期バージョンでは履歴機能を盛り込んだこともあった。ただ履歴を単純にリストで表示しても分かりづらく、使いやすさに欠けていることからリリースは見送ってきた。そうしてずいぶん長い間放っておいたのだが昨日「Expose風に過去の範囲を見せて、それをユーザに選択させたらどうだろう?」と突然思いついた。思いついたら居ても立ってもいられなくて?iPhone開発を中断してこれにとりかかることにした。思いつきが本当に使いやすいか?とにかく実装してみよう。

次回から設計に入る。
(続く)


[参考情報] 過去の検討など


(旧) Cocoaの日々: 範囲選択履歴(その1)
(旧) Cocoaの日々: 範囲選択履歴(その2)
(旧) Cocoaの日々: 範囲選択履歴(その3)
(旧) Cocoaの日々: 範囲選択履歴(その4)ポップアップメニュー
(旧) Cocoaの日々: 範囲選択履歴(その5)ポップアップメニューのハンドリング
(旧) Cocoaの日々: 範囲選択履歴(その6)モデル作成
(旧) Cocoaの日々: 範囲選択履歴(その7)履歴の追加
(旧) Cocoaの日々: 範囲選択履歴(その8)履歴の選択
(旧) Cocoaの日々: 範囲選択履歴(その9)履歴の保存

簡易スライドビューア [22] ナビゲーション再考

2010年10月25日月曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [21] 削除

大体の機能が揃った。ここで一旦ユーザインターフェイスを見直すことにする。


ショーケースモードと通常モードのハイブリッド


ショーケースモードとは前後の画像が横に表示される表示モードのこと。


途中から取り入れたアイディアで割と気に入っている(それが為に時間がかかってしまったが...)。画像をめくっていくときも1枚だけが表示される通常モードよりも感覚的でわかりやすい。ただ拡大縮小には向いていないのが気になっていた。
ショーケースモードで拡大するとこんな感じ。


一方,通常モードは標準の写真アプリと同じように1枚の画像だけを表示するモード。こちらは拡大縮小やスライドショーに向いている。ただページをめくるのは前後が見えないので、次の画像がめくってみないとわからない(別に困りはしないが)。この点はショーケースモードの方が向いている。※サムネイルを同時に表示するという解決策もある。



そこで両方のいいとこ取りをできないかとハイブリッドなインターフェイスを考えてみた。

最初はショーケースモードからスタートして、ダブルタップで両方のモードを行き来できるようにする。ピンチ(拡大縮小)やスライドショー開始の時には通常モードへ移行する。
難点はダブルタップが標準写真アプリでは拡大縮小操作になっているのに対して、今回の場合はモード切り替えになっているところ。この辺りは実機で触ってみて違和感がないか確認してみる。


実機確認


実装して実機で確認してみた。


初期表示


ダブルタップで通常モードへ

ピンチで拡大

ダブルタップでショーケースモードへ


悪くない。これでしばらく触ってみよう。


ソースコード


GitHubからどうぞ。
xcatsan's iOS-Sample-Code at 2010-10-25 - GitHub


- - - -
簡易スライドビューアは今回で一区切り。この後は公開に向けて他の作業と並行して少しづつ進める。

簡易スライドビューア [21] 削除

2010年10月24日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [20] ボタンで移動

今回は指定のページを削除するメソッドを追加した。


通常モード


削除を実行すると現在のページがフェードアウト、次ページの画像がフェードインするようにする。この場合例外が2つあって、ひとつは一番最後のページを表示している時。この時は前のページをフェードインさせる。もう一つの例外はページが1枚しか無いとき。この時は単純にフェードアウトするだけ(背景色=今回黒、が現れる)。

削除をすると Fadeトランジションがかかり次ページの画像が現れる。


ショーケースモード


通常モードと同じ Fadeトランジションでもいいのだが、隣のイメージが見えているのでスライドして間を詰めるような視覚効果にしてみた。実装としては削除時に一旦表示位置を右へずらしておき、UIViewのアニメーションを使って元の位置まで戻す(左<=右方向)。最後のページを表示している場合、アニメーションは左=>右方向。

こんなイメージ。




ソースコード


GitHub からどうぞ。
EasyGallery at 2010-10-24 from xcatsan's iOS-Sample-Code - GitHub

簡易スライドビューア [20] ボタンで移動

2010年10月23日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [19] 指定のページを表示する

今回は指ではなくボタンで画像をめくれるようにする。


サンプル実行


ツールバーに移動用のボタンを付けてそれで移動する。


実装


移動用の公開メソッドを用意した。
- (void)movePreviousPage;
- (void)moveNextPage;
- (void)movePreviousPageAnimated:(BOOL)animated;
- (void)moveNextPageAnimated:(BOOL)animated;

実装は既にあるメソッドの組み合わせで簡単にできた。
- (void)movePage_:(BOOL)animated
{
 CGSize size;
 if (self.showcaseModeEnabled) {
  size = self.scrollView.bounds.size;
  size.width -= spacing_.width;
 } else {
  size = self.bounds.size;
 }
 size.width += spacing_.width;
 
 passDidScroll_ = YES;
 [self.scrollView setContentOffset:CGPointMake(
  self.contentOffsetIndex*size.width, 0)
        animated:animated];
}

- (void)movePreviousPageAnimated:(BOOL)animated
{
 if (self.currentPage<= 0) {
  // do nothing
  return;
 }
 
 self.currentImageIndex--;
 self.contentOffsetIndex--;
 self.pageControl.currentPage--;
 [self setupPreviousImage];
 [self movePage_:animated];
}

- (void)movePreviousPage
{
 [self movePreviousPageAnimated:YES];
}

- (void)moveNextPageAnimated:(BOOL)animated
{
 if (self.currentPage >= [self numberOfImages]-1) {
  // do nothing
  return;
 }

 self.currentImageIndex++;
 self.contentOffsetIndex++;
 self.pageControl.currentPage++;
 [self setupNextImage];
 [self movePage_:animated];
}

- (void)moveNextPage
{
 [self moveNextPageAnimated:YES];
}


ソースコード


GitHubからどうぞ
xcatsan's iOS-Sample-Code at 2010-10-23 - GitHub

簡易スライドビューア [19] 指定のページを表示する

2010年10月22日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [18] スライドショー動作完了

指定のページを表示するメソッドを追加した。地味だがこういうのは重要。

実装


まずインターフェイスを用意する。ページのプロパティとアニメーション指定ができるメソッドの2つ。UIScrollView.contentOffset がこのスタイルを取っている。
@property (nonatomic, assign) NSInteger currentPage; // start with 0
- (void)setCurrentPage:(NSInteger)page animated:(BOOL)animated;

実装コードはこんな感じ。
- (void)setCurrentPage:(NSInteger)page animated:(BOOL)animated
{
 if (page == self.currentImageIndex) {
  return;
 }

 NSInteger numberOfViews = [self numberOfImages];

 if (page < 0) {
  page = 0;
 } else if (page >= numberOfViews) {
  page = numberOfViews - 1;
 }
 
 self.currentImageIndex = page;
 self.contentOffsetIndex = page;
 self.pageControl.currentPage = page;
 
 for (int index=0; index < kMaxOfScrollView; index++) {
  [self setImageAtIndex:self.currentImageIndex+index-kLengthFromCetner
     toScrollView:[self.innerScrollViews objectAtIndex:index]];
 }
 

 [self relayoutViewsAnimated:NO];
 [self layoutSubviewsWithSizeChecking:NO animated:animated];

}

- (void)setCurrentPage:(NSInteger)page
{
 [self setCurrentPage:page animated:YES];
}

- (NSInteger)currentPage
{
 return currentImageIndex_;
}
プロパティは自前実装とする。


サンプル実行


4ページ目を初期表示するようにしてみた。
self.galleryView.currentPage = 3;

初期状態

プロパティを設定するとスクロールのアニメーションが動き



目的のページで止まる。



ソースコード


GitHub からどうぞ。
EasyGallery at 2010-10-22 from xcatsan's iOS-Sample-Code - GitHub

簡易スライドビューア [18] スライドショー動作完了

2010年10月21日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [17] モードの切り替え

今回スライドショー動作を完成させる。

ショーケースモードでのスライドショー


基本動作はできているので後はショーケースモードでスライドショーを起動した場合の処理を入れるだけ。順番はこんな感じ。

0. ショーケースモードでスライドショー実行
1. 通常モードへ切り替え
2. スライドショー動作
3. 終了時にショーケースモードへ戻す

(コード割愛)


サンプル実行


まずショーケースモードへ変更する。

そしてスライドショー実行。通常モードへ移行する。

画面が移り変わる。

途中でタップするとスライドショーが停止し、ショーケースモードへ戻る。


ソースコード


GitHubからどうぞ。
EasyGallery at 2010-10-21 from xcatsan's iOS-Sample-Code - GitHub


再考


実はショーケースモードの状態でもスライドショーはきちんと動作する。これは後で気がついた。

こちらのスライドショー動作も味わい深い(?)。モードと動作を少し整理してデフォルトの動作をきちんと決めたほうがよさそうだ。その上でカスタマイズができるとベスト。


- - - -
スライドビューアはまだまだ続く。

簡易スライドビューア [17] モードの切り替え

2010年10月20日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [16] スライドショー(3) 停止その2

スライドショーはショーケースモード(左右に前後の画像が少し見えるヤツ)でも対応したい。一旦1枚表示モードに切り替えてからスライドショー実行し、終了時に元にもどるようなイメージ。

その為には両モードの切り替えが自由にできるようにならないといけない。今回はその切替。



リファクタリング


ビュー階層はこんな感じになっている。

これらのビューの位置やサイズは初期処理内だけで行っていた。
-(void)初期処理 {
  表示モードによる初期処理
  ベースのUIScrollViewの位置・サイズ設定処理
  画像を表示する XCGalleryInnerScrollView の生成と位置・サイズ設定処理
}

各ビューの位置とサイズ設定処理は、モード変更時にも発生するのでこれらをメソッドとして切り出す。
-(void)表示モード設定 {
  :
}
-(void)ベース UIScrollView位置・サイズ設定 {
  :
}
XCGalleryInnerScrollView位置・サイズ設定{
  :
}

-(void)初期処理() {
  [self 表示モード設定];
    :
  [self ベースUIScrollView位置・サイズ設定];
    :
  [self XCGalleryInnerScrollView位置・サイズ設定];
    :
}

一旦切り出せば再利用可能になるので、これをモード切替時に呼び出す。実際にはスクリーンのローテーション時にもレイアウト変更が発生するので使える。

(コードは割愛)


サンプル実行


静止画だとわかりずらいが、左下のボタンで両方のモードを行き来できる。
通常モード
ショーケースモード

なお切替時のレイアウト変更処理を +[UIView beginAnimations:context:] と +[UIView commitAnimations]で囲うことでアニメーションするようにしてある。
- (void)setShowcaseModeEnabled:(BOOL)enabled animated:(BOOL)animated
{
 showcaseModeEnabled_ = enabled;
 passDidScroll_ = YES;

 [UIView beginAnimations:nil context:nil];
 [self setupSpacingAndMargin];
 [self relayoutBaseScrollView];
 [self relayoutInnerScrollViews];
 [UIView commitAnimations];
 
}


scrollViewDidScroll:


UIScrollView の contentSize や contentOffset を変更した場合も UIScrollViewDelegate の -scrollViewDidScroll: が呼ばれる。前回まではこのメソッドは指でスクロールさせた時の処理が書いてあり、これらプロパティの変更に伴うスクロールは考慮していなかった。この為、モードを切り替えた時の contentSize/contentOffset 変更によっても -scrollViewDidScroll: が呼び出され意図しない動作が発生していた。例えば6ページ目を見ている状態でモードを切り替えると表示が7ページ目に移動してしまう。これはモードの切り替えにより contentSize/contentOffsetが再設定され、その結果 -scrollViewDidScroll:が呼ばれ、さらに一定のスクロール量が発生したとみなされるとこのメソッド内で次ページもしくは前ページへの移動処理が走ってしまうため。

プロパティ変更と指によるスクロールの判別の方法がわからなかったので、今回はメンバ変数 BOOL passDidScroll_ を用意し、プロパティ変更を行う場合はあらかじめ YES を設定するようにした。
:
 passDidScroll_ = YES;
 self.scrollView.contentSize = CGSizeMake(
  [self.delegate numberImagesInGallery:self]*newSizeWithSpace.width,
  newSize.height);

 passDidScroll_ = YES;
 self.scrollView.contentOffset = CGPointMake(
  self.contentOffsetIndex*newSizeWithSpace.width, 0);
                :
その上で、-scrollViewDidScroll: 内ではこのフラグが立っている場合は1回処理をスキップする処理を加えた。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
 if (passDidScroll_) {
  passDidScroll_ = NO;
  return;
 }
       :
ベタなやり方だがとりあえず問題は回避できた。


ソースコード


GitHubからどうぞ。
EasyGallery at 2010-10-20 from xcatsan's iOS-Sample-Code - GitHub

簡易スライドビューア [16] スライドショー(3) 停止その2

2010年10月19日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [15] スライドショー雛形(2) 調整

ピンチやフリックでスライドショーが停止しない。前回の実装ではイベントハンドリングが不足しているのでこれを直す。

#もはやタイトルが何のことだがわからないが...


イベント処理


対処すべきは、タップ、ピンチ(拡大縮小)、フリック(ページめくり)の3つ。前回は最初のタップしかハンドリングしていなかったので、スライドショ−実行中にピンチ、フリックしても止まらない。
ビュー階層


タップとピンチは一番上の UIScrollView が処理する。フリックは2番目のベースの UIScrollViewが処理。まず前回のコードを書き直してタップとピンチに対処する。
@class XCGalleryInnerScrollView;
@protocol XCGalleryInnerScrollViewDelegate

- (void)didTouched:(XCGalleryInnerScrollView*)innerScrollView;

@end
touchesBegan:withView: と UIScrollViewDelegate の scrollViewWillBeginZooming:withView: を実装する。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 [self.eventDelegate didTouched:self];
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
 [self.eventDelegate didTouched:self];
}



続いてフリックの対処。ベースの UISCrollViewのデリゲート先は XCGalleryViewになっているのでここでフリックを処理する。
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
 [self stopSlideShow];
}


これでスライドショーが停止するようになった。


ソースは GitHubからどうぞ。
EasyGallery at 2010-10-19 from xcatsan's iOS-Sample-Code - GitHub


(今回は以上)

簡易スライドビューア [15] スライドショー雛形(2) 調整

2010年10月18日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: 簡易スライドビューア [14] スライドショー雛形

前回からスライドショー(数秒単位で画像が切り替わる)の実装を始めたが、途中で止めたり、フリックした時の挙動がおかしかった。これを直していこう。


タッチで停止


標準の画像アプリのスライドショーのように画面をタッチしたら停止するようにする。

現在のビュー構成はこんな感じ。

タッチイベント(touchesBegan:withEvent:)は一番上の XCGalleryInnerScrollView が受け取るが、その下のビューへは自動的に伝搬されない。そこで自前のプロトコルを定義して、それを実装するデリゲートへイベントを転送してやる。
@class XCGalleryInnerScrollView;
@protocol XCGalleryInnerScrollViewDelegate

- (void)innerScrollView:(XCGalleryInnerScrollView*)innerScrollView
     touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;

@end

@interface XCGalleryInnerScrollView : UIScrollView  {

 UIImageView* imageView_;
 id  eventDelegate_;
}

@property (nonatomic, retain) UIImageView* imageView;
@property (nonatomic, assign) id  eventDelegate
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 [self.eventDelegate innerScrollView:self
         touchesBegan:touches
         withEvent:event];
}
eventDelegate には XCGalleryView を指定しておき、このメッセージが投げられたらスライドショー実行(タイマー)を停止する。
- (void)innerScrollView:(XCGalleryInnerScrollView*)innerScrollView
     touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 NSLog(@"touchesBegan2");
 [self stopSlideShow];
}


画像切替ロジックの変更


前回はベースとなる UIScrollView の位置を動かさず、表示中の XCGalleryInnerScrollView をその場でトランジションをかけて表示していた。この為、スライドショー実行を中断した後フリックしてページをめくると左右の画像がおかしなことになる。これを回避するためにスライドショー実行中はベースの UIScrollView をスクロールさせて正しい位置でトランジションをかける必要がある。

前回のトランジションイメージはこう。

これを次のように変える。ある時点で下記の状態になっていたとする。
タイマーが発火した時点で次の画像位置へベースの UIScrollViewをスクロールさせる(可視範囲が動く)。この直前に transitionView にひとつ前の画像を表示する(hidden=NO)。一方、本来表示されるべき n番目のビューは hidden=YESとして非表示にする。
最後に CATransitionを使い、表示・非表示をそれぞれのビューで逆転させる。
これでトランジションと共にスクロール位置も正しく設定されるので、スライドショーを停止してもその後の位置関係がおかしくならない。


ソースコード


GitHubからどうぞ。
EasyGallery at 2010-10-18 from xcatsan's iOS-Sample-Code - GitHub

NSLog

2010年10月17日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: __PRETTY_FUNCTION__

NSLog でどんな情報が表示できるか少し調べてみた。

まとめ


シンボル説明
__FILE__ファイル名/proj/classes/ViewController.m
__LINE__ソースコードの行番号364
__FUNCTION__メソッド名(関数名)-[ViewController viewDidLoad:]
__PRETTY_FUNCTION__クラス名とメソッド名-[ViewController viewDidLoad:]


__FILE__の例


コード
NSLog(@"__FILE__: %s", __FILE__);
結果
__FILE__: /Users/hashi/Development/
    IOS-Sample-Code/XcodeMacro/Classes/ViewController.m

__LINE__の例


コード
NSLog(@"__LINE__: %d", __LINE__);
結果
__LINE__: 44


__FUNCTION__の例


クラスメソッド内

コード
+ (void)ClassFooMethodWith:(NSString*)arg1 and:(NSString*)arg2
{
 NSLog(@"__FUNCTION__: %s", __FUNCTION__);
}
結果
__FUNCTION__: +[ViewController ClassFooMethodWith:and:]


インスタンスメソッド内

コード
- (void)InstanceFooMethodWith:(NSString*)arg1 and:(NSString*)arg2
{
 NSLog(@"__FUNCTION__: %s", __FUNCTION__);
}
結果
__FUNCTION__: -[ViewController InstanceFooMethodWith:and:]

関数内

コード
void Function(NSString* arg1)
{
 NSLog(@"__FUNCTION__: %s", __FUNCTION__);
}
結果
__FUNCTION__: Function


__PRETTY_FUNCTION__の例


クラスメソッド内

コード
+ (void)ClassFooMethodWith:(NSString*)arg1 and:(NSString*)arg2
{
 NSLog(@"__PRETTY_FUNCTION__: %s", __PRETTY_FUNCTION__);
}
結果
__PRETTY_FUNCTION__: +[ViewController ClassFooMethodWith:and:]

インスタンスメソッド内

コード
- (void)InstanceFooMethodWith:(NSString*)arg1 and:(NSString*)arg2
{
 NSLog(@"__PRETTY_FUNCTION__: %s", __PRETTY_FUNCTION__);
}
結果
__PRETTY_FUNCTION__: -[ViewController InstanceFooMethodWith:and:]

関数内

コード
void Function(NSString* arg1)
{
 NSLog(@"__PRETTY_FUNCTION__: %s", __PRETTY_FUNCTION__);
}
結果
__PRETTY_FUNCTION__: Function


参考情報


NSLog:NSLog
NSLogの日本語リファレンス。書式が詳しく解説されていて役立つ。

NSLog tips and tricks - Stack Overflow
NSLogの様々な Tips。面白い。

__PRETTY_FUNCTION__

2010年10月16日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

@griffin_stewie さんのツィートで、__PRETTY_FUNCTION__ を NSLog() で使うと、処理中のクラスとメソッド名を表示できることを知った。
こうすると
- (void)viewDidLoad {
    :
 NSLog(@"1: %s", __PRETTY_FUNCTION__);
    :
}
こう出力される。
1: -[ViewController viewDidLoad]
おーこれは便利だ。

さらに @hkato193 さんのツィートが気になっていたので確認してみた。

こんなコードを書いた。
- (void)viewDidLoad {
    [super viewDidLoad];

 NSLog(@"1: %s", __PRETTY_FUNCTION__);
 
 [UIView animateWithDuration:1.0
      animations:^{
       NSLog(@"2: %s", __PRETTY_FUNCTION__);
      }];
 
 [UIView animateWithDuration:1.0
      animations:^{
       NSLog(@"3: %s", __PRETTY_FUNCTION__);
      }];


 [UIView animateWithDuration:1.0
      animations:^{
       [UIView animateWithDuration:1.0
            animations:^{
             NSLog(@"4: %s", __PRETTY_FUNCTION__);
            }];
      }];
}

実行結果。
1: -[ViewController viewDidLoad]
 2: __-[ViewController viewDidLoad]_block_invoke_1
 3: __-[ViewController viewDidLoad]_block_invoke_2
 4: __-[ViewController viewDidLoad]_block_invoke_4

ほー。

ソース
xcatsan's iOS-Sample-Code at 2010-10-16 - GitHub



参考情報


Cocoaの日々: Xcode のマクロ定義

CoreLocation - [5] MKPlacemark

2010年10月15日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: CoreLocation - [4] 緯度経度から住所情報を得る - MKReverseGeocoder

今回は MKPlacemark を触ってみた。


MKPlacemark


MKPlacemarkのオブジェクト は MKReverseGeocoder から生成され、国や住所情報が格納されている。

MKPlacemark Class Reference より


実際に MKReverseGeocoder を使い任意の緯度経度から MKPlacemarkを取得した時の例
addressDictionary['CountryCode']: JP
addressDictionary['Street']: 14丁目 236−1
addressDictionary['SubThoroughfare']: 236−1
addressDictionary['SubLocality']: 日本橋
addressDictionary['City']: 中央区
addressDictionary['State']: 東京都
addressDictionary['Thoroughfare']: 14丁目
addressDictionary['Country']: 日本
addressDictionary['FormattedAddressLines'][0]: 日本
addressDictionary['FormattedAddressLines'][1]: 東京都中央区日本橋14丁目236−1
----
thoroughfare : 14丁目
subThoroughfare : 236−1
locality : 中央区
subLocality : 日本橋
administrativeArea : 東京都
subAdministrativeArea : (null)
postalCode : (null)
country : 日本
countryCode : JP


addressDictionary の各キーは Address Book framework - ABPerson クラスの "Address Property" で定義されている。
ABPerson Reference より



<MKAnnotaction>


MKPlacemark は MKAnnotationプロトコルを実装している。ということはそのまま MKMapView へ追加することができる。前回のコードで自前の MKAnnotation実装オブジェクトを使うのをやめて単純に取得した MKPlacemarkオブジェクトを MKMapViewへ addAnnotation したところ地図上にピンが表示され、タップで住所が表示されるようになった。
- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder didFindPlacemark:(MKPlacemark*)placemark {

 [self.mapView addAnnotation:placemark];

 /*
 self.label.text = placemark.title;
 [self logPlacemark:placemark];

 SimpleAnnotation* annotation =
  [self annotationForCoordinate:geocoder.coordinate];
 annotation.title = placemark.title;
  */
}
簡易な用途ならこれで十分。


ソースコード


GitHubからどうぞ。

CoreLocation - [4] 緯度経度から住所情報を得る - MKReverseGeocoder

2010年10月14日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: CoreLocation - [3] MKMapView の初期表示設定

今回は CLLocationManager から取得した緯度経度から住所情報を取得してみる。


MKReverseGeocoder


緯度経度から住所情報(文字列)を取得するには MKReverseGeocoder を使う。

使い方は簡単で緯度経度を引数としてインスタンスを生成し -start を投げるだけ。住所情報が取得できると MKReverseGeocoderDelegate で定義されたメソッドが呼び出される。
MKReverseGeocoder* reverseGeocoder = [[MKReverseGeocoder alloc] 
        initWithCoordinate:location.coordinate];
reverseGeocoder.delegate = self;
[reverseGeocoder start];
  :

- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder
 didFindPlacemark:(MKPlacemark*)placemark {
    // 住所情報取得が成功した場合
}  

- (void) reverseGeocoder:(MKReverseGeocoder *)geocoder
 didFailWithError:(NSError*) error {  
    // 住所情報取得が失敗した場合
}


サンプル実装


前回までのプログラムに組み込んでみる。現在位置を表示する時にその緯度経度から住所情報を取得し表示するようにした。またピン(MKAnnotation実装クラス)に住所情報をもたせ、ピンをタップした時に吹き出しで表示するようにしてみた。

まず住所取得箇所。前回のピン表示メソッドの最後に MKReverseGeocoder の処理を追加する。
- (void)setPinToCoordinate:(CLLocation*)location
{
 // add annotation
 SimpleAnnotation* annotation = [[[SimpleAnnotation alloc] init] autorelease];
 annotation.location = location;
 [self.mapView addAnnotation:annotation];

 [self setAnnotation:annotation
     forCoordinate:location.coordinate];  // ...(*1)

 // setup default span
 MKCoordinateSpan span;
 if (self.mapView.region.span.latitudeDelta > 100) {
  span = MKCoordinateSpanMake(0.005, 0.005);
 } else {
  span = self.mapView.region.span;
 }

 // set the map view to location
 CLLocationCoordinate2D centerCoordinate = location.coordinate;
 MKCoordinateRegion coordinateRegion =
  MKCoordinateRegionMake(centerCoordinate, span);
 [self.mapView setRegion:coordinateRegion animated:YES]; 
 
 MKReverseGeocoder* reverseGeocoder = [[MKReverseGeocoder alloc]
                initWithCoordinate:location.coordinate];
 reverseGeocoder.delegate = self;
 [reverseGeocoder start];
}
SimpleAnnotaion を緯度経度(CLLocationCoordinate2D)をキーにして辞書へとっておく(*1の箇所)。これは MKReverseGeocoder の住所取得が終わった時に住所情報を格納するため。住所取得処理は次の通り。
#pragma mark -
#pragma mark MKReverseGeocoderDelegate
- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder
 didFindPlacemark:(MKPlacemark*)placemark {
 self.label.text = placemark.title;

 SimpleAnnotation* annotation =
  [self annotationForCoordinate:geocoder.coordinate];
 annotation.title = placemark.title;

}  

- (void) reverseGeocoder:(MKReverseGeocoder *)geocoder
 didFailWithError:(NSError*) error {  

 self.label.text = [error description];

 SimpleAnnotation* annotation =
 [self annotationForCoordinate:geocoder.coordinate];
 annotation.title = [error description];
}
成功(あるいは失敗)したら取っておいた SimpleAnnotation を coodinate(緯度経度)をキーにして取り出し、titleへ住所情報を格納している。
SimpleAnnotation には titleプロパティを追加してある。
@interface SimpleAnnotation : NSObject {

 CLLocation* location_;
 NSString* title_;
}
@property (nonatomic, copy) CLLocation* location;
@property (nonatomic, retain) NSString* title;

@end
SimpleAnnotation の管理は NSMutableDictionaryで行う。出し入れのメソッドは次の通り。
#pragma mark -
#pragma mark Management for annotationDictionary
- (void)setAnnotation:(SimpleAnnotation*)annotation
 forCoordinate:(CLLocationCoordinate2D)coordinate
{
 NSValue* coordinateValue = [NSValue value:&coordinate
         withObjCType:@encode(CLLocationCoordinate2D)];
 [self.annotationDictionary setObject:annotation
          forKey:coordinateValue];
}

- (SimpleAnnotation*)annotationForCoordinate:(CLLocationCoordinate2D)coordinate
{
 NSValue* coordinateValue = [NSValue value:&coordinate
         withObjCType:@encode(CLLocationCoordinate2D)];
 SimpleAnnotation* annotation = [self.annotationDictionary objectForKey:coordinateValue];
 [self.annotationDictionary removeObjectForKey:coordinateValue];
 
 return annotation;
}
辞書のキーとなる CLLocationCoordinate2D はCの構造体はそのままでは入れられないので一旦 NSValue へ詰め替えてから使用している。


サンプル実行


ピンをタップすると住所情報が表示される。

ソースコード


GitHub からどうぞ。
CoreLocationSample at 2010-10-14 from xcatsan's iOS-Sample-Code - GitHub


備考


MKReverseGeocoder は GoogleのAPIを利用しているらしいが、取得に失敗することが多いらしく評判があまりよくない。

琴線探査: MKReverseGeocoderは大きい道路上を指定すると常にエラーか?

MKReverseGeocoder not working? « Welcome to Mobile World !!!

PBRequesterErrorDomain errors with reverse geocoding - iPhone Dev SDK Forum

MKReverseGeocoderは使わない方がいいみたい « wigglin’ bloggin’

上記ブログより抜粋。
良い時で1割くらい、悪いときは百発百中でこのエラーが発生します。
実際、少し試しただけでも数回に1回ぐらいの頻度で取得に失敗した。あるケースでは郵便番号だけが帰ってきたこともあった。
GoogleMapsのREST APIを使ったらうまくいったとの報告があったので、同じ事を試してみたらアッサリと解決したのでした。


実際サンプル作成中にも下記のエラーが発生した。
/SourceCache/GoogleMobileMaps_Sim/GoogleMobileMaps-257/
  googlenav/mac/Loader.mm:231 server returned error: 503


このあたり本格的な利用に際しては GoogleMapsのREST APIの利用も検討した方が良いかもしれない。あるいは何回かリトライしてみるとか?


参考情報


MKReverseGeocoder Class Reference
リファレンス

[iPhone] MapKit でリバースジオコーディング、緯度経度から住所を取得 | Sun Limited Mt.
MKReverseGeocoder の使い方で参考になった。

NSArrayにNSValueでC構造体を格納する - stoikheiaの日記
CLLocationCoordinate2D を NSMutableDictionary へ格納するのに参考になった。

琴線探査: MKReverseGeocoderでより精度の高い住所情報はMKPlacemark.addressDictionaryにあった!

人気の投稿(過去 30日間)