Archive for the ‘Dev’ Category

Picasaを使う(iPhone)

2011年9月16日

今度は iPhone アプリで Picasa Web Album を使うサンプルを作成した。(ソース

Google data API のObjective-C 版は以下の場所に紹介してある。
http://code.google.com/p/gdata-objectivec-client/

ソースをダウンロードするとサンプルコードも一緒についてくるが、iOS 用のサンプルは、OAuth のサンプルしかない。Picasa Web Album にアクセスするサンプルはOS X用のサンプル(GooglePhotoSample)がある。

OS X 用のサンプルプログラム

これを参考にiOS用に書き換えるしかなさそうだ。

プロジェクトの作成

今回は単純にするために、Window based application で作成した。Window は TextView だけで構成され、取得したデータをこの TextViewに表示するだけの簡単なものにする。

プロジェクトの設定

まずは、Google Data API をリンクするための設定。Google Data API ライブラリのプロジェクトは GData.xcodeproj。この中に、ライブラリはOSX用のGDataFrameworkとiOS用のGDtataTouchStaticLibがある。GooglePhotoSampleのプロジェクトには、このライブラリのプロジェクト( GData.xcodeproj)自体が取り込まれている。これに倣って、GData.xcodeprojをプロジェクトに追加する。

GData.xcodeprojを追加

Target Dependencies

GDataFramework の代わりに GDtataTouchStaticLib を設定。

OS X 版はGDataFramework

iOS版はGDataTouchStaticLibにする

Link Binary With Libraries

GDataFramework の代わりに GDtataTouchStaticLib を設定。
さらに、libxml2 も必要だった。

OS X版はGDataFramework

iOS版はlibGDataTouchStaticLibとlibxml2が必要

Header Search Path

OSXでは Framework Search Path を指定する。Framework にヘッダファイルも含まれるためだろう。

iOSでは、実際のヘッダファイルの場所を指定。API の Source フォルダの下(Recursiveに指定)と、libxml2 のフォルダを指定する。なお、Source フォルダの場所を指定するために、GDATA_SRC というユーザー定義を作成した。

OS X

iOS

ヘッダファイルのimport

OSX版のサンプルでは、

#import "GooglePhotosSampleWindowController.h"
#import "GData/GDataServiceGooglePhotos.h"
#import "GData/GDataEntryPhotoAlbum.h"
#import "GData/GDataEntryPhoto.h"
#import "GData/GDataFeedPhoto.h"

このように GData/ をつけてヘッダファイルを指定していた。Sourceの下にある実際のヘッダファイルは GData というフォルダの下にはないが、このような書き方が可能なのは、デッダファイルはFrameworkに含まれているからだと思われる。

iOS版ではこのような書き方はできないが、Header Search Path に実際のヘッダファイルの場所を再帰的に検索するよう指定したので、フォルダの階層は意識せず、ただヘッダファイル名だけを指定すればよい。

処理

OSX版のサンプルでは、ユーザー名とパスワードを入力してボタンを押すと、アルバムの一覧を表示し、選択されたアルバムの情報と写真の一覧を表示し、画像のアップロードなども可能だが、今回はまず、ただAPIが使えることを確かめたかっただけなので、できるだけシンプルに、だたアルバムの情報を取得して表示するだけにした。OSX版のソースから、アルバムの情報を取得する処理だけを取り出してコピーした。ユーザー名とパスワードはソース内に直書き(なので、事項する際は自分のアカウントで書き換えてください)、アルバムの一覧を取得して、リストの先頭のアルバムの情報だけを表示する。

実行画面

最初の行はアルバムのリストで、この例では1つ(DropBox)だけ。このDropBoxの情報を下に表示している。

起きろ!…起きろってば!(深い眠りに入ったアプリを叩き起こす)

2011年8月27日

カメラに、タイマーとSMS着信で撮影する機能をつけてみた。

SMS着信からの撮影は問題なかったが、意外にも、タイマーでの撮影がうまくかない。deep sleep に入ったときに反応しなくなってしまう。

まずは、セルフタイマーカメラで使用したCountDownTimerを使用してみた。Debugしているとき、つまりUSBでPCと接続しているときはほぼ正常に動いていたが、PCから離して単体で動かすと、長い時間動かしているうちにタイマーが反応しなくなってしまう。イベントはキューにたまってるようで、デバイスに触ったり、なにかのタイミングで急に動き出すが、たまっていた分を一度に実行しようとしてプログラムは落ちてしまう。

なにか別の手段を考えないといけない。deep sleep で検索して、まずは以下を参照。

http://developer.android.com/reference/android/os/SystemClock.html

ACTION_TIME_TICK を listen する方法かあるようだ。Receiver を使うので SMS の受信の仕組みと同じ。SMS の受信はスリープ時でも動いているので、これなら大丈夫かと思ったが、試してみたらだめだった。SMS の Receiver と同じように PackageManager.setComponentEnabledSetting() で Receiver を DONT_KILL_APP に設定してもだめだった。

スリープから覚めた途端にキューにたまった分を実行し始めるので。もしかするとタイマーは動いているが、その先の処理がとまっているだけかもしれない、と考え、タイマー処理で PowerManager で WakeLock してみる。しかしだめだった。起動から終了まで PowerManager で WakeLock をかけたままにする方法もあるが、これでは、スリープに入るのを防ぐことはできるが、これではバッテリーが持たないろう。タイマーで動作させる意味が無い。

もう一度 SystemClock の説明を読み返してみると、AlermManager なら deep sleep でも動作すると書いてある。なるほど、確かにアラームはスリープ状態でも決まった時間に動作する。これならきっと大丈夫だ。

        AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this, <起動させたいComponentのClass>);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + <間隔(ミリ秒)>, <間隔(ミリ秒)>, pi);

