前回は、アイデア、スキルが全く無い状態から、アプリのコンセプトが決定するまでの流れを書きました。
前回の内容をまとめると、
「Objective-Cを習得しつつ」「ライトアプリ程度の難易度のアプリ」を「3ヶ月で制作」し「有料アプリ」として「リリースしたい」
ということでした。
今回は実際のソースをみながら、この最初のライトアプリが完成するまでの過程を説明していきたいと思います。
アプリ完成まで
まず、ヘッダーファイル(ViewController.h)にAVFoundationフレームワークをインポートします。
[code]
#import <AVFoundation/AVFoundation.h>
[/code]
AVFoundationフレームワークは、iOSデバイスからの画像や音声の入出力を行うために、必要なオブジェクトがまとめられているものです。
ここでライトなのに何故?と思う方もいるかもしれませんが、もともとiPhoneのLEDはライトとしてではなく、暗い場所での動画や静止画の撮影をサポートするための照明機能なので、そのLEDのON/OFFを行うための命令は AVFoundation に組み込まれている、という訳です。
そして、以下のコードを ViewController.m にペーストします。
[code]
– (void)torchOnFunction
{
AVCaptureDevice *flashLight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if([flashLight isTorchAvailable] && [flashLight isTorchModeSupported:AVCaptureTorchModeOn])
{
BOOL success = [flashLight lockForConfiguration:nil];
if(success)
{
[flashLight setTorchMode:AVCaptureTorchModeOn];
[flashLight unlockForConfiguration];
}
}
}
– (void)torchOffFunction
{
AVCaptureDevice *flashLight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if([flashLight isTorchAvailable] && [flashLight isTorchModeSupported:AVCaptureTorchModeOff])
{
BOOL success = [flashLight lockForConfiguration:nil];
if(success)
{
[flashLight setTorchMode:AVCaptureTorchModeOff];
[flashLight unlockForConfiguration];
}
}
}
[/code]
最後にIB(インターフェースビルダー)でボタンオブジェクトを作成し、IBActionと接続します。そのボタンの IBAction から torchOnFunction を実行すればiPhone背面のLEDが点灯し、torchOffFunction を実行すればLEDが消灯します。
終わりです(笑)
もちろん、これで実際に申請に出したアプリが完成と言うわけではなく、アイコンデザイン、UIデザイン、各種設定画面やアニメーション効果を作成し実装していくことになりますが、既存の無料ライトアプリの基本は、この程度のコーディングで実現できます。
アプリの問題点
しかし、前回でも少し触れましたが、そもそもこのライトアプリというものは、Appleのアプリ審査ガイドラインの2.11項にリジェクト対象となることが明確に定義されています。
App Storeで既に存在するアプリの複製、特に、おなら、げっぷ、懐中電灯、カーマスートラアプリのように多く存在するアプリの場合はリジェクトされます
リジェクトされることがあります、ではなく、特に、懐中電灯アプリはリジェクトされます、とあります。特に〜と強調し、明確にそのジャンルまで定義されている項目はここだけです。それだけ手がつけやすいアプリだからでしょう。
この事実を知らないまま、せっせとこのライトアプリをブラッシュアップし、最初のアプリ「SmartLight」が完成し、初めての申請を行いました。
初のリジェクト
その後、リジェクト通知が来るのは早かったです(笑)知らなかったとは言え、ダメなことをやったわけですから当たり前です。最初からうまくいくとは思っていなかったので、リジェクト自体にショックはありませんでしたが、リジェクトされた理由にショックを受けました。
この後すぐガイドライン全てに目を通したのは言うまでもありません。
ここで、自分の作ったアプリが明確にリジェクトの対象であることを知れば、別コンセプトのアプリに方向を切り替え、一旦仕切りなおすはずですが、この時思ったのが「どうにかしてこのライトアプリをリリースさせたい」というものでした。
ライトの機能にこだわる理由
何故そう思ったのか、それはリジェクトされた原因であるガイドライン2.11項を見た時でした。もう一度引用します。
App Storeで既に存在するアプリの複製、特に、おなら、げっぷ、懐中電灯、カーマスートラアプリのように多く存在するアプリの場合はリジェクトされます
前述したように「懐中電灯はリジェクトされます」は明確な表現ですが、「App Storeで既に存在するアプリの複製」に関しては遵守されていないと感じました。複製とまではいかないまでも、App Storeには既に似たようなアプリで溢れかえっているからです。
次の2.12項も気になりました。
ユニークではない、あまり有用でない、単にウェブサイトをバンドルしたもの、永続する娯楽価値を提供しないアプリケーションはリジェクトされます
また、10.6項では
Appleと当社の顧客は、シンプルで洗練された、創造的なインターフェイスを好みます。彼らはより多くの働きを求めるが、そこにはそれだけの価値があります。Appleは高いハードルを設定しています。あなたのユーザインタフェースが複雑、または非常に良いとされる条件を満たせていない場合、リジェクトされることがあります
とあります。開発者にとってはありがた迷惑な基準ですが、この10.6項にAppleの全てが込められていると思います。ここは普段から自分自身がものづくりに関して一番意識している部分なので、個人的には好きな項目です。
ガイドラインの本当の意味
これらをみてみると、ガイドラインの特定の項目に関しては、非常に柔軟性のある定義であることに気づくはずです。そして、このことはAppleのレビュワーには、開発者が申請に出したアプリのリリースに対しての権限に、ある程度の裁量が与えられていると言うことを意味します。
一見するとガイドラインさえ守れば、何でもリリース出来るように思いがちですが、実際はそんなことだけではないように感じられました。それと同時に、アプリの審査に関してAppleのレビュワーの裁量がどういったものなのかを、今回制作したアプリである程度把握しておきたいと思った訳です。
まとめ
次回は、この無謀な考えを実現するために、リジェクト後にどのようにしてコンセプトを再構築し、SmartScopeが完成したかをサンプルコードを交えながら説明していきたいと思います。
最後に今回紹介したライトアプリが、コピペだけで動くようにしたアプリのサンプルコードを載せておきます。
アプリ制作の基本を知るという意味においては非常に有効な手段ですので、是非実機でテストしてみてください。
- 制作手順
-
- Xcodeの新規プロジェクトからSingle View Applicationでスケルトンを作成します。
- IBで UIButton を toggleBtn という名前で作成します。
- 以下のコードを該当ファイルにペーストし、オブジェクトとIBActionを toggleBtn にリンクします。
- ビルドして完成。
サンプルコード
- ViewController.h
- [code]
#import <AVFoundation/AVFoundation.h>@interface ViewController : UIViewController
{
IBOutlet UIButton *toggleBtn;
}– (IBAction)touchUp:(id)sender;
@property(retain,nonatomic) IBOutlet UIButton *toggleBtn;
@end
[/code] - ViewController.m
- [code]
#import “ViewController.h”@interface ViewController ()
@end
@implementation ViewController
@synthesize toggleBtn;
– (void)viewDidLoad
{
[super viewDidLoad];
}– (void)torchOnFunction
{
AVCaptureDevice *flashLight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if([flashLight isTorchAvailable] && [flashLight isTorchModeSupported:AVCaptureTorchModeOn])
{
BOOL success = [flashLight lockForConfiguration:nil];
if(success)
{
[flashLight setTorchMode:AVCaptureTorchModeOn];
[flashLight unlockForConfiguration];
}
}
}– (void)torchOffFunction
{
AVCaptureDevice *flashLight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if([flashLight isTorchAvailable] && [flashLight isTorchModeSupported:AVCaptureTorchModeOff])
{
BOOL success = [flashLight lockForConfiguration:nil];
if(success)
{
[flashLight setTorchMode:AVCaptureTorchModeOff];
[flashLight unlockForConfiguration];
}
}
}– (IBAction)touchUp:(id)sender
{
toggleBtn.selected = !toggleBtn.selected;
if (toggleBtn.selected) {
[self torchOnFunction];
}
else {
[self torchOffFunction];
}
}– (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}@end
[/code]