Mac-ISV Macで、Micro-ISV

287月/10

Cocoaアプリの起動までのプロセスとイベント処理の仕組み

この資料は内部で勉強用に作成したものでCocoa Programming for Mac OS Xの内容をベースにしております。Cocoaアプリの起動からユーザイベントの処理までの仕組みが記述されています。

この文書に含まれている一部図は同書、あるいはアップルのドキュメントから抜粋しておりますのであくまでも参考にだけ使ってください。なお、この文書の説明は概要までで止めております。より詳しい内容が知りたい場合は同書、あるいはアップルの資料を参考にしてください。

HowCocoaAppRuns

Filed under: MacDev No Comments
257月/10

UIView to draw sweet graphs

iPhoneアプリでグラフを表示したいと思い使える物がないか検索してみました。
すると下記図のようなクールなグラフを簡単に描けるライブラリがありました。その名もs7graphview

使い方はとても簡単!

http://code.google.com/p/s7graphview/

上記リンクの右側にあるFeatured downloads:という所のs7graphview-basic-release.zipをダウンロードしてファイル一式を自分のプロジェクトに追加して使います。

ちなみに、DemoS7GraphView.zipというプロジェクトは私の環境では原因不明のエラーによりデモを実行してみることは出来ませんでした。

s7graphview-basic-release.zipを解凍すると下記のような3つのファイルが存在しており自分のプロジェクトの中で

#import "S7GraphView.h"することによりAPIを使えるようになります。

使い方はTableViewの実装と同様にグラフを描くためにUIVIewControllerでDataSourceを実装してあげるだけでグラフを描いてくれます。

まず、ヘッダーファイルで

#import "S7GraphView.h"

@interface TestViewController : UIViewController<S7GraphViewDataSource> {

S7GraphView *graphView;

}

@property (nonatomic, retain) S7GraphView *graphView;

のように宣言し

ソースフィアルで色やデータソースなどを設定し(使った後のメモリ解放処理も忘れないようにしましょう)

@synthesize graphView;

// Implement loadView to create a view hierarchy programmatically, without using a nib.

