[iOS]アラートのカスタマイズ(3)
何故、透明にならないのか?自動で背景ビューが挿入されるのか?と考えて、以下のメソッドでビュー構造をダンプしてみた。
- (void)dumpView:(id)aView level:(int)level
{
for (int i = 0; i < level; i++) printf("\t");
printf("%s\n", [[NSString stringWithFormat:@"%@", [[aView class] description]] UTF8String]);
for (int i = 0; i < level; i++) printf("\t");
printf("%s\n", [[NSString stringWithFormat:@"%@", NSStringFromCGRect([aView frame])] UTF8String]);
for (UIView *subview in [aView subviews]) {
[self dumpView:subview level:(level + 1)];
}
}
結果は以下のとおり。
UIView
{{0, 20}, {320, 460}}
UIView
{{60, 130}, {200, 200}}
UIRoundedRectButton
{{20, 143}, {72, 37}}
UIButtonLabel
{{24, 9}, {23, 19}}
UIRoundedRectButton
{{108, 143}, {72, 37}}
UIButtonLabel
{{20, 9}, {31, 19}}
自動で背景ビューが挿入されている訳ではないようだ。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Dialog - GitHub
関連情報
[iOS] UIAlertView 上に UIProgressView を載せる [2] キャンセルボタン表示
『Cocoaの日々』いつも参考にさせていただいています。ありがとう!助かります。
[iOS]アラートのカスタマイズ(2)
アラートの内容をカスタマイズするなら、ビューコントローラにしてnibにしてしまえば?ということで、やってみました。
アラート風のビューコントローラを呼び出すコードだ。
- (IBAction)modalPane:(id)sender
{
ModalPaneViewController *modalPaneViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"ModalPaneViewController"];
[modalPaneViewController setCompletionHandler:^(ModalPaneViewControllerResult result) {
switch (result) {
case ModalPaneViewControllerResultCancelled:
[self performSelectorOnMainThread:@selector(didCancel:) withObject:nil waitUntilDone:NO];
break;
case ModalPaneViewControllerResultDone:
[self performSelectorOnMainThread:@selector(didDone:) withObject:nil waitUntilDone:NO];
break;
default:
break;
}
[self dismissModalViewControllerAnimated:YES];
}];
[self presentModalViewController:modalPaneViewController animated:YES];
}
- (void)didDone:(id)arg
{
DBGMSG(@"%s", __func__);
}
- (void)didCancel:(id)arg
{
DBGMSG(@"%s", __func__);
}
アラート風ビューコントローラのコードは、以前紹介したModelPaneとなる。
typedef enum ModalPaneViewControllerResult {
ModalPaneViewControllerResultCancelled,
ModalPaneViewControllerResultDone
} ModalPaneViewControllerResult;
typedef void (^ModalPaneViewControllerCompletionHandler)(ModalPaneViewControllerResult result);
@interface ModalPaneViewController : UIViewController
@property (nonatomic, copy) ModalPaneViewControllerCompletionHandler completionHandler;
- (IBAction)done:(id)sender;
- (IBAction)cancel:(id)sender;
@end
@implementation ModalPaneViewController
@synthesize completionHandler = _completionHandler;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
self.delegate = nil;
self.completionHandler = nil;
[super viewDidUnload];
}
- (IBAction)done:(id)sender
{
DBGMSG(@"%s", __func__);
if (self.completionHandler) {
self.completionHandler(ModalPaneViewControllerResultDone);
}
}
- (IBAction)cancel:(id)sender
{
DBGMSG(@"%s", __func__);
if (self.completionHandler) {
self.completionHandler(ModalPaneViewControllerResultCancelled);
}
}
@end
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Dialog - GitHub
関連情報
[iOS] UIAlertView 上に UIProgressView を載せる [2] キャンセルボタン表示
『Cocoaの日々』いつも参考にさせていただいています。ありがとう!助かります。
[iOS]アラートのカスタマイズ
UIAlertViewについては、もっとカスタマイズできるようにして欲しいが、何故かあまり手をつけられていない。Blocks対応もまだなので、もしかしたら、何か大きな変更を考えているのかもしれない。
アラートで画像等を表示したくなった。そも最も簡単な方法が画像のビューを追加する方法だが、追加するだけだとアラート全体のサイズが変更されないので、追加した画像と、元々のボタンが重なる等、不具合が発生する。そこで、自分が追加した画像のサイズから、アラート全体とボタンの位置を調整する事になる。
- (IBAction)alertImage:(id)sender
{
/* 画像を用意 */
UIImage *image = [UIImage imageNamed:@"likeness.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(10.0, 80.0, 100.0, 100.0);
UIAlertView *alertView = [[UIAlertView alloc] init];
alertView.delegate = self;
alertView.title = @"Alert Image";
alertView.message = @"a likeness";
[alertView addSubview:imageView]; /* 画像を追加 */
[alertView addButtonWithTitle:@"NO"];
[alertView addButtonWithTitle:@"YES"];
alertView.cancelButtonIndex = 0;
[alertView show];
}
/* アラートのボタン押下に対応 */
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
DBGMSG(@"%s, buttonIndex(%d)", __func__, (int)buttonIndex);
}
/* アラートのサイズを調整する */
- (void)willPresentAlertView:(UIAlertView *)alertView
{
CGRect frame = alertView.frame;
frame.origin.y -= 50.0; /* 追加した分、縦軸の座標を調整 */
frame.size.height += 100.0; /* 追加した分、縦の長さを増やす */
alertView.frame = frame;
for (UIView* view in alertView.subviews) {
frame = view.frame;
if (frame.origin.y > 80) { /* 追加したビューより下 */
frame.origin.y += 100; /* 追加した分、縦軸の座標を調整 */
view.frame = frame;
}
}
}
上手く表示されたが、何度もテストして位置の調整が必要そうで、あまり、お勧めできるもので内容に思えるので、次回は違う方法を模索してみたい。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Dialog - GitHub
関連情報
[iOS] UIAlertView 上に UIProgressView を載せる [2] キャンセルボタン表示
『Cocoaの日々』いつも参考にさせていただいています。ありがとう!助かります。
[iOS]アプリ内課金(レシートの確認)
アプリケーションは購入情報を得ても、外部のサーバが購入情報によって何らかの対応を行う場合は、それが正しい物である事を確認できないといけない。ということで、Storeレシートの確認に挑戦する。
外部サーバとの通信が必要ない場合。例えば、購入されると、アプリケーション内の機能制限フラグを落として、隠し機能を有効にする場合は、前回までの方法で対応できる。サーバから、購入に対応するデータを取得して、アプリケーションで利用できるようにする場合、外部のサーバへの通知が本当に正しいのか気にする必要があるようだ。そこで、購入が成功するとStoreKitからレシートを渡されるので、されを外部サーバに渡し、外部サーバがSoteにレシートの内容が正しい事を確認するという仕組みのようだ。
サンプルでは、購入成功を受けたメソッドで、簡易的にアプリケーションからStoreにレシートの内容を確認している。
まず初めに、サンプル・コードはBASE64にエンコードするコードだ。簡易なもので、テストも不十分なのであしからず。
#define BASE64PAD @"="
static const char base64Alphabet[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
- (NSString *)stringEncodedWithBase64:(NSData *)data
{
if (! data) return nil;
NSUInteger dataLen = data.length;
unsigned char *dataBytes = (unsigned char *)[data bytes];
NSMutableString *str = [[NSMutableString alloc] init];
NSUInteger dataIndex = 0;
while (dataIndex < dataLen) {
char d[3] = {0, 0, 0};
d[0] = dataBytes[dataIndex];
if ((dataIndex + 1) < dataLen)
d[1] = dataBytes[dataIndex + 1];
if ((dataIndex + 2) < dataLen)
d[2] = dataBytes[dataIndex + 2];
NSUInteger bit6 = 0;
char s[5];
bit6 = (d[0] >> 2) & 0x3F;
s[0] = base64Alphabet[bit6];
bit6 = ((d[1] >> 4) & 0x0F) | ((d[0] << 4) & 0x3F);
s[1] = base64Alphabet[bit6];
bit6 = ((d[2] >> 6) & 0x03) | ((d[1] << 2) & 0x3F);
s[2] = base64Alphabet[bit6];
bit6 = d[2] & 0x3F;
s[3] = base64Alphabet[bit6];
s[4] = '\0';
[str appendString:[NSString stringWithCString:s encoding:NSASCIIStringEncoding]];
dataIndex += 3;
}
if (dataIndex < dataLen) {
NSRange aRange = NSMakeRange(dataLen + 2, (dataIndex - dataLen));
[str replaceCharactersInRange:(NSRange)aRange withString:BASE64PAD];
}
NSUInteger padNum = [str length] % 4;
for (NSUInteger i = 0; i < padNum; i++) {
[str appendString:BASE64PAD];
}
return str;
}
自分自身が分かりやすい事を優先したコードなので、ちょっと、恥ずかしい。
次はレシートを確認するコードだ。
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
/* NSURL *url = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]; */
NSURL *url = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *json = [NSString stringWithFormat:@"{\"receipt-data\" :\"%@\"}",
[self stringEncodedWithBase64:transaction.transactionReceipt]];
[request setHTTPBody:[json dataUsingEncoding:NSUTF8StringEncoding]];
NSURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
通信先のURLは"https://buy.itunes.apple.com/verifyReceipt"が正しいのだが、テストなのでサンドボックス環境の"https://sandbox.itunes.apple.com/verifyReceipt"を設定している。
transaction.transactionReceiptがレシートで、これを外部のサーバに私て、外部サーバが上記のようなコードを例えば、PythonやRuby、PHPで記述して、レシートを確認するという事だ。
以下が結果。
{
"receipt":{
...
"status":0}
正しいということか。
関連情報
In App Purchase プログラミングガイド
Technical Note TN2259
Adding In-App Purchase to your iOS and Mac Applications
失敗しないiOS In-App Purechaseプログラミング
『A Day In The Life』参考にさせていただきました。分かりやすく、具体的な説明、ありがとうございます。
[iOS]アプリ内課金(購入する)
前回は、登録したプロダクトの情報が取得されたまでで、今回はそれを選択して、購入してみる。
開発者サイトの情報には制限があるので、概要の説明で我慢して欲しい。
著者の経験から最初にやっておいた方がいいのが、アプリケーション内課金用の試験アカウントを用意だ。試験用とはいえ、通常のApple IDと同様な条件で作成なので、色々と準備しておく事があるので、デバッグ段階だと慌ててしまうと思う。やり方だが、iTunes ConnectのManag UsersでTest Userを作成する。操作自体は、それ程、難しいものではないと思う。
購入処理を行うクラスに、SKPaymentTransactionObserverを設定する。
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
@interface ViewController : UIViewController
@end
初期化を行う箇所に以下のコードを追加する。サンプルでは、SKPaymentTransactionObserverプロトコルを設定した、ビュー・コントローラを指定している。
- (void)viewDidLoad
{
[super viewDidLoad];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
購入やリストアのトランザクションに対応するメソッドを実装する。
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
NSLog(@"%s, SKPaymentTransactionStatePurchased", __func__);
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(@"%s, SKPaymentTransactionStateFailed", __func__);
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(@"%s, SKPaymentTransactionStateRestored", __func__);
[self restoreTransaction:transaction];
default:
break;
}
}
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
//[self recordTransaction: transaction];
//[self provideContent: transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
if (transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"transaction.error.code is not SKErrorPaymentCancelled");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
//[self recordTransaction: transaction];
//[self provideContent:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
本当に仮の実装。前回のプロダクト情報を取得するメソッドで、取得したプロダクトを購入する。
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
for (SKProduct *product in response.products) {
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
シミュレータで実行。すると、本当に購入するか聞いてきた。
購入を選択すると、シミュレータなのでIDを聞いてきた。先ほど作成した試験用IDを指定すればいい。
すると、完了が通知された。
デバッグ出力も印字されている。
-[ViewController paymentQueue:updatedTransactions:], SKPaymentTransactionStatePurchased
関連情報
In App Purchase プログラミングガイド
[OSX][iOS]第52回Cocoa勉強会(関東)
記録によると第1回が開催されたのが2003年10月4日なので、今年で9年目。その間、Cocoaを巡る周りの状況は大きく変わったものだ。感慨深い。
発表の内容をざっと紹介すると、iCabで使用されているAppLink、見積もりのお話、クラウド化する開発者の作業環境のディスカッション。そして、今回はディスカッションの時間を長めにとって、色々な話題が議論された。7月からアプリケーション開発を主な業務にして行く予定なので、とても参考にある貴重な情報が聞けて、有意義だった。
[iOS]アプリ内課金(プロダクト情報の取得)
アプリ内課金は、暫くは使わないだろうと考え後回しにしていたのだが、避ける事が出来なくなったようだ。
Store Kitフレームワークを使ったIn-App Purchaseの何が面倒かというと、環境を用意するところだと思う。
今回はサンプル・コードは用意できていない。なぜなら、登録の都合上、弊社の開発中のアプリケーションとしてアプリケーションを制作したからだ。申し訳ない。
iOS Provisioning Portalで、App IDを登録する。
iTunes ConnectのManage Your Applicationsで、アプリケーションを追加する。
Manage In-App Purchasesでプロダクトを追加する。
XcodeのiOSアプリケーションのプロジェクトを生成して、StoreKit.frameworkを追加し、StoreKit/StoreKit.hをimportする。
SKRequestDelegateとSKProductsRequestDelegateの2つのプロトコルに対応させる。
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
@interface ViewController : UIViewController <SKRequestDelegate, SKProductsRequestDelegate>
@end
アプリ内課金に対応しているか確認し、指定したプロダクトIDの情報を取得する。
- (void)viewDidLoad
{
[super viewDidLoad];
if ([SKPaymentQueue canMakePayments]) {
NSLog(@"ユーザにStoreを表示する");
[self requestProductData];
}
else {
NSLog(@"購入できないことをユーザに警告する");
}
}
プロダクトIDの情報取得を要求し、結果を受け取るメソッドは以下のとおり。
- (void)requestProductData
{
SKProductsRequest *request= [[SKProductsRequest alloc]
initWithProductIdentifiers:
[NSSet setWithObject:<プロダクトID>]];
request.delegate = self;
[request start];
}
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProduct = response.products;
NSLog(@"%@", myProduct);
SKProduct *skp = [myProduct objectAtIndex:0U];
NSLog(@"%@", skp.localizedDescription);
}
準備は面倒だが、この程度なら、あっけなく動いた。
関連情報
In App Purchase プログラミングガイド
[iOS]Facebook SDKを利用する
Facebook Developersサイトの説明が素晴らしいのと、アプリケーションとして登録する必要があるため、サンプルとしては困る部分があるので、著者が工夫した事について説明する。
Facebook SDKを使ったアプリケーションをGitHubで公開する場合に気をつけた事に、Facebook SDKのソースをサンプルのgitに含めないことだ。前回作成した静的ライブラリ一式をXcodeのプロジェクトにドラッグ&ドロップすると、それをgitに追加しようとする。
そこで、Finderの操作で静的ライブラリ一式をプロジェクトのフォルダにコピーしておいて、それをXcodeのプロジェクトにドラッグ&ドロップする。
こうすれば、既にディレクトリ上に存在する為、Xcodeはそれをgitに追加しようとしないようだ。
Facebook対応で最初に行うのは、OAuth対応だと思うが、機器上にFacebookアプリがない場合はSafariで、ある場合はFacebookアプリで認証を行うようだ。なので、シミュレータと実機で異なる処理の流れになる。
そして、認証後にアプリケーションに戻ってくる為に、URLスキームを登録するようだ。
URLスキームの頭の"fb"でFacebookであるとの、その後ろにつけるアプリケーションIDでアプリケーションを区別して、認証後に戻るアプリケーションを呼び出す仕組みのようだ。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Facemash - GitHub
関連情報
Facebook開発者
iOS Tutorial
facebookの開発者サイト。
facebook-ios-sdk
Facebook SDK for iOS
iOSチュートリアル
Facebook開発者の為のサポートサイト fb.developers'+
[iOS]Facebook SDKの静的ライブラリ
Facebook SDK for iOSの情報は豊富にあると思うので、著者が実施した手順について説明する。
豊富な情報があるが、新旧の情報もあり迷うと思うが、著者はFacebook開発者サイトの情報をベースに作業を進めた。
まずは、以下のサイトで制作しているアプリケーションの情報を登録した。ただし、今回はサンプルの為の登録の為、仮の内容なので後でこれが問題になると思う。やってみないとどうなるか分からないので、問題が発生したら考えることにする。
https://developers.facebook.com/apps/
Facebook SDKのソースコードをプロジェクトに追加する方法と、静的ライブラリを作成して、それをプロジェクトに追加する方法があるようだが、ARCを有効にした場合、SDKのソースはARC未対応、ようするにreleaseが残っているのでそれの対応が面倒という事で、静的ライブラリの作成がおすすめだそうだ。
著者は、git closeでSDKを取得したが、SDKを置いているディレクトリで以下のコマンドを実行した。
$ cd ~/facebook/
$ ~/facebook/facebook-ios-sdk/scripts/build_facebook_ios_sdk_static_lib.sh
Project Home: ~/facebook/facebook-ios-sdk
Start Universal facebook-ios-sdk SDK Generation
Step 1 : facebook-ios-sdk SDK Build Library for simulator and device architecture
Build settings from command line:
SDKROOT = iphonesimulator5.0
SYMROOT = ~/facebook/facebook-ios-sdk/build
=== CLEAN NATIVE TARGET facebook-ios-sdk OF PROJECT facebook-ios-sdk WITH CONFIGURATION Release ===
Check dependencies
:
** CLEAN SUCCEEDED **
=== BUILD NATIVE TARGET facebook-ios-sdk OF PROJECT facebook-ios-sdk WITH CONFIGURATION Release ===
Check dependencies
:
** BUILD SUCCEEDED **
Build settings from command line:
SDKROOT = iphoneos5.0
SYMROOT = ~/facebook/facebook-ios-sdk/build
=== CLEAN NATIVE TARGET facebook-ios-sdk OF PROJECT facebook-ios-sdk WITH CONFIGURATION Release ===
Check dependencies
:
** CLEAN SUCCEEDED **
=== BUILD NATIVE TARGET facebook-ios-sdk OF PROJECT facebook-ios-sdk WITH CONFIGURATION Release ===
Check dependencies
:
** BUILD SUCCEEDED **
Step 2 : Remove older SDK Directory
Step 3 : Create new SDK Directory Version
Step 4 : Create combine lib files for various platforms into one
Step 5 : Copy headers Needed
Step 6 : Copy other file needed like bundle
Finished Universal facebook-ios-sdk SDK Generation
You can now use the static library that can be found at:
~/facebook/facebook-ios-sdk/lib/facebook-ios-sdk
Just drag the facebook-ios-sdk directory into your project to include the Facebook iOS SDK static library
2回ビルドしているのは、シミュレータ用と実機用という事のようだ。
SDKのディレクトリに移動してみると、libディレクトリが作成されていて、そこにライブラリのアーカイブとヘッダ・ファイル類が置かれていた。
$ cd facebook-ios-sdk/
$ ls
README.mdown
build
$ ls lib
facebook-ios-sdk
$ ls lib/facebook-ios-sdk/
FBConnect.h
FBDialog.bundle
FBDialog.h
FBFrictionlessRequestSettings.h
FBLoginDialog.h
FBRequest.h
Facebook.h
JSON.h
今回はここまで。続きは次回。
関連情報
Facebook開発者
iOS Tutorial
facebookの開発者サイト。
facebook-ios-sdk
Facebook SDK for iOS
iOSチュートリアル
Facebook開発者の為のサポートサイト fb.developers'+
[OSX][iOS]開発規模の見積もり
ソフトウェア開発の色んな局面で出てくるものとして、ルールか出来ない領域だとは分かるが、ルール化による基準を設けないと、結果の検証が出来ず、次に繋がらない。規模の見積もりも同様だろう。
過去の経験から規模を予想する方法は、人によって結果の差があったり、ちょっとした差異の影響を結果に取り込む事ができないという課題があるが、経験が少ないと、予想できないという問題がある。個人で仕事をやっていて先輩のアドバイスを聞けない場合やスタートアップ時は、これは辛い。
それで、色々調べてみて見つけた方法を紹介する。
例えば、アンケート等で得られた情報から、その国の国民について予測する方法があると思う。それと同じ考えだ。
全体の個数が分かるという考えから、要件から開発する機能をリストアップし、それをある程度の粒度に分割する。
その中から、数個選んでプロトタイプを作成する。そもそも、アプリケーション開発では試してみないと分からない部分があり、事前にプロトタイプを作成すると思うが、それを規模の見積もりに利用するという事だ。
作成したプロトタイプのステップ数を計算する。
次に標本平均と標準偏差を計算する。そして、以下の計算式から、95%と99%の下限・上限の推定値を計算する。
具体的な計算方法については、『SEのための見積りの基本』に計算用のExcelファイルの作り方が説明されているので、それを参考にして欲しい。
実際やって気がついた事は、本当に無作為抽出になっているか?だ。プロトタイプが作りやすい項目が選ばれていないか?プロトタイプもきちんと完成させるところを省略しがちなので、選択する機能の項目には偏りが発生していると思われる。なので、精度を高めるには、機能分割の方法と、プロトタイプを作成する機能が、ある機能の一部というより、それ自体が独立した小さな機能になるように心がけるということかな?と思っている。
関連情報
SEのための見積りの基本 (SEの現場シリーズ)
必要になった今、とても参考になっている。