Recently in Programming Category

iPhone OSにおいて、アプリケーション間でデータを共有する、最も簡単な方法は、ペーストボードを使用することです。

典型的なパターンをあげてみましょう。
ユーザがメモアプリケーションで文字列を選んで「コピー」の操作をし、メール送信画面で「ペースト」操作を行うと、その文字列がメールに貼り付けられます。これは、コピー操作で、メモアプリケーションが文字列をペーストボードに格納し、メールアプリケーションが、ペースト操作の時にそこから文字列を取り出すことで実現されています。それと同じ方法で、どんなアプリケーションでも、ペーストボードを介してデータを受け渡すことが可能です。

ペーストボードには、文字列だけでなく、さまざまな種類のデータを格納することができます。例えば、弊社のExport for Google Documentsというアプリケーションでは、Googleドキュメントからエクスポートしたドキュメントデータを、ペーストボードに格納する機能を持っています。

ここでは、どのような形でペーストボードにデータを格納すればいいのか及び、弊社製Copy & Sendアプリでそのデータを活用する例について簡単に説明したいと思います。

UIPasteboardクラス

iPhone SDKの中では、UIPasteboardクラスがペーストボードを表現するオブジェクトになります。システム内には、複数のペーストボードが存在していますが、それぞれが、UIPasteboardのインスタンスに対応します。

システム標準で用意されているものには、以下の2種類があります。
  • General pasteboard
    • システム標準のペーストボードであり、通常のコピー/ペースト操作で使用されます。
  • Find pasteboard
    • 検索バーに入力されたテキストが保存されるペーストボード

上記以外に、各アプリケーションで固有なものを、自由に作成することができます。標準のペーストボードは、予期しない場面で勝手に上書きされてしまうことがあるので、アプリケーション固有のデータは、固有のペーストボードを使用することをお奨めします。

また、各ペーストボードには、一意に識別するための名称が付与されており、名称が不明なペーストボードにはアクセスできないようになっています。

ペーストボード内のデータ構造

ペーストボード内のデータは、アイテムと呼ばれるNSDictionryの集合として管理されています。
アイテムのNSDictionryのキーは、データの種類を表すUTI(Uniform Type Identifier)と呼ばれる文字列であり、値としてデータのオブジェクトが格納されています。したがって、アイテムの中には、異なる種類のデータを複数格納することができます。
以下に、データ構造の概念図を示します。

Pasteboard_items.png

これは、少し複雑な組合せの例になっています。
1つ目のアイテムは、URLとテキストのペアで構成されています。例えば、Safariでハイパーリンクを長押ししてコピーした場合は、この形になります。
2つ目のアイテムは、Excelのスプレッドシートを、単独で格納したアイテムの例です。
3つ目のアイテムは、JPEG画像とテキストのペアで構成されています。カメラロールの写真をコピーした場合には、この形になります。

Copy & Send及び、Copy & Send Liteで、ペーストボードの内容をブラウズすると、これらのデータ構造を確認することができます。興味がある方は、お試しください。

UIPasteboardのAPIについて

アイテムを操作するためのAPIは、以下の種類に分かれます。
(詳細は、UIPasteboardのリファレンスを参照してください)

  • 標準的な単一タイプのデータを扱うためのもの
    • データにアクセスするためのプロパティとして定義されています。
      • string/strings
      • image/images
      • URL/URLs
      • color/colors
  • 任意のデータタイプを扱うためのもの
    • 上記以外に、UTIを明示的に指定してデータを操作するメソッドが用意されています。

ペーストボードの活用例

一部、冒頭で述べたことの繰り返しになりますが、弊社製の以下のアプリ間で、ペーストボードを使ったデータ連携を実現しています。
  • Export for Google Documents
    • Googleドキュメントからエクスポートしたドキュメントデータを、ペーストボードに格納する。
  • Copy & Send / Copy & Send Lite
    • ペーストボードに格納されているアイテムを活用する機能を実装している。
  • 他のデバイスへの送信
  • 永続領域への保存
  • 内容のブラウズ
これによって、Export for Google Documentsのドキュメントデータを、他のデバイスに送信するといったことができるようになっています。