- (void)loadView {

//self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

self.graphView = [[S7GraphView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

self.view = self.graphView;

self.graphView.dataSource = self;

//self.view.backgroundColor = [UIColor yellowColor];

}

- (void)viewDidLoad {

[super viewDidLoad];

// Uncomment the following line to display an Edit button in the navigation bar for this view controller.

// self.navigationItem.rightBarButtonItem = self.editButtonItem;

NSNumberFormatter *numberFormatter = [NSNumberFormatter new];

[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];

[numberFormatter setMinimumFractionDigits:0];

[numberFormatter setMaximumFractionDigits:0];

self.graphView.yValuesFormatter = numberFormatter;

NSDateFormatter *dateFormatter = [NSDateFormatter new];

[dateFormatter setTimeStyle:NSDateFormatterNoStyle];

[dateFormatter setDateStyle:NSDateFormatterShortStyle];

self.graphView.xValuesFormatter = dateFormatter;

[dateFormatter release];

[numberFormatter release];

self.graphView.backgroundColor = [UIColor blackColor];

self.graphView.drawAxisX = YES;

self.graphView.drawAxisY = YES;

self.graphView.drawGridX = YES;

self.graphView.drawGridY = YES;

self.graphView.xValuesColor = [UIColor whiteColor];

self.graphView.yValuesColor = [UIColor whiteColor];

self.graphView.gridXColor = [UIColor whiteColor];

self.graphView.gridYColor = [UIColor whiteColor];

self.graphView.drawInfo = NO;

self.graphView.info = @"Load";

self.graphView.infoColor = [UIColor whiteColor];

//When you need to update the data, make this call:

[self.graphView reloadData];

}

- (void)dealloc {

[graphView release];

graphView = nil;

[super dealloc];

}

DataSourceの中身を実装してあげるだけでグラフを表示してくれます。

#pragma mark -

#pragma mark end

#pragma mark protocol S7GraphViewDataSource

- (NSUInteger)graphViewNumberOfPlots:(S7GraphView *)graphView {

/* Return the number of plots you are going to have in the view. 1+ */

return 2;

}

- (NSArray *)graphViewXValues:(S7GraphView *)graphView {

/* An array of objects that will be further formatted to be displayed on the X-axis.

The number of elements should be equal to the number of points you have for every plot. */

NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:101];

for ( int i = -50 ; i <= 50 ; i ++ ) {

[array addObject:[NSNumber numberWithInt:i]];

}

return array;

}

- (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex {

/* Return the values for a specific graph. Each plot is meant to have equal number of points.

And this amount should be equal to the amount of elements you return from graphViewXValues: method. */

NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:101];

switch (plotIndex) {

default:

case 0:

for ( int i = -50 ; i <= 50 ; i ++ ) {

[array addObject:[NSNumber numberWithInt:i*i]]; // y = x*x

}

break;

case 1:

for ( int i = -50 ; i <= 50 ; i ++ ) {

[array addObject:[NSNumber numberWithInt:i*i*i]]; // y = x*x*x

}

break;

}

return array;

}

このようなすばらしいライブラリを作って頂いた作者に感謝です。m(__)m

247月/10

MoneyViewが1.5にバージョンアップ

MoneyViewのバージョンが1.15から1.5にアップしました。

お客さんの要望であった毎日の簡単なメモをとる機能を追加しました。そして統計画面で表示される詳細パネールでも各費目のメモが見えるようにしました。他に細かいバグ対応を行いました。

試用期間を経て購入してくださるお客さんもいらっしゃって、本当にありがたいと思っています。

でも、もっとお客さんの声が聞きたいと思っています。足りない機能、不便なところがあったら教えていただければ幸いです。

Filed under: MoneyView No Comments
237月/10

アプリの雑誌紹介の効果

以前MoneyViewがMac PeopleやMac Fanに紹介されたことを書いたことがありましたが、それについて。

両誌とも日本ではメジャーなMac雑誌だったし、Mac Fanでは1ページ全部の紙面を利用して紹介されましたのである程度販売に効果があるのではと期待していましたが、残念ながらその効果はほぼ見えませんでした。逆にあるダウンロードサイトやアップルジャパンのダウンロードページに紹介された際の効果がすごかったんです。

これじゃ、マーケティングの手段としての雑誌はちょっと考える必要がありそうです。(もちろん、これは単純に雑誌の問題ではなく我らの製品とその雑誌との相性とも言えますので他の方にも同じく適用されるとは思っていませんが。)

237月/10

NSTextField関連バグ?

今作成中のアプリに、一つのウィンドウの上に子ウィンドウとして新しいウィンドウを表示する部分があります。このウィンドウの内容を表示するためにNSObjectControllerでバインディングしたテキストフィールドを複数おいてますが、これらの表示に問題が。

最初に表示する分には問題がありませんが、どっかのフィルドをマウスでクリックして選択したままそのウィンドウを閉じてしまうと後でまた開いた時にそこの値が表示できない問題が発生しました。そのテキストフィルドを再びクリックすると値が表示されますが、他のフィルドをクリックするとまた値が表示されなくなります。

最初はバインディングの問題かと思い、色々いじってみましたが改善できず、ウェブにもそれらしき情報はありませんでした。

色々試していたところ、テキストフィールドの背景色を表示しない、また輪郭を書かないように設定するとこの問題が解消されることを確認。

今のところこの設定で問題ないのでこれで行こうとしていますが、これはNSTextFieldのバグでしょうか?

Filed under: MacDev No Comments
274月/10

MoneyViewがMac Peopleに紹介されました。

先ほどMoneyViewがMac Fan誌に紹介されたと書いたばかりですが、実は同じ日に発売されたMac People誌にも紹介されました。ただ、ここでは特にレビュー記事とかではなく、オンラインウェアの紹介ページの一部として掲載されました。

知り合いの人に自慢したら「おお、マック本デビューだね!」と言われました。とてもうれしいです!

274月/10

MoneyViewのレビュー記事がMac Fanに掲載されました。

今日発売されたMac FanにMoneyViewのレビュー記事が掲載されました。分量は1ページでかなりいい評価をして頂きました。

MoneyViewについて客間的な観点でのレビュー記事を読むのは初めてだったので、ただ面白かっただけでなく、かなり役に立ちました。

さて、大事なのはこれからですね。MoneyViewはまだまだバージョンも低く、機能も足りてないと思います。実はお客さんからの貴重な意見を直接頂いたりもしておりますので、これからもより充実なものにするために頑張らないといけないですね。うん、うん、頑張らせて頂きます!

それと、読者プレゼントとしてMoneyViewのライセンスを2つ差し上げることとなっております。ご興味のある方はどうぞ、ご応募ください。プレゼント応募は下記のページからできますよ。

Micro-ISVをやっているもののしては、このように雑誌の記事になった場合にどれくらいの効果があるかを知るチャンスなのですごく興味深いと思っています。

194月/10

COCOAでのASIHTTPRequestとJSONの使い方

今回はCOCOAのHttp通信ライブラリASIHTTPRequestとJSONライブラリSBJONを紹介したいと思います。

ASIHttpRequest 英文ドキュメント
http://allseeing-i.com/ASIHTTPRequest/How-to-use

JSON
http://blog.zachwaugh.com/post/309924609/how-to-use-json-in-cocoaobjective-c

基本的な使い方は

	NSURL *ns_url = [NSURL URLWithString:@“http://test.com”];
	ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:ns_url];
	[request setPostValue:user_id forKey:@“loginId”];
	[request setPostValue:pass forKey:@“loginPass”];

ASIRequest ObjectにURLはtest.com、パラメタはpost形式の値を設定します。
同期(synchronous)の場合はrequestを送ってエラーがなかったらresposeを取得する処理を順次、記述します。

	[request startSynchronous];

	NSError *error = [request error];
	NSString *response;

	if (!error) {
		response = [request responseString];
	}else{
		return false;
	}
	...

非同期の場合は

受信処理を行うデリゲートを設定して非同期的な処理を任せます。

主メッソドの例
※この場合はデリゲートとして自分を指定します。

   [request setDelegate:self];
   [request startAsynchronous];

デリゲートメソッドを実装

- (void)requestFinished:(ASIHTTPRequest *)request
{
   // Use when fetching text data
   NSString *responseString = [request responseString];

   // Use when fetching binary data
   NSData *responseData = [request responseData];

	処理
	........

}

- (void)requestFailed:(ASIHTTPRequest *)request
{
   NSError *error = [request error];

	エラー処理
	........
}

STRINGデータをJSONに変換するには SBJSONを使います。
例)

	SBJSON *json = [SBJSON new];

	NSArray *statuses = [parser objectWithString:json_string error:nil];
	[json release];

	// Each element in statuses is a single status
	// represented as a NSDictionary
	for (NSDictionary *status in statuses)
	{
// You can retrieve individual values using objectForKey on the statusNSDictionary
// This will print the tweet and username to the console
	NSLog(@"%@ - %@", [status objectForKey:@"text"],
		[[status objectForKey:@"user"] objectForKey:@"screen_name"]);
	}