AlarmManager.setInexactRepeating() の2番目の引数は、最初に起動する時刻を指定するが、最初の引数で指定した方法で指定しなければならない。この例だと elapsedRealtime を使って指定する。 Time.toMills() で得られる値とは異なる。また、あとでキャンセルするためには、指定した PendingIntent の参照が必要。

この方法で、deep sleep に入ったときでも起動できるようになった。

これで、タイマーまたはSMSを受信したタイミングで、カメラで画像を撮影し、Picasa へのアップロードを行うアプリができた。(→ダウンロード

Picasaに画像をアップする(Android)

2011年8月21日

Android から Picasa に画像をアップロードするサンプルがあった。

http://gdata-java-client.googlecode.com/svn/tags/2.2.1-alpha/sample/picasa/picasa-atom-android-sample/instructions.html

ビルドして実行みたが、認証画面らしきものが出てくるが、先に進めない。コードを見てみると、認証方式が OAuth に指定されていた。これを ACCOUNT_MANAGER に変更すると、動いた!

アプリ上での操作は、アルバムの作成、削除、名前の編集くらいしかできないが、画像の送信機能も実装されている。別の画像閲覧アプリ(Albumなど)で画像を共有する機能を選択すると、選択肢の中にこのアプリが追加されていて、これを使って Picasa に画像を転送できる。なお、事前に Android の Account Manager にGoogleアカウントが登録されている必要がある。

このサンプルを参考に、アプリを作成してみた(ソースはこちら)。以前作った、Intent でカメラを起動させるアプリにこの送信機能を追加する。

まず、com.google.api.data.sample.picasa.model のパッケージをサンプルプロジェクトからそのままコピーする。

別途 gdata-android-2.2.1-alpha.zip をダウンロードし、展開。(サンプルから持ってきてもいいはず)

プロジェクトには、上のZIP を展開した中にあるJARをExternal Jarとして追加。

サンプルから Picasa に関するコードだけをコピーして PicasaTool というクラスを作成。Account Manager しか使わないので、他の認証タイプに関連した不要なコードは削除。

Account Manager をIntent で起動して結果を得るという処理で Activity の onActivityResult をオーバーライドする必要があるが、作成したPicasaTool は Activity ではないので、Activity の onActivityResult から呼び出される Callback メソッドを定義し、ここに必要な処理を実装。

カメラで撮影された画像をそのまま送信すると大きすぎるかもしれないので、まず Bitmap の compress メソッドで圧縮する。

Picasa に送信する処理には、以下の3つの情報が必要。

  • ファイルの Uri
  • ファイルサイズ
  • ファイル名(実際に送信するファイルの名前とは違ってもいい。Picasa 上の写真につける名前)

送信に対する応答のHttpResponse には、そのアップロードした画像にアクセスするためのURLなどが入っている。


Activityを分割

2011年8月13日

カメラのActivityをカメラと画像表示画面とに分けてみた。(ソース

画像表示画面用にPictViewerというActivityを作成し、画像表示機能に関するコードを移す。但しMediaScannerは両方で使うのでカメラ側にも残す。

AndroidManifest.xml に Activity を追加。

        <activity android:name=".PictViewer"
        		  android:theme="@android:style/Theme.NoTitleBar" >
        </activity>

カメラ側には、サムネイルを表示したボタンを押されたときに、画像表示画面を呼び出す為のコードを作成。

        Intent i = new Intent(this, PictViewer.class);
        startActivity(i);

分割することで、動作がかなり安定した。

これでまずまず使えるレベルになったので、Market にUp

 

Androidでカメラを使うアプリを作成ー機能追加

2011年8月7日

前回作成したセルフタイマー専用カメラに機能追加。(ソース)

  • 外部メモリへの保存
  • メディアスキャナーへ画像を登録
  • オートフォーカス
  • 画像表示画面でGalleryによる画像選択を可能に。
  • 画像削除機能

外部メモリへの保存

まず、 Environment.getExternalStorageState() で外部メモリが使用可能か否かを判定。

        String state = Environment.getExternalStorageState();
        if (!Environment.MEDIA_MOUNTED.equals(state)) {
            return false;
        }

Environment.getExternalStorageDirectory() で外部メモリへのパスを取得。

    	File directory = Environment.getExternalStorageDirectory();

データは、外部メモリ内の “Android/data/<パッケージ名>” というフォルダに保存しないといけないようなので、そのフォルダがなければ作成(mkdirs)する。

    	File directory = Environment.getExternalStorageDirectory();
    	String dirpath = "Android/data/" + pkgname + "/files";
    	File subdirectory = new File(directory, dirpath);
    	if(subdirectory.exists() == false) {
        	subdirectory.mkdirs();
    	}

BufferedOutputStream を開いて書き込む。

    	File f = new File(subdirectory.getPath(), filename);
    	OutputStream out = new BufferedOutputStream(new FileOutputStream(f));
	out.write(data);

メディアスキャナーへ画像を登録

MediaScannerConnectionで画像ファイルを登録すると、内蔵の画像ビューアーなどで画像が見れるようになる。このとき、サムネイルも作ってくれる。

MediaScannerConnectionのインスタンス作成時に、MediaScannerConnection.MediaScannerConnectionClient のインタフェースの参照が必要。今回はActivity本体にこのインタフェースを実装した。

スキャンを開始する前に connect() を呼び出す。完了すると onMediaScannerConnected() が呼び出される。パスを指定して scanFile() を呼び出し、スキャンしたらすぐにdisconnect している。この使い方が正しいのか、よくわからないが、とりあえず機能しているように見える。

    public void onMediaScannerConnected () {
        if(mMsc.isConnected()) {
            mMsc.scanFile(mScanPath, null);
            mMsc.disconnect();
        }
    }

スキャンが完了すると、onScanCompleted (String, Uri) が呼び出される。スキャンしたファイルのパスとURIが取得できるが、今回はどちらもつかわないので、ここでは何もしていない。

オートフォーカス

Camera の autoFocus (Camera.AutoFocusCallback) を呼び出すと、オートフォーカスができたタイミングで AutoFocusCallback の onAutoFocus (boolean, Camera) が呼び出される。この中で、Camera の takePicture () を呼び出すだけでよかった。takePicture() を呼び出すところは既にできていたので、実質、追加したのは2行だけ。

        mCam.autoFocus(new Camera.AutoFocusCallback() { //追加
            @Override
            public void onAutoFocus(boolean success, Camera camera) { //追加
                camera.takePicture(null, null, new Camera.PictureCallback() { //ここは同じ
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {
           /* 省略。ここでデータを保存した後、startPreview を呼び出す */
                    }
                });        
            } //追加
        }); //追加

画像表示画面でGalleryによる画像選択を可能に

下のサムネイルから画像を選択

最初はApiDemo の ImageSwitcher + Gallery の組み合わせをそのまま使うつもりだったが、イメージを2つ用意しないといけない為か、メモリ不足で落ちることが多かったので、ImageSwitcher はやめて、普通の ImageView に変更。

Gallery の詳しい説明は

ApiDemo の ImageSwitcher のコードは

また、この修正の過程で ImageView.setImageURI() の使い方が判明。実は、前回からこのメソッドを使って画像を表示させようとしていたが、うまくいかず、結局自分で Bitmap を作成することにしたのだが、前回は

        Uri uri = Uri.fromFile(new File(path));
        imgView.setImageURI(uri);

のように、Uri.fromFile() から作成した Uri を使っていたが、だめだった。しかし、今回改めて調べたところ、下のように Uri.parse() を使えば表示できるとの情報があり、ためしてみると、表示できた!(・・・何が違うんだ!)

        Uri uri = Uri.parse(path);
        imgView.setImageURI(uri);

しかし、結局 setImageUri() では表示が遅くなるので、自分で Bitmap を作成する方法に戻してしまった。きっと、setImageUri() ではファイルのフルサイズのイメージを作成してしまうので、遅いのかもしれない。(自分で Bitmap を作成するときは、画面の大きさに合わせて縮小している。)

画像削除機能

画像を表示しているときに、その画像を削除できる機能を追加。メニューボタンからとコンテキストメニューから呼び出せる。

コンテキストメニューの使い方

また、画像を削除した際も、メディアスキャナーに登録すると、サムネイルを削除してくれる。これを行わないと、内蔵の画像ビュアーには「壊れた画像」的に表示が残ってしまう。

問題点

  • スクリーンロックがかかると、必ず再起動してしまう。
    • OnResume ではなく、OnCreate が呼び出されてしまう。変数もみな初期化されている。そのせいで、画像表示中にロックされると、カメラに戻ってしまう。なぜ?
  • ロックから戻るときの不具合が多い。
    • カメラのインスタンス取得に失敗し、プレビュー表示ができていなかったり、落ちてしまったりする。
  • Activityの分割
    • Activityをカメラと画像表示で分けた方がいいのかもしれない。

R.java を削除してしまった!

2011年7月30日

自動的に作成されるはずなのに、ProjectをBuildしても戻ってこない。

gen [Generated Java Files] を右クリックして [Restore from Local History…]から元に戻すことができたが、Layoutへの変更を反映しなくなってしまった。作り直すために Project をClearしたら、R.Java は消えてしまった。

再度、[Restore from Local History…]から元に戻して、いろいろと調べてみたところ、R.Java のプロパティからGroupの書き込み権限がなくなっていたのが原因らしい。R.Java のProperties画面で[Restore Default]を押すと元に戻った。

Androidのアプリでカメラを使う(Intentを使う)

2011年7月25日

Androidのアプリからカメラを使うには・・・実はもっと簡単な方法があった。。。orz

Intentを使えばよかったらしい。

本当に簡単だった。
サンプルプログラムにはボタンとImageViewがあり、ボタンが押されたらIntentを作成してstartActivityForResultを呼び出す。2個目の引数の0(ゼロ)は、Actionの成功時にrequestCodeとして返される。1つのプログラムで複数のIntentActionを使う場合は、これで判別できる。

	Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
	startActivityForResult(intent, 0);

カメラでシャッターが押されたら(画像がキャプチャされたら)onActivityResult が呼び出される。
IntentからBundleを使ってExtra dataを取り出す。
(”data”というKeyだったが、1つしかないので、ここではあまり気にしない)
取り出したObjectをImageにキャストしてImageViewで表示する/

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 0) {
            if (resultCode == RESULT_OK) {
            	//If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap object in the extra field. 
            	Bundle b = data.getExtras();
                ImageView img = (ImageView)this.findViewById(stndstn.intenttest.R.id.imageView1);
                Set keys = b.keySet();
                Iterator k = keys.iterator();
                while(k.hasNext()) {
                	Object o = b.get(k.next());
                	if(o.getClass() == Bitmap.class) {
                    	Bitmap bmp = (Bitmap)o;
                    	img.setImageBitmap(bmp);                		
                    	break;
                	}                				
                }
            }
        }
    }

カメラのシャッターは自分で押さないといけないが、アプリからカメラ機能を使う状況を考えると、普通はこれで十分だろう。ただし、セルフタイマーなどのようにプログラムからシャッターを切るような動作はできないようにみえる。

Androidでカメラを使うアプリを作成

2011年7月24日

Androidでカメラをつかった簡単なプログラムを作成してみた。(ソースはこちら)

標準でついているカメラにはセルフタイマーがないので、セルフタイマー専用のカメラを作ってみる。

左上のカメラのアイコンを押すと撮影。左下は前回撮影した画像。

残り秒数がカウントダウンされる

★カメラの使用方法については、開発者サイトの以下のリンクを参照(android.hardware.Camera)

要旨だけまとめると

①カメラのインスタンスを取得 open(int).

②既存の設定(デフォルト)を取得 getParameters().

③必要であれば、2で取得した Camera.Parameters オブジェクトを変更しsetParameters(Camera.Parameters) を呼び出して変更した設定を反映

④必要なら setDisplayOrientation(int). で画像の向きを設定

⑤初期化済みの SurfaceHoldersetPreviewDisplay(SurfaceHolder). に渡す。これがないとプレビューができない。

startPreview() を呼び出してプレビューを開始。写真を撮る前にプレビューが開始されないといけない。

takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback) を呼び出すと撮影される。イメージが作成されると各コールバックが呼び出されるので、そこでデータを取得できる。

⑧撮影後はプレビューは止まっているので、再開するには startPreview() を呼び出す。

stopPreview() でプレビューを停止

⑩他のアプリケーションがカメラを使えるように、カメラを release() する。アプリケーションは onPause() でカメラをリリースしなければならない(そして onResume() で再び open() する).

上のリンクの情報はよくまとめられているので、ここ情報だけでほぼ十分だった。(個人的な意見ですが、Appleの開発者サイトに比べるとGoogleの開発者サイトのほうがわかりやすいですね。)

■このプログラムの仕様

・プレビュー画面左上のボタンを押すと、10秒後に撮影される。

・プレビュー画面上で残り秒数がカウントダウンされる。

・撮影した画像のサムネイルが左下に表示され、サムネイルをクリックすると画像が表示される。

・画像は最後の一枚しか保存されない。(内蔵メモリに保存するので・・・)

プレビュー用画面のレイアウトは、プレビュー表示用のSurfaceViewの上に、オーバーレイ状にボタンとラベルを配置。

オーバーレイ状に画面を重ねるので、一番下のレイアウトはFrameLayout、その上にSurfaceViewとRelativeLayoutを重ねる。

RelativeLayoutの左上にタイマー開始のボタン(カメラのアイコン)、真ん中にカウントダウン表示用のラベル、左下にサムネイル表示用のイメージボタンを配置。

画面レイアウト

なお、プレビュー画面の方向を変えることができなかったので、画面の向きはLandscapeに固定する。

画像確認用の画面はImageViewのみで構成する。

また、Manufestに以下の項目が必要

<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />

■各処理の説明

1.初期化処理

(1)OnCreate

  • まずプレビュー画面が表示されるように設定(setContentView)
  • 画面の向きをLandscapeに設定
  • 各コントロールの参照を取得
  • SurfaceViewのSurfaceHolderにコールバックのリスナーを設定(Activityにコールバックを実装するので、this を指定)
  • SurfaceViewのSurfaceHolderのTypeを SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS に設定。※これを設定しないとプレビュー開始時にエラーになる。
  • 画面上の配置するコントロールのVisibiityを設定。ボタンはVisible、カウントダウン用のラベルはInvisible
  • ボタンが押された際の動作を記述

(2)OnResume

  • カメラのインスタンスを取得 open(int).(①)

(3)surfaceCreated ※SurfaceHolderのコールバック

  • Surfaceが初期化されたので、カメラのsetPreviewDisplayに設定する(⑤)

(4)surfaceChanged ※SurfaceHolderのコールバック

  • Surfaceのサイズなどが決まったタイミングで呼び出されると思われる。ここでgetParametersでパラメータを取得する(②)。後で画面のサイズを取得する際に使用する。
  • 他にも設定したいParameterがあればここで設定(③、④)したらよいのではないかと思われるが、今回は特に必要がなかったので設定していない。(※設定するタイミングについては、実はまだよくわかっていない。現状では、SuspendからResumeしたときに、例外が発生して落ちてしまうことが多い。カメラのインスタンスを取得できないのが原因らしいが、このへんのタイミングについては、もっと調べる必要がある。)
  • カメラのstartPreviewを呼び出す。

2.タイマーのスタート

(1)ボタンが押されたら、カウントダウンのラベルをVisibleに設定し、CountDownTimerを作成してスタートさせる。

(2)CountDownTimerのonTickでカウントダウンのラベルの数字を更新。

(3)CountDownTimerのonFinishでカウントダウンのラベルをInvisibleに戻し、カメラのtakePictureを呼び出す。

3.撮影

(1)カメラのtakePictureを呼び出す。このプログラムではJPEGデータのコールバックだけを指定。

(2)JPEGデータのコールバック(onPictureTaken)で、データを取得してファイルに保存。サムネイル用のビットマップを更新した後、カメラのstartPreviewを呼び出す。

4.画像の保存

★内蔵メモリへのデータ保存については以下のリンクを参照

(1)各アプリケーション用に指定された領域にファイルを作成する。(openFileOutput)

(2)画像データは byte[] なので、これを FileOutputStream に書き込んで、閉じる

  FileOutputStream fos = this.openFileOutput(name, Context.MODE_WORLD_WRITEABLE);
  fos.write(data);
  fos.close();

5.サムネイル表示の更新

(1)getFileStreamPath で、4で各アプリケーション用に指定された領域に保存したファイルのパスを取得。

(2)カメラのParameterから画像サイズを取得し、サムネイル用にするには元の大きさの何%にしたらよいかを計算。

(3)BitmapFactory.decodeFile を呼び出し、Bitmap を作成。

(4)サムネイル表示用のImageButtonにBitmapを設定。

■わかったこと、及び課題

1.保存したファイルにアクセスできない

今回は、openFileOutput を使って、内蔵メモリの各アプリケーション用に指定された領域にファイルを作成した。ここは他のアプリケーションからはアクセスできないように設計されているのかもしれない。画像のようなデータは外部メモリに保存したほうがよかった。撮った画像データにアクセスできないと意味が無いので、後で修正します。

2.レベル8以上でないと使えない便利なAPIが多い

サムネイルを作成したり、外部メモリへの保存先を指定したり、といった「これは使いたい」と思ったAPIが、レベル8以上の指定になっていて使うことができず、残念な思いをした。

3.SuspendからResumeしたときに、例外が発生して落ちてしまうことが多い。

内蔵のカメラではそのようなことはないので、なにかが足りないはずだ。エラーの内容を見るとカメラのインスタンスを取得できていないようだ。カメラの準備ができてないタイミングでアクセスしようとしてしまっているのか?まだ調査が必要。

4.オートフォーカス

Manufestに以下のように設定しておけば自動的にオートフォーカスになるのかと思っていたが

     <uses-feature android:name=”android.hardware.camera.autofocus” android:required=”false” />
どうも違うらしい。オートフォーカス用のコールバックを実装しないといけないようなので、今回はパス。

ドロップダウンリストをNavigatorControl+TableViewで実装する

2011年7月17日

「ゴルフのスコアカウンター(iPhone版)」で、PARの選択にドロップダウンリスト風にTableViewを使うように修正してみた。Windowベースで作成したアプリをNavigatorControl+TableViewで実装する仕組みに変更する。

ソース

修正前のレイアウト

修正前のレイアウト

修正後の画面

修正後の画面

1.NavigationControllerを nib ファイルに追加。

右側のライブラリからNavigationControllerを網の上にドロップすると、画面のイメージと左端にオブジェクトが追加される。

Navigatorコントロールを追加

Navigatorコントロールを追加

2.Navigation Controller の Output を AppDelegate に追加

(1)View>Editor>Assistant にする

(2)nibウィンドウからNavigationControllerを右クリックしてAppDelegateのヘッダーファイルにドラッグ&ドロップ

NavigationControllerのOutletを追加

NavigationControllerのOutletを追加

3.Window の rootViewController をNavigator Controller にする

(1)nibウィンドウで Window を選択

(2)Connections Inspector の rootViewController の右側の丸印をドラッグして、2で追加した Navigation Controller の上にあわせる。

rootViewControllewを設定

rootViewControllewを設定

4.TableViewControllerを上のnib に追加

右側のライブラリからNavigationControllerを網の上にドロップすると、画面のイメージと左端にオブジェクトが追加される。

5.TableViewController のクラスファイルを追加(RootTableViewControler)

6.Window にのっていたUIのコントロールを4で追加した TableViewController に移す

(1)TableViewController のヘッダー部分に下地となる View を追加し、その上にWindowからPAR選択部分の上にあるコントロール群をカット&ペースト

(2)TableViewController のフッター部分に下地となる View を追加し、その上にWindowからPAR選択部分の下にあるコントロール群をカット&ペースト

7.Controllerに作成していた、各コントロールのOutletとイベントハンドラをRootTableViewControlerにコピーし、nib上でOutletとコントローラー、およびイベントハンドラの結びつけを行う。

8.RootTableViewControler の Output を AppDelegate に追加

(1)View>Editor>Assistant にする

(2)nibウィンドウから RootTableViewControler を右クリックしてAppDelegateのヘッダーファイルにドラッグ&ドロップ

9.最初に表示される View を設定

ApplicationDelegate の didFinishLaunchingWithOptions で 2で作成したNavigationController の Outlet に 7で作成した RootTableViewControler の view を initWithRootViewController する。

10.RootTableViewControler に、dataSource のメソッドを実装。

★アイテムは1つしかないので。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
  // Return the number of sections.
  return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  // Return the number of rows in the section.
  return 1;
}

★メニュー内の各セルのスタイルとテキストを設定。サブメニューがあることを示す”>”を表示し(UITableViewCellAccessoryDisclosureIndicator)、現在の値(詳細)も表示するスタイル(UITableViewCellStyleValue1)に設定。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *CellIdentifier = @"Cell";

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
  }

  // Configure the cell...
  cell.textLabel.text = @"PAR:";
  cell.detailTextLabel.text =
  [NSString stringWithFormat:@"%d",m_score_table[m_hole - 1][SCORE_TABLE_PAR]];

  cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

  return cell;
}

11.nib に もう一つの TableViewController を追加、PAR数選択用

12.上の TableViewController のクラスファイルを追加(ParSelTableViewControler)

13.ParSelTableViewControler の Output を RootTableViewControler に追加

14.RootTableViewControler の didSelectRowAtIndexPath に、リストが選択されたときの動作を記述。PAR選択用のView(ParSelTableViewControler の view)に移動する。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  // Navigation logic may go here. Create and push another view controller.
  golfscntAppDelegate * app = (golfscntAppDelegate*)[UIApplication sharedApplication].delegate;
  [app.navigationController pushViewController:parSelTableViewController animated:YES];
}

15.PAR選択用ViewのParSelTableViewControler に、dataSource のメソッドを実装。
★アイテムは3つ。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
  // Return the number of sections.
  return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  // Return the number of rows in the section.
  return 3;
}

★スタイルは標準。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *CellIdentifier = @"Cell";

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
  }

  // Configure the cell...
  int sel = indexPath.row;
  switch (sel) {
    case 0:
      cell.textLabel.text = @"PAR 3";
      break;
    case 1:
      cell.textLabel.text = @"PAR 4";
      break;
    case 2:
      cell.textLabel.text = @"PAR 5";
      break;
    default:
      break;
  }
  return cell;
}

16.ParSelTableViewControler の didSelectRowAtIndexPath に、リストが選択されたときの動作を記述。データを更新し、元のView(RootTableViewControler の view)に移動する。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here. Create and push another view controller.
    golfscntAppDelegate * app = (golfscntAppDelegate*)[UIApplication sharedApplication].delegate;
    RootTableViewController* c = app.rootTableViewController;
    int hole = c->m_hole;
    switch (indexPath.row) {
        case 0:
            c->m_score_table[hole - 1][SCORE_TABLE_PAR]=3;
            break;
        case 1:
            c->m_score_table[hole - 1][SCORE_TABLE_PAR]=4;
            break;
        case 2:
            c->m_score_table[hole - 1][SCORE_TABLE_PAR]=5;
            break;
        default:
            break;
    }
    [app.navigationController popViewControllerAnimated:YES];
    [c update_display];
  }
}

17.画面データを更新するタイミングで、tableView reloadData を呼び出し、テーブルに変更後のデータが反映されるようにする。

iPhonenのドロップダウンリスト(またはメニュー)=Navigation Controller + Table View Controller だった

2011年7月5日

iPhonenのUIにドロップダウンリストやメニューがないので困っていたが、その機能は Navigation Controller + Table View Controller で実装できるらしい。

ドロップダウンやメニューだと、同じ画面上に選択肢がベロッと出てくるが、Navigation Controller + Table View Controller では、選択肢だけの別画面に移動するような感じになる。確かに狭い画面で操作することを考えれば、こちらのほうが理にかなっている気がする。でも、実装が面倒くさそうで・・・慣れるまでは苦労しそうだ。

まず最初に、1画面でいけるのか、あるいは複数画面になるのか、を決めておかないと、後で変更するのは面倒だ。ドロップダウンやメニューで選択させる場合は複数画面確定。・・・ということは、新規作成するときに、とりあえずNavigation based アプリで作っておくのがいいのかもしれない。Navigation based アプリが新規作成画面の一番左にあるのは、そういうことだったのか・・・。