目指せ!iPhoneアプリ開発エキスパート

第8回アプリの完成を目指して

前回から進めているアプリ開発を、さらに進めて完成させます。

バーゲン教師を作る

今回は、実際にリリースされているアプリを見ながら、これまでに学んだことを復習していきます。まずは、App Storeで公開されている「バーゲン教師」と同じものを作ってみましょう。

バーゲン教師の画面
バーゲン教師の画面

パーツの配置と接続

バーゲン教師の画面構成と動作について説明します。画面上のパーツをより詳しく見ると、次のようになります。

バーゲン教師の画面を構成するパーツ。青い点線で囲まれたパーツは単なるラベル
バーゲン教師の画面を構成するパーツ。青い点線で囲まれたパーツは単なるラベル

まず、(1)のテキストフィールドに金額を入力します。その後、(2)のセグメンテッドコントロールで割引率を選択すると、(3)のラベルに割引率をパーセント表示するとともに、(5)のラベルに割引後の金額が表示されます。(4)のスライダーを左右に動かすことで、5%から95%までの割引率を5%単位で調整することができます。(6)のボタンを押すと、(5)のラベルに表示されている金額と(7)のラベルに表示されている金額を合計して(7)のラベルに再表示します。(8)のボタンを押すとカートをクリアしてよいか確認するダイアログが表示され、ユーザがクリアを選択すると(7)のラベルが0になります。

(1)のテキストフィールドには数字を入力しますので、キーボードは「Number Pad」を選択しておきます。(4)のスライダーは5%から95%までの範囲が選択できるように、ValuesのMinimumを0.5に、Maximumを0.95に設定しておきましょう。

パーツと接続するアウトレットおよびアクションの組み合わせは次の通りです。

アウトレット
  • priceOriginal - (1)テキストフィールド
  • discountButton - (2)セグメンテッドコントロール
  • discountPercent - (3)ラベル
  • discountSlider - (4)スライダー
  • priceNew - (5)ラベル
  • cartPrice - (7)ラベル
アクション
  • buttonValueChanged: - (2)セグメンテッドコントロール / Value Changed
  • sliderValueChanged - (4)スライダー / Value Changed
  • escapeButtonPush: - (2)セグメンテッドコントロール / Value Changed 、(4)スライダー / Value Changed
  • pushAddCart - (6)ボタン / Touch Down
  • pushClearCart - (8)ボタン / Touch Down

これまでアクションの名前には最初から付いている「myAction1:」などを利用してきましたが、これもアウトレットと同じように好きな名前に変更することができます。ただし、変更する際はアクション名の最後に「:」⁠コロン)を付けるのを忘れないようにしてください。

また、アクション「escapeButtonPush」には2つのパーツのきっかけが設定されていますが、このように複数のきっかけをひとつのアクションに設定することができます。

上記の組み合わせをもとにInterface Builderを使ってパーツを配置し、File's Ownerにアウトレットとアクションを作成して接続してみましょう。これまでに学んだことを生かして、それぞれのアウトレットからどういった情報を取得するのか、またアクションの中ではどういった処理を行うのか、バーゲン教師の機能と見比べながら考えてみてください。

背景色は、パーツが何も選択されていない状態(Viewのタイトルバーをクリックすると選択が解除されます)で、インスペクタの「View Attributes」にある「Background Color」から変更することができます。カラーパレットから好きな色を選びましょう。

パーツの配置とラベルの入力、さらにアウトレット・アクションの接続が完了したら、File's Ownerが選択された状態で「File⁠⁠→⁠Write Class Files...」を選択してファイルに書き出しましょう。

アクションの実装

それでは、アクションの中身を記述します。

- (IBAction)buttonValueChanged:(id)sender {
    //割引率を保持する変数
    float discount_value = 0.2f;
    //セグメンテッドコントロールの選択位置から割引率を決定
    int selected_index = [discountButton selectedSegmentIndex];
    switch(selected_index){
    case 0:
        discount_value = 0.2f;
        break;
    case 1:
        discount_value = 0.3f;
        break;
    case 2:
        discount_value = 0.4f;
        break;
    case 3:
        discount_value = 0.5f;
        break;
    default:
        break;
    }
    //選択されている割引率の位置にスライダーのつまみを動かす
    [discountSlider setValue:discount_value animated:YES];
    //割引率をパーセント形式でラベルに表示
    [discountPercent setText:[NSString stringWithFormat:@"%d", (int)(discount_value * 100.f)]];
    //元値と割引率から割引後の値段を計算してラベルに表示
    int price_original = [[priceOriginal text] intValue];
    if(price_original > 0){
        [priceNew setText:[NSString stringWithFormat:@"%d", (int)(price_original * (1.f - discount_value))]];
    }
}