上記のようにjson_stringからDictionary Objectを生成して変換します。

実際のASIHTTPと連携したソースはこんな感じになります。

+ (NSArray *)scheduleWithId:(NSString*)user_id pass:(NSString*)pass date:(NSDate *)date {

	//日付から年と月を取得
	NSDateFormatter *outputFormatter = [[[NSDateFormatter alloc] init] autorelease];
	[outputFormatter setDateFormat:@“yyyy-MM-dd”];
	NSString *newDateString = [outputFormatter stringFromDate:date];
	NSArray *dateArray = [newDateString componentsSeparatedByString:@“-“];
	NSLog(@“current date : %@“,dateArray);

	//URL及びパラメターを設定
	NSURL *ns_url = [NSURL URLWithString:TEST_URL];
	ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:ns_url];
	[request setPostValue:user_id forKey:@“loginId”];
	[request setPostValue:pass forKey:@“loginPw”];
	[request setPostValue:[dateArray objectAtIndex:0] forKey:@“currentYear”];
	[request setPostValue:[dateArray objectAtIndex:1] forKey:@“currentMonth”];

	[request startSynchronous];

	NSError *error = [request error];
	NSString *response;

	if (!error) {
		//responseデータをTRIMする
response = [[request responseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

		//responseデータから特殊文字を処理  &#034; -> \
		response = [response stringByReplacingOccurrencesOfString:@“&#034;” withString:@“\””];
	}else{
		return false;
	}

	NSLog(@“response : %@“,response);

	SBJSON *json = [SBJSON new];
	NSDictionary *statuses = [json objectWithString:response error:nil];
	[json release];
	NSLog(@“json : %@“,statuses);

	// Each element in statuses is a single status
	// represented as a NSDictionary
	NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
	[dateFormatter setDateFormat:@“yyyy-MM-dd”];

	NSArray *scheduleItems = [statuses objectForKey:@“scheduleItems”];
	for (NSMutableDictionary *schedule in scheduleItems)
	{
		[schedule setObject:[dateFormatter dateFromString:[schedule objectForKey:@“startDate”]] forKey:@“startDate”];
		[schedule setObject:[dateFormatter dateFromString:[schedule objectForKey:@“endDate”]] forKey:@“endDate”];
		// You can retrieve individual values using objectForKey on the status NSDictionary
		// This will print the tweet and username to the console
		NSLog(@“title : %@ , contents : %@ , startDate : %@ , endDate : %@ “,
			  [schedule objectForKey:@“title”] ,
			  [schedule objectForKey:@“contents”] ,
			  [schedule objectForKey:@“startDate”] ,
			  [schedule objectForKey:@“endDate”]
			  );
	}
	return scheduleItems;
}

サーバからは
scheduleItems : {"title": “タイトル”, “contents”: “” , “startDate”:”2010-3-10” ,“endDate”:”2010-3-11”}
このような形式のデータか送られて来るので最初にDictionaryからscheduleItemsを取得し、
配列データを取得します。

Filed under: MacDev No Comments
134月/10

AquaticPrimeでのライセンスメールの文字化け

AquaticPrimeはライセンスフレームワークで有名なオープンソースのフレームワークです。
Paypal及びKagiなどの決済サイトと連動しライセンスの自動発行も可能でサンプルソースも提供していますが、php阪のUNICODE対応にちょっと不具合があるので修正が必要です。

元ソース

AquaticPrimePayPal.phpの60行目

for ($i = 1; $i < count($lines); $i++)
{
	list($lineKey, $lineValue) = explode("=", $lines[$i]);
	$keyarray[urldecode($lineKey)] = urldecode($lineValue);
}

$product = $keyarray['item_name'];
$name = $keyarray['first_name']." ".$keyarray['last_name'];
$email = $keyarray['payer_email'];
$amount = $keyarray['mc_gross'];
$count = $keyarray['quantity'];
// RFC 2822 formatted date
$timestamp = date("r", strtotime($keyarray['payment_date']));
$transactionID = $keyarray['txn_id'];

// Create our license dictionary to be signed
$dict = array("Product" => $product,
			  "Name" => $name,
			  "Email" => $email,
			  "Licenses" => $count,
			  "Timestamp" => $timestamp,
			  "TransactionID" => $transactionID);

$license = licenseDataForDictionary($dict, $key, $privateKey);

元スースはurldecode後のデータをそのままライセンス発行とメール送信に使用しています。
しかしもしデータの中にASCII以外の文字列が入った場合には正しく動くという保証はありません。

UNICODE文字に対応する為には下記のような処理が必要です。

...
$name = convert_to($keyarray['first_name']." ".$keyarray['last_name'] , "UTF-8");
$base64_name = base64_encode($name);
.....
$dict = array("Product" => $product,
			  "Name" => $base64_name,
			  "Email" => $email,
			  "Licenses" => $count,
			  "Timestamp" => $timestamp,
			  "TransactionID" => $transactionID);

$license = licenseDataForDictionary($dict, $key, $privateKey);

convert関数
function convert_to ( $source, $target_encoding )
{
    $encoding = mb_detect_encoding( $source, "auto" );

    $target = str_replace( "?", "[question_mark]", $source );
    $target = mb_convert_encoding( $target, $target_encoding, $encoding);
    $target = str_replace( "?", "", $target );
    $target = str_replace( "[question_mark]", "?", $target );
    return $target;
}

まず 日本語の$name 属性はUTF-8に変換します、ライセンス発行ロジックには
UTF-8文字列をそのままでは使えない為、$name変数をbase64形式に変換します。
メールにはUTF-8にencodeした$nameを、ライセンス発行には$base64_nameを
使います。

AquaticPrime.php 196行目

function sendMail($to, $from, $subject, $message, $license, $name, $bcc='')
{
	// Create a random boundary
	$boundary = base64_encode(MD5((string)rand()));
	$message = mb_convert_encoding($message,"ISO-2022-JP", "UTF-8");

最後にメール送信ヘッダにUTF-8のデータをISO-2022-JPの形式で変換して送信すれば完了です。

Filed under: MacDev No Comments
134月/10

NSDateのTips

COCOA開発で日付の処理はよくある事です。
今日はいくつかの個人的Tipを共有します。

1. 日付間の日数を計算

#define TIME_INTERVAL_FOR_DAY 86400
// Calculate and return number of days between two dates.
+ (int) numberOfDaysBetween:(NSDate*)firstDate and:(NSDate*)secondDate {
	NSTimeInterval interval = [secondDate timeIntervalSinceDate:firstDate];
       // Add 1 for correct number of days
	return (((int)interval) / TIME_INTERVAL_FOR_DAY)+1;
}

2. 日付に月を加算

//Add Month from Date
+ (NSDate *) date:(NSDate *)fromDate byAddingMonth:(int)monthOffset
{
	NSDateComponents *month = [[NSDateComponents alloc] init];
	[month setMonth:monthOffset];
	NSDate *monthStartDateWithOffset =
[[NSCalendar currentCalendar] dateByAddingComponents:month toDate:fromDate options:0];
	[month release];
	return monthStartDateWithOffset;
}

3. 日付から一日のデータ取得
iPhoneの開発でデータを日付で絞って取得することはよくある処理でしょう。
※下記のソースはiPhone SDKでcore dataを使った場合です


- (NSArray *)dailyDataFromDate:(NSDate *)from_date {
	NSCalendar *calendar= [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

	//①
	NSDateComponents *initComponents = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:from_date];
	//②
	NSDateComponents *componentsToAdd = [[NSDateComponents alloc] init];
	[componentsToSubtract setDay:+1];

	NSDate *fromDate = [calendar dateFromComponents:initComponents];
	NSDate *toDate = [calendar dateByAddingComponents: componentsToAdd toDate:fromDate options:0];

	NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:@"(timeStamp >= %@ ) and (timeStamp < %@)",fromDate,toDate];
	//③
	NSArray *matchingFetchedObjects = [fetchedResultsController.fetchedObjects filteredArrayUsingPredicate:requestPredicate];

	[calendar release];
	[componentsToSubtract release];

	return matchingFetchedObjects;
}

まず、①は日付の時刻を初期化するコンポーネントを生成して一日の始発時間を取ります。
②では一日を追加するコンポーネントを生成します。
③でArrayに①と②で取得した日付をフィルタリングを掛けてデータを取得します。

アップルのサイトを参考
http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtDates.html

Filed under: MacDev No Comments