みなさんが作成したアプリケーションでも、Copy & Send / Copy & Send Liteを有効に活用していただければ幸いです。

開発者の方のために、具体的な連携機能の実装方法について、アプリの中からアクセスできるヘルプドキュメントにて説明しています。よろしければ、そちらをご参照いただければと思います。

よろしくお願いいたします。



Copy & Sendでは、データの送受信に、iPhone OS 3.0から追加されたGameKitを使用しています。当初はBluetoothのみのサポートだったのですが、iPhone OS 3.1からは、WiFi経由での通信もできるようになっていることは、あまり認知されていないように思います。われわれも、Copy & Sendアプリケーションを開発している過程で、偶然気付いたというのが正直なところです。
便利な機能なのですが、いくつか制約もあるので、そのときの経験を元に、概要をまとめておきたいと思います。

開発者向けのドキュメントには明記されていない

Appleから公開されているGameKitに関するドキュメントは、以下の2つですが、いずれにも、WiFi機能のサポートについては説明されていません。


Developer Forums の発言によると、これらのドキュメントは、いずれも3.0レベルのまま更新されていないので、3.1から追加されたWiFi通信機能のことが記載されていないだけのようです。

GKPeerPickerControllerを使った場合は、Bluetoothのみ使用可能である

なぜか、GKPeerPickerControllerクラスを使って接続を確立する場合は、Bluetooth通信のみが使用可能なようです。
WiFi通信を使うためには、GKSessionを使って、自分で接続相手を探して接続する必要があります。

BluetoothとWiFiのどちらを使用するのかを明示的に選択することはできない

WiFiとBluetoothの両方が有効な状態の場合、どちらを使って通信するのかということを、明示的に選択することはできません。Game Kit Frameworkが、勝手に選んでしまいます。GKPeerPickerControllerを使わないで実装した場合は、暗黙的にWiFiを使ってしまうわけですね。
裏を返せば、ドキュメントに記載されていないこととあわせて考えると、GKPeerPickerControllerを使わないで実装した場合にのみ、偶然この機能に気付くということにもなります。

WiFiとBluetoothの状態を検知できない

GKPeerPickerControllerを使う場合、Bluetoothが無効な場合に、以下のようなalertメッセージが出ます。

Bluetooth_unavail.png

GKPeerPickerControllerを使わない場合は、WiFiとBluetoothの状態を検知することができません。

Bluetoothをサポートしていないデバイスでも通信できる

同一WiFiネットワーク内に限定されますが、iPod touch 1Gでも、通信することができるのは、メリットかなと思います。
あと、iPhone Simulatorとも通信できるで、iPhoneの実機が1台しかなくても、テストが可能になることもうれしいですね。

Copy & Sendアプリケーションへの影響

Copy & Sendでは、GKPeerPickerControllerを使わないで実装しているので、以下のような影響を受けています。

  • そのときの状況により、WiFiとBluetoothのどちらで通信されるのかを予測することはできません。
  • WiFiとBluetoothの両方が無効な状態であっても、そのことをユーザに通知することができません。

このあたりについては、ユーザのみなさんに不便を感じさせてしまうところだと思うので、もう少しきめ細かい制御ができるようになると、ありがたいところです。

Copy & Sendを使用して場合は、お手数ですが、WiFiとBluetoothを予めONにしていただくよう、お願いいたします。






iPhoneOSでは、各アプリケーションごとに、独自のURLスキームを定義できることは、iPhoneアプリケーション開発者のみなさんはご存知のことと思います。そのURLが、-[UIApplication openURL:]メソッドの引数で渡されると、対応するアプリケーションが起動されます。
起動される側のアプリケーションは、Application DelegateクラスでそのURLを受け取って、必要な処理を行います。
iPhoneOS 2.xまでは、URLのハンドリングは、application:handleOpenURL: メソッドでのみ行うことが可能でしたが。3.0からは、新しく追加された、application:didFinishLaunchingWithOptions: メソッドでも可能となりました。
このapplication:didFinishLaunchingWithOptions: メソッドのことを簡単にまとめると、以下のような感じになります。(詳細は、UIApplicationDelegate Protocol Reference を参照してください)
  • これまでの、applicationDidFinishLaunching: メソッドに、起動時のオプションパラメタを処理する機能が追加されたものである。
  • application:didFinishLaunchingWithOptions: メソッドが存在していると、applicationDidFinishLaunching: メソッドと、application:handleOpenURL: メソッドは、呼ばれなくなる。
  • 引数で渡される起動オプションは、NSDictionaryオブジェクトであり、push notificationまたは、URL起動時のパラメタが格納されている。
ということで、applicationDidFinishLaunching: メソッドと、application:handleOpenURL: メソッドを合成したようなものであると言えます。

URL起動の場合、起動オプションには、以下の2つのキーが存在します。
  • UIApplicationLaunchOptionsURLKey - 起動URLオブジェクト(NSURL)
  • UIApplicationLaunchOptionsSourceApplicationKey - 起動元アプリケーションのバンドル名
ふつうに、ホーム画面のアイコンをタップして起動された場合は、起動オプションはnilになります。

Location Clip 1.2 では、カメラで撮った写真をカメラロールに保存する機能がありますが、今日は、この機能の実装について取り上げたいと思います。

皆さんご存知の通り、写真をカメラロールに保存する際に利用するAPIは・・・

ですが、以下のような実装だけで終わらせている(保存完了のコールバックを設定していない)サンプルを実によく見かけます。

このような実装は、一見簡単でいいように見えますが、もし、このように実装してしまった場合、写真を保存し終わったことをアプリケーションが知る術がなくなってしまい、ちゃんと保存できたのかどうかを、アプリケーションで判断できなくなってしまいます。
処理が正常に終わったかどうかを確認するために、ちゃんとcompletionTargetとcompletionSelectorを設定するようにしましょう。

そしてまた、保存処理中にアプリケーションを終了してしまったりすると、カメラロールに写真が保存できなかったり、サムネイルが保存されなかったりすることがあるため、 UIActivityIndicatorView などを使って保存処理中であることをユーザに対して示すような配慮をしてあげると良いと思います。

影付き画像(UIImage)を作成する。

| 1 Comment | 0 TrackBacks
Location Clip 1.2(現在審査中)では、写真を地図上に貼付ける機能が追加されますが、今日は、この機能の中から、地図上に貼付ける画像に影を付ける部分の実装について取り上げたいと思います。
locationclip1_2.jpg

ポイントは以下の通りです。
- 作成する画像のサイズは、写真のサイズに影のサイズを加算したサイズにする。
- 影を付けるには、CGContextSetShadow(または、CGContextSetShadowWithColor)を使う。
- CGContextSetShadowで、グラフィックスコンテキストに対して、影を有効にした後、画像を描く。

以下に、実装例を示します。
以下の実装例では、写真部分を 320 x 240px、影部分を 12px で出力するものとしています。




MKMapViewの地図のタイプを変更する。

| 1 Comment | 0 TrackBacks
MKMapViewでは、地図のタイプとして、以下の3種類を選択することができます。
  • マップ
  • 航空写真
  • 地図+写真

今日は、この地図タイプの切り替えについてのトピックです。

ポイントは、以下の通りです。
  • 地図のタイプを選択するセグメントコントロールを用意する。
  • UIControlEventValueChangedイベント(セグメントコントロールの値が変わったタイミングで発行されるイベント)を処理するためのメソッドを定義する。
  • セグメントコントロールの値が変わったタイミングで、地図のタイプを変更する。
  • 地図のタイプの設定は、MKMapViewのmapTypeプロパティの値を設定することで行う。

以下に、実装例を示します。

-- MyViewController.h --

-- MyViewController.m --


簡単でいいですね。
MapKit、とても良く出来ています。

MKMapView上にアノテーションを追加すると、当然、ドラッグして動かしたくなりますよね。
今日は、その時に、アノテーションを "ぽよん" と浮かせる方法についての記事です。

ポイントは、以下の通りです。
- MKPinAnnotationViewのサブクラスで、タッチイベントを処理する。
- touchesBegan:withEvent: が呼ばれたら、MKPinAnnotationViewのimageをピンが浮いているように見える画像と入れ替える。

えっ、それだけ?という方もいらっしゃるかもしれませんが、はい、これだけです。

以下に、実装例を示します。

-- MyPinAnnotationView.h --

-- MyPinAnnotationView.m --


注)
なお、この方法は、Location Clipでは、そうしているというだけで、正しいやり方かどうかはわかりません。ご了承ください。

Export for Google Documentsでは、Google Documentsからダウンロードしたドキュメントファイルを、プレビューする機能を備えています。それは、アプリケーションで複雑なレンダリング処理を実装しているわけではなく、iPhone SDK標準のUIWebViewというコンポーネントを利用して実現しています。
UIWebViewは、その名の通り、Webサイトのコンテンツを表示するためのコンポーネントですが、HTML以外の種類のドキュメントにも対応しており、MS OfficeのドキュメントやPDFの内容を表示することができます。
Export for Google Documentsが、MS Officeの形式に変換したドキュメントを表示している処理は、実は、データをUIWebViewに渡しているだけの簡単な実装で実現されています。

詳細は、以下のドキュメントを参照してください。

Technical Q&A QA1630: Using UIWebView to display select document types

前回は、実際にGoogle Documentsサービスにアクセスして、
  • ドキュメントの一覧をGDataFeedDocListオブジェクトとして取得する
  • さらに、その中に含まれる個々のドキュメントエントリデータの属性情報(GDataEntryDocBaseオブジェクト)を取り出す
といった方法について、説明しました。今回は、そこから一歩進んで、GDataEntryDocBaseオブジェクトの属性情報を使って、目的のドキュメントをダウンロードする方法について説明したいと思います。

GDataEntryDocBase(エントリデータ)オブジェクト

GDataEntryDocBaseは、抽象スーパクラスであり、ドキュメントの種類ごとに、具象クラスが定義されています。
ドキュメントの種類具象クラス名
ドキュメント(文書)GDataEntryStandardDoc
スプレッドシート GDataEntrySpreadsheetDoc
プレゼンテーションGDataEntryPresentationDoc
PDF
GDataEntryPDFDoc
フォルダ
GDataEntryFolderDoc

各具象クラスでは、スプレッドシートの特殊な操作以外には、インタフェースの拡張はされていないので、通常は、GDataEntryDocBaseとして使用します。

ドキュメントの種類を判定する方法

ドキュメントの種類を判定したい場合は、各エントリデータオブジェクトのカテゴリ情報を使用します。
ドキュメント種類は、GDataDocConstants.hに定数定義されています。
_EXTERN NSString* const kGDataCategoryFolderDoc         _INITIALIZE_AS(@"http://schemas.google.com/docs/2007#folder");
_EXTERN NSString* const kGDataCategoryPDFDoc            _INITIALIZE_AS(@"http://schemas.google.com/docs/2007#pdf");
_EXTERN NSString* const kGDataCategoryPresentationDoc   _INITIALIZE_AS(@"http://schemas.google.com/docs/2007#presentation");
_EXTERN NSString* const kGDataCategorySpreadsheetDoc    _INITIALIZE_AS(@"http://schemas.google.com/docs/2007#spreadsheet");
_EXTERN NSString* const kGDataCategoryStandardDoc       _INITIALIZE_AS(@"http://schemas.google.com/docs/2007#document");

以下に、判定処理の例を示します。
    GDataEntryDocBase *document; // ドキュメントエントリデータ
    NSString *docKindTerm = @"unknown";

    // カテゴリ情報の配列の先頭に、ドキュメント種類が設定されている。
    NSArray *categories = [GDataCategory categoriesWithScheme:kGDataCategoryScheme fromCategories:[document categories]];
    if ([categories count] >= 1) {
        docKindTerm = [[categories objectAtIndex:0] term];

    }
    if([kGDataCategoryFolderDoc isEqualToString:docKindTerm]) {
        // フォルダに固有な処理

    } else if([kGDataCategorySpreadsheetDoc isEqualToString:docKindTerm]) {
        // スプレッドシートに固有な処理
        
    } else {
       // 上記以外のドキュメントに対する処理
        
    }


ドキュメントデータのダウンロード

ドキュメントエントリデータには、それを一意に識別するためのresourceIDが付与されています。ダウンロードは、HTTP GETリクエストにて行われますが、そのときに指定するURLの中に、resourceIDを含めることによって、目的のドキュメントが識別されます。
尚、このURLは、ドキュメントの一覧を取得時に、GDataEntryDocBaseオブジェクトに設定されるため、アプリケーションでは意識する必要はありません。
以下に、同期型の処理で、ドキュメントをダウンロードする例を示します。

    GDataEntryDocBase *targetDoc = ...; // ドキュメントエントリデータ
 
    // ドキュメントのタイトルを、保存するファイル名として利用
    NSString *fileName = [[targetDoc title] stringValue];
    NSString *savePath = [NSTemporaryDirectory() stringByAppendingString:fileName];

    // ドキュメントデータのURIを取得
    NSString *sourceURI = [[targetDoc content] sourceURI];
    NSURL *url = [NSURL URLWithString:sourceURI];
    if (url) {
        // 同期型でダウンロードする場合の例です。
        // iPhoneSDK標準のHTTP通信のAPIを使用します。
       
        // サービスオブジェクトから、リクエストを生成する。
        GDataServiceGoogle *service = ...];
        NSURLRequest *request = [service requestForURL:url
                                                  ETag:nil
                                            httpMethod:nil];
        NSURLResponse *response = nil;
        NSError *error = nil;
        // NSURLConnectionの同期通信リクエストを実行する。
        NSData *data = [NSURLConnection sendSynchronousRequest:request
                                             returningResponse:&response
                                                         error:&error];

        if (error != nil) {
            // ダウンロード処理のエラーハンドリング例
            NSLog(@"Error retrieving file: %@", error);
            UIAlertView *alertView = [[UIAlertView alloc]
                                      initWithTitle:[error localizedDescription] message:[[error userInfo] description] delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alertView show];
            [alertView release];
           
        } else {
            // ダウンロードしたファイルの保存
            BOOL didWrite = [data writeToFile:savePath
                                      options:NSAtomicWrite
                                        error:&error];
            if (!didWrite) {
                // ファイル保存処理のエラーハンドリング例
                NSLog(@"Error saving file: %@", error);
                UIAlertView *alertView = [[UIAlertView alloc]
                                          initWithTitle:[error localizedDescription] message:[[error userInfo] description] delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
                [alertView show];
                [alertView release];
           }
        }
    }

これで、目的のドキュメントをダウンロードして、ローカルのファイルとして保存することができますが、デフォルトでは、PDF以外のドキュメントは、HTMLでエクスポートされます。そのままでは扱いにくいので、Export for Google Documentsアプリケーションでは、MS Officeのドキュメントフォーマットでエクスポートするオプションを使用しています。そのためには、ドキュメントのURLに、エクスポートするフォーマットを指定するパラメタ(exportFormat)を追加します。

詳細は、以下のドキュメントを参照してください。
Developer's Guide: The Protocol - Google Documents List Data API v3.0 - Google Code

ここまで、4回に分けて、Google Documentsサービスからドキュメントデータをダウンロードするプログラムを書くための、基本的な手順を説明してきました。Google Documentsを利用するアプリケーションを作る時にご参考になれば幸いです。

すべてをまとめて読みたい場合は、こちらにアクセスしてください。





MapKitが提供するMKAnnotationやMKPinAnnotationViewを使って、MKMapView上にアノテーションを追加することができます。

ポイントは、以下の通りです。
  • MKAnnotationプロトコルを実装したインタフェースを定義する。
  • MKPinAnnotationViewを継承したインタフェースを実装する。
  • MKMapViewDelegate の mapView:viewForAnnotation: で、MKPinAnnotationViewを継承したインスタンスを生成して返却する。
  • MKAnnotationプロトコルを実装したインスタンスを生成して、MKMapView上に追加する。

以下に、実装例を示します。

-- MyAnnotation.h --

-- MyAnnotation.m --
 
-- MyPinAnnotationView.h --

-- MyPinAnnotationView.m --
 
-- MyViewController.h --

-- MyViewController.m --

iPhone Applications

Twitter