これはセグメンテッドコントロールで割引率が変更されたときのアクションです。discountButtonで選択されている番号をもとに割引率を小数で表し、1から引いた値を元値にかけて割引後の金額を求めます。割引率をパーセントで表したものをdiscountPersentに、割引後の金額をpriceNewに表示しています。

また、discountButtonで選択されている番号の値に対応する割引率と、discountSliderに接続されているスライダーの位置とが連動するように、スライダーに対して値を設定するメッセージ「setValue」を使っています。この際スライダーがアニメーションするように、メッセージの「animated」引数に「YES」を渡します。

- (IBAction)sliderValueChanged:(id)sender {
    //スライダーの値から割引率を決定、0.5単位に切り捨てる
    float discount_value = (int)([(UISlider*)sender value] * 20) / 20.f;
    //セグメンテッドコントロールに一致する割引率があれば選択されている状態にする
    if(discount_value == 0.2f){
        [discountButton setSelectedSegmentIndex:0];
    }
    else if(discount_value == 0.3f){
        [discountButton setSelectedSegmentIndex:1];
    }
    else if(discount_value == 0.4f){
        [discountButton setSelectedSegmentIndex:2];
    }
    else if(discount_value == 0.5f){
        [discountButton setSelectedSegmentIndex:3];
    }
    else{
        [discountButton setSelectedSegmentIndex:-1];
    }
    //割引率をパーセント形式でラベルに表示
    [discountPercent setText:[NSString stringWithFormat:@"%d", (int)(discount_value * 100.f)]];
    //元値と割引率から割引後の値段を計算してラベルに表示
    int price_original = [[priceOriginal text] intValue];
    if(price_original > 0){
        [priceNew setText:[NSString stringWithFormat:@"%d", (int)(price_original * (1.f - discount_value))]];
    }
}

続いてはスライダーによって割引率が変更されたときのアクションです。スライダーでは5%から95%までの範囲を5%単位で選択しますので、実際の値は0.5から0.95までの範囲で0.5刻みになっている必要があります。ここでは、20をかけて小数点以下を切り捨て整数にしたあと、再度20で割って小数に戻すという方法で0.5刻みの値にしています。

値が求まったら、discountButtonに対応する割引率があるかどうかをチェックします。該当する割引率がある場合、その番号をセグメンテッドコントロールの「setSelectedSegmentIndex」メッセージを使って設定します。メッセージの引数にはセグメンテッドコントロールのボタンの番号を渡します。この番号に-1を設定すると選択が解除されますので、いずれにも該当しない場合は-1を設定して選択を解除しておくのが良いでしょう。

割引率が決定したらあとはセグメンテッドコントロールのときと同じように、割引率のパーセント表示と割引後の金額表示を行います。

- (IBAction)escapeButtonPush:(id)sender {
    [priceOriginal endEditing:YES];
}

これはキーボードを閉じるきっかけとなるパーツから呼び出されるアクションです。ここではセグメンテッドコントロールの値が変更された時とスライダーが変更された時にアクションが実行されるよう設定しましたが、前回学んだように入力完了ボタンや透明なボタンを用意してそこにアクションを割り当てても良いでしょう。

- (IBAction)pushAddCart:(id)sender {
    [cartPrice setText:[NSString stringWithFormat:@"%d", [[priceNew text] intValue] + [[cartPrice text] intValue]]];
}

カートに追加するためのボタンが押された時のアクションは単純で、priceNewに表示されている値とcartPriceに表示されている値を足し合わせたものを、cartPriceに書き戻せば良いだけです。

- (IBAction)pushClearCart:(id)sender {
	UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"カートをクリアしますか?" delegate:self cancelButtonTitle:@"キャンセル" destructiveButtonTitle:@"クリアする" otherButtonTitles:nil];
	[actionSheet showInView:self.view];
	[actionSheet release];
}

最後はカートをクリアするボタンが押されたときのアクションです。ダイアログを表示して、ユーザにカートをクリアして良いかを確認します。⁠クリアする」が押されたら、実際にカートの中身の金額を0にします。ダイアログでの選択結果を受け取るためのプログラムを追加で記述します。

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
    if(buttonIndex == 0){
        //カートをクリアする
        [cartPrice setText:[NSString stringWithFormat:@"%d", 0]];
    }
}

このように、ダイアログのdestructiveButtonが押されたらcartPriceに0を表示します。

動作テストとバグ

これでプログラムができあがりました。ビルドして動作を確認しましょう。

ここでなんとなく動いているからといって安心はできません。一見大丈夫そうに見えても、何らかの操作によってアプリが予期しない動作をしたり、突然終了してホーム画面に戻ってしまったりすることがあります。それが「バグ」です。自分で使うだけのアプリなら特に問題はありませんが、アプリを公開する場合には多くのユーザが使うことになるため、想定されるあらゆる操作をあらかじめテストしておく必要があります。

しかしながら、完璧な動作をするアプリ、すなわち1つもバグのないアプリというのは実際には作ることができません。たとえプログラムの中身を完璧に把握していたとしても、Cocoa Touchとの組み合わせで予期せぬ問題が起きたり、iPhone OSや他のアプリとの兼ね合いで動作が不安定になったりすることがあります。

動作が複雑なアプリはそれだけテストに費やす時間も多くなる傾向にあります。使用するパーツの数が増えれば増えるほど、またアプリの機能が増えるほど、テストするべき内容も多くなります。そういった面からも、まずはできるだけ機能を絞り込み、動作が単純でわかりやすいアプリを作ることを心がけるのが良いでしょう。

バーゲン教師の動作テスト

バーゲン教師では次のようなテストを行ってみましょう。いずれも想定される基本操作にあたります。

  • 金額が正しく入力できるか
  • セグメンテッドコントロールのすべてのボタンが正しく動作するか
  • スライダーが正しく動作するか
  • 割引後の金額が正しく表示されるか
  • カートへの追加が正しく行えるか
  • カートが正しくクリアされるか
  • カートクリア時のダイアログでキャンセルが正しく行えるか
  • 一旦カートをクリアした後、再度カートへの追加が正しく行えるか

バグがないことが確認できたら、ついにアプリの完成です。

アイデアを形に

ここまできたら、次はいよいよ思い浮かんだアイデアを実際にアプリにしてみる番です。テーマを決めて1つのアプリを作ってみるのも良し、またCocoa Touchで実現できるより高度な機能を使いこなすために、iPhoneアプリ開発やCocoaプログラミングを題材とした書籍などで理解を深めるのも良いでしょう。

割勘奉行も中身は同じ

より多くのことを学ぼうとせずとも、これまでに身に付けたことを最大限に活かすだけで、立派な「使える」アプリを作ることは可能です。App Storeで公開されている「割勘奉行」は、金額と人数を入力するとひとりあたりの料金を表示してくれる、まさに割り勘をする際に便利なアプリです。機能や見た目は違っていても、そのプログラムの中身は「入力された数字を使って決められた計算を行い、結果を表示する」という点でバーゲン教師とほとんど同じです。

割勘奉行の画面
割勘奉行の画面

金額と人数のテキストフィールドにはキーボードを使って数字を入力しますが、画面の上半分にはテキストフィールド以外にアクションのきっかけとなるパーツがありませんので、キーボードを消すためのアクションのきっかけとして透明のボタンを配置しておく必要があります。その他のパーツは特別なものはありませんので、アウトレットとアクションの接続まで特に問題なく行えるでしょう。

割勘奉行には、単純な人数での割り勘だけでなく、割り勘結果の端数を丸めるための「端数処理」や、端数を繰り上げて多めに徴収するか、または端数を切り捨てて上司や幹事などが負担するかを選べる「幹事負担」の機能があります。この端数処理と幹事負担の状態によって計算の方法が変わってきますので、プログラムの中身はバーゲン教師よりやや難しくなります。

プロジェクトをダウンロード

割勘奉行はソースコードを公開しています。プロジェクトのファイル一式をダウンロードするには、Finderから「アプリケーション⁠⁠→⁠ユーティリティ⁠⁠→⁠ターミナル」を起動し、表示されたウィンドウの $ に続いて次のコマンドを入力します。

svn checkout http://warikan.googlecode.com/svn/trunk/ warikan

ホームディレクトリ(ユーザの名前がついたフォルダ)にwarikanという名前のフォルダが作成され、プロジェクトのファイル一式がダウンロードされます。割勘奉行のプログラムでは、パーツの状態を変数に保持したり、割り勘結果の計算といった何度も行う可能性がある処理を1つにまとめるようなことも行っています。

自分なりのやり方で

プログラミングの勉強はひとまず今回で終わりです。XcodeやInterface Builderも随分と使いこなせるようになってきたことでしょう。パーツの使い方やプログラムの書き方を含め、これまでに紹介してきたものは数ある方法のうちのほんの一例にすぎません。慣れてきたら、ぜひ自分なりのやり方を見つけてみてください。

次回は、作成したアプリをiPhoneおよびiPod touch上で動作させる手順、およびそのために必要な「iPhone Developer Program」⁠有償)への登録手続きについてご紹介します。

おすすめ記事

記事・ニュース一覧