— boreal-kiss.com

Archive
Tag "iPhone"

第8回、9回講義(2009/04/27、29、要iTunes)聴講。第8回はUITableView(+UIScrollView)について、第9回はデータの読み込み・書き込みについて幅広く紹介。

第8回

ゲストスピーカーにUITableViewの開発チームの人(Jason Beaver)を呼んでTableViewについて解説してもらっているがこれが失敗。レクチャー慣れしていならしくUITableViewについて淡々と解説していくだけで、時折ソースコードなどを交えながら解説するも、すでに自前で書き終えたソースをズラズラ見せられるだけなので何を説明したいのか伝わってこない。特に目新しいことを説明しているわけでもないので、これなら教本でじっくり理解した方が良い。(おそろしいことに第9回の解説はUITableViewをすでに理解しているという前提の上に進んでいく)

第9回

こちらのスピーカーはいつものEvan Doll。Data persistenceについて、Property listやData archivingといったCocoaでよく見るデータ読み書き方法だけでなく、SQLiteをiPhone OS内で使うところまでも実際のコードを交えて解説(むしろSQLiteメインな解説だった)。外部データを扱う場面でもJSONの解説に時間が割かれている(一瞬だがFlickrからデータを取得するデモ付き)。このiPhone appコースだが、世にある書籍に書かれていない内容を意識的に扱うようにしているように感じられとても好印象だ。

  • NSUserDefaultsもplistファイルを作成していることを知った。iPhoneシミュレーターで確認したらたしかにplistで保存されていた。これは便利。
    ~/Library/Application Support/iPhone Simulator/
    	User/Applications/APP_ID/Library/Preferences/
    
  • さらに洗練されたデータ構造としてCore Dataをちらっと紹介。iPhone OS 2.xでは使えないと強調していたのでOS 3.0から使えるようになるのかな。
  • 前々回(第7回)に引き続き、グローバル変数やシングルトンモデルを使うなと強調。前回は理由が納得できなかったが、特定のモジュールの単体テストができなくなることを理由に挙げていた。なるほど納得。
  • 重要な内容なのに講義最後5分間ぐらいしか解説されていないが、idやプロトコルで定義されているインスタンス変数(例えばdelegate)のSetter/Getterの書き方やリリース方法について。何故retainではなくassignを使うべきなのか理由が今イチ理解できず。絵にしてもらえたらよかった。
Read More

[iPhone] HTMLリンクのようなUIButtonのサブクラスでボタンタイトルの文字列の長さに応じてアンダーラインを引く方法を試行錯誤していたが、NSStringオブジェクトのCGSizeを返すメソッドであっさり解決した。

//  UIStringDrawing.h
//  UIKit
 
@interface NSString(UIStringDrawing)
 
- (CGSize)sizeWithFont:(UIFont *)font;
 
@end

修正前

//  HTMLLinkButton.m
 
- (void)drawRect:(CGRect)rect {
	CGFloat w = rect.size.width;
	CGFloat h = rect.size.height;
	CGFloat capHeight = self.font.capHeight;
	CGFloat xHeight = self.font.xHeight;
	CGFloat underlineLength = [_buttonTitle length] * xHeight;
	CGFloat startX = (w - underlineLength) / 2.0;
	CGFloat startY = (h + capHeight) / 2.0;
	CGFloat endX = startX + underlineLength;
	CGFloat endY = startY;
 
	CGContextRef context = UIGraphicsGetCurrentContext();
	CGContextSetStrokeColorWithColor(context, 
		_colorController.currentColor.CGColor);
	CGContextMoveToPoint(context, startX, startY);
	CGContextAddLineToPoint(context, endX, endY);
	CGContextStrokePath(context);
 
}

修正後

//  HTMLLinkButton.m
 
- (void)drawRect:(CGRect)rect {
	CGFloat w = rect.size.width;
	CGFloat h = rect.size.height;
	CGFloat titleWidth = [_buttonTitle sizeWithFont:self.font].width;
	CGFloat titleHeight = [_buttonTitle sizeWithFont:self.font].height;
	CGFloat descender = self.font.descender;
	CGFloat startX = (w - titleWidth) / 2.0;
	CGFloat startY = (h + titleHeight) / 2.0 + descender;
	CGFloat endX = startX + titleWidth;
	CGFloat endY = startY;
 
	CGContextRef context = UIGraphicsGetCurrentContext();
	CGContextSetStrokeColorWithColor(context, 
		_colorController.currentColor.CGColor);
	CGContextMoveToPoint(context, startX, startY);
	CGContextAddLineToPoint(context, endX, endY);
	CGContextStrokePath(context);
}
Read More

第6回、7回講義(2009/04/20、22、要iTunes)聴講。第6回はUIViewControllerについて、第7回はUINavigationControllerについて。

第6回

何もない。マジで何も感想がない。UIViewControllerを知らない人以外は見なくてよい(かも)。ただ、後半にNSUserDefaultsを使ったデータ保存の簡単なデモをやっていたのでData persistenceについて知らない人には有益(かも)。

第7回

第6回と異なり、良い意味で細かい情報満載だった。Dave Mark – Beginning iPhone DevelopmentErica Sadun – The iPhone Developer’s Cookbookに解説されていない有益な情報多数、UINavigationControllerは思っていた以上にフレキシブルなことがわかった。UINavigationControllerはUITableViewとあわせて説明されている場合が多いが(欲張りな教本では一緒くたに解説されていることが多い)、今回はUITableViewは一切用いず説明しているため構造が理解しやすくなっている。

  • 意外だったのだけど、UINavigationController配下(に限ったことではないが)のViewController間のデータの受け渡しはグローバル変数(Singletonモデルなど)を用いずに引数でちゃんと渡せと強調していた。コントローラーが再利用しにくくなるからとか何とか一般的な説明をしていたが、そもそもCocoaのコントローラー、特にViewControllerを再利用する機会があるのかどうか疑わしい。
  • UIViewController.navigationControllerというプロパティが存在した!UINavigationController配下のViewControllerはAppDelegateなどを呼び出さずにUINavigationControllerオブジェクトにアクセスできる(UINavigationController配下でない場合はもちろんnil)。
  • UIBarButtonSystemItemの存在を知った!APIドキュメント見たら20個ぐらい種類あって、しかもマーク画像まで掲載されていて感動。例えばプラスマーク表示したい時は、
    UIBarButtonItem *item = [[UIBarButtonItem alloc] 
    	initWithBarButtonSystemItem:UIBarButtonSystemItemAdd 
    	target:self 
    	action:@selector(add:)];
    self.navigationItem.rightBarButtonItem = item;
    [item release];

    (注意: ViewController内にadd:メソッドが定義済みと仮定)

  • Backボタン(戻るボタン)の名前の変更方法があった!例えば親ViewController内に
    self.title = @"This is my title";
     
    UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] 
    	initWithTitle:@"Back" 
    	style:UIBarButtonItemStyleBordered 
    	target:nil 
    	action:nil];
    self.navigationItem.backBarButtonItem = backButtonItem;
    [backButtonItem release];

    と書いておくと、子ViewController内に表示されるBackボタンのタイトルを”This is my title”から”Back”に表示変更できる。(Backボタンはtargetもactionも設定不要)

Read More

UIKitにはテキストにアンダーラインを付ける仕組みがない。そのため、HTMLのリンク表示のようなボタンを作るためにはアンダーラインを自前で描画してやらなければならない。ここで問題になるのが、アンダーラインの長さと描画位置が、アンダーラインを付加したい文字列の長さとフォントサイズに依存するということだ。以下のカスタムクラス(HTMLLinkButton)は、ボタンのタイトル文字列の長さとフォントサイズに比例してアンダーラインを可能な限り適切な位置に描画する。

使い方は簡単で、ボタンタイトルとリンク先URLを設定してやるだけ。例えば、UIViewControllerクラスに以下のように記述してやると上記画像のようなHTMLリンクボタンが表示される。リンクをクリックするとテキストがハイライトされ、指定のURLへナビゲートされる。

#import "HTMLLinkButton.h"
 
- (void)viewDidLoad {
    HTMLLinkButton *button1 = [[HTMLLinkButton alloc] 
		initWithTitle:@"Google" url:@"http://google.com/"];
	button1.frame = CGRectMake(50, 100, 100, 37);
	button1.font = [UIFont systemFontOfSize:24];
	[self.view addSubview:button1];
	[button1 release];
 
	HTMLLinkButton *button2 = [[HTMLLinkButton alloc] 
		initWithTitle:@"Yahoo" url:@"http://yahoo.com/"];
	button2.frame = CGRectMake(150, 100, 150, 37);
	button2.font = [UIFont systemFontOfSize:40];
	[self.view addSubview:button2];
	[button2 release];
}

先ほど、可能な限り適切な位置に描画する、と言ったのには訳がある。ボタンタイトルの文字列の扱いに以下のような仮定があるからである。

  • ボタン文字列の高さはUIButton.font.capHeight(現在使用されているフォントの大文字の高さ)に等しい
  • ボタン文字列の各文字の幅はUIButton.font.xHeight(現在使用されているフォントの小文字xの高さ。幅ではない)に等しい

これらの仮定を用いた処理はHTMLButtonLinkクラスのdrawRect:メソッドで行われている。改良の余地アリ。

(**追記2009/05/02: 文字列の長さ・高さの扱いは– (CGSize)sizeWithFont:(UIFont *)fontでスマートに解決した)

Source code

//  HTMLLinkButton.h
 
#import <UIKit/UIKit.h>
 
extern CGFloat const HTMLLinkButtonDefaultButtonWidth;
extern CGFloat const HTMLLinkButtonDefaultButtonHeight;
extern NSUInteger const HTMLLinkButtonDefaulltButtonNormalColor;
extern NSUInteger const HTMLLinkButtonDefaulltButtonHighlightedColor;
 
@interface HTMLLinkButton : UIButton {
	NSString *_buttonTitle;
	NSString *_url;
	UIColor *_normalColor;
	UIColor *_highlightedColor;
	UIColor *_currentColor;
}
@property (nonatomic, retain) NSString *buttonTitle;
@property (nonatomic, retain) NSString *url;
@property (nonatomic, retain) UIColor *normalColor;
@property (nonatomic, retain) UIColor *highlightedColor;
 
/**
 * The designated initializer.
 * @param title	Button title.
 * @param url URL string to link to.
 * @param normalColor Color of the button title in the normal state.
 * @param highlightedColor Color of the button title when highlighted.
 */
-(id)initWithTitle:(NSString *)title url:(NSString *)url 
	normalColor:(UIColor *)normalColor 
	highlightedColor:(UIColor *)highlightedColor;
 
/**
 * The same as the above initialize 
 * except for that the title color sets to default.
 * @param title	Button title.
 * @param url URL string to link to.
 */
-(id)initWithTitle:(NSString *)title url:(NSString *)url;
@end
 
//  HTMLLinkButton.m
 
#import "HTMLLinkButton.h"
#import "HTMLLinkButton-Private.h"
#import "UIColor-HexEncoding.h"
 
CGFloat const HTMLLinkButtonDefaultButtonWidth = 72.0;
CGFloat const HTMLLinkButtonDefaultButtonHeight = 37.0;
NSUInteger const HTMLLinkButtonDefaulltButtonNormalColor = 0x2255AA;
NSUInteger const HTMLLinkButtonDefaulltButtonHighlightedColor = 0xFF9900;
 
@implementation HTMLLinkButton
@synthesize buttonTitle = _buttonTitle;
@synthesize normalColor = _normalColor;
@synthesize highlightedColor = _highlightedColor;
@synthesize url = _url;
 
-(id)initWithTitle:(NSString *)title url:(NSString *)url 
			normalColor:(UIColor *)normalColor 
			highlightedColor:(UIColor *)highlightedColor{
	if (self = [super init]){
		self.buttonTitle = title;
		self.url = url;
		self.normalColor = normalColor;
		self.highlightedColor = highlightedColor;
		_currentColor = _normalColor;
		[self setup];
	}
	return self;
}
 
-(id)initWithTitle:(NSString *)title url:(NSString *)url{
	UIColor *normalColor = 
		[UIColor colorFromHex:HTMLLinkButtonDefaulltButtonNormalColor];
	UIColor *highlightedColor = 
		[UIColor colorFromHex:HTMLLinkButtonDefaulltButtonHighlightedColor];
	return [self initWithTitle:title 
			url:url normalColor:normalColor 
			highlightedColor:highlightedColor];
}
 
//Override
- (void)drawRect:(CGRect)rect {
	CGFloat w = rect.size.width;
	CGFloat h = rect.size.height;
	CGFloat capHeight = self.font.capHeight;
	CGFloat xHeight = self.font.xHeight;
	CGFloat underlineLength = [_buttonTitle length] * xHeight;
	CGFloat startX = (w - underlineLength) / 2.0;
	CGFloat startY = (h + capHeight) / 2.0;
	CGFloat endX = startX + underlineLength;
	CGFloat endY = startY;
 
	CGContextRef context = UIGraphicsGetCurrentContext();
	CGContextSetStrokeColorWithColor(context, _currentColor.CGColor);
	CGContextMoveToPoint(context, startX, startY);
	CGContextAddLineToPoint(context, endX, endY);
	CGContextStrokePath(context);
}
 
- (void)dealloc {
	[_buttonTitle release];
	[_url release];
	[_normalColor release];
	[_highlightedColor release];
	[_currentColor release];
    [super dealloc];
}
 
#pragma mark -
#pragma mark Setters
 
//Override
-(void)setFrame:(CGRect)frame{
	[super setFrame:frame];
	[self setNeedsDisplay];
}
 
//Override
-(void)setFont:(UIFont *)font{
	[super setFont:font];
	[self setNeedsDisplay];
}
 
//Override
-(void)setNormalColor:(UIColor *)color{
	if (_normalColor != color){
		[_normalColor release];
		_normalColor = [color retain];
		[self setNeedsDisplay];
	}
}
 
//Override
-(void)setHighlightedColor:(UIColor *)color{
	if (_highlightedColor != color){
		[_highlightedColor release];
		_highlightedColor = [color retain];
		[self setNeedsDisplay];
	}
}
@end
 
//  HTMLLinkButton-Private.h
 
#import <UIKit/UIKit.h>
#import "HTMLLinkButton.h"
 
@interface HTMLLinkButton (Private)
 
/**
 * @private
 */ 
-(void)setup;
-(void)changeTextColor;
-(void)touchUpInside;
@end
 
//  HTMLLinkButton-Private.m
 
#import "HTMLLinkButton-Private.h"
 
@implementation HTMLLinkButton (Private)
 
-(void)setup{
	[self setFrame:CGRectMake(0, 0, HTMLLinkButtonDefaultButtonWidth, 
		HTMLLinkButtonDefaultButtonHeight)];
	[self setTitle:_buttonTitle forState:UIControlStateNormal];
	[self setTitle:_buttonTitle forState:UIControlStateHighlighted];
	[self setTitleColor:_normalColor forState:UIControlStateNormal];
	[self setTitleColor:_highlightedColor forState:UIControlStateHighlighted];
	[self addTarget:self action:@selector(changeTextColor) 
		forControlEvents:UIControlEventTouchDown];
	[self addTarget:self action:@selector(changeTextColor) 
		forControlEvents:UIControlEventTouchDragExit];
	[self addTarget:self action:@selector(changeTextColor) 
		forControlEvents:UIControlEventTouchDragEnter];
	[self addTarget:self action:@selector(touchUpInside) 
		forControlEvents:UIControlEventTouchUpInside];
}
 
-(void)changeTextColor{
	if (_currentColor == _normalColor){
		_currentColor = _highlightedColor;
	}
	else{
		_currentColor = _normalColor;
	}
	[self setNeedsDisplay];
}
 
-(void)touchUpInside{
	[self changeTextColor];
	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:_url]];
}
 
@end
 
// UIColor-HexEncoding.h
 
#import <UIKit/UIKit.h>
 
@interface UIColor (HexEncoding)
+(UIColor *)colorFromHex:(NSUInteger)color24;
@end
 
// UIColor-HexEncoding.m
 
#import "UIColor-HexEncoding.h"
 
@implementation UIColor (HexEncoding)
 
+(UIColor *)colorFromHex:(NSUInteger)color24{
	CGFloat r = (color24 >> 16) / 255.0f;
	CGFloat g = (color24 >> 8 & 0xFF) / 255.0f;
	CGFloat b = (color24 & 0xFF) / 255.0f;
	return [UIColor colorWithRed:r green:g blue:b alpha:1.0f];
}
 
@end
 
Read More

第5回講義(2009/04/15、要iTunes)聴講。UIViewクラス(Flashで言うところのDisplayObject)について、クラス説明、描画方法(Core Graphics)、ビルトインアニメーションについてなど濃い内容だった。後半20分ほどライブコーディングもありおもしろかった。

  • UIView.frameとUIView.boundsの違いについて図でわかりやすく説明。
  • drawRect:を他から直接呼ぶべきでない理由を説明していたが、あまり納得できず。ちなみにdeallocを直接呼ぶべきでない理由も僕は理解していない。なんでそんなメソッドがパブリック状態で野放しなのかね。パブリックと言えば、Objective-Cのメソッドはアクセス制御不能で基本的に全部パブリック(プロパティは制御可能)。隠蔽したい場合はカテゴリを使ったりして一工夫しないといけない(例えばPrivate Methods)。
  • UIView.centerというプロパティが存在した!聴講してなかったら一生気付かなかったかもしれない。
  • UIViewのビルトインアニメーションについて結構な時間を割いて説明していた。食わず嫌いをしていたので知るよい機会になった。全てUIViewのクラスメソッドで設定できるので、よく使うアニメーション効果などはカテゴリで作っておくと便利かもしれない(例えばUIViewのフェードイン/フェードアウト)。
  • マウス追従するUIViewアニメーションのライブコーディング。名前のクソ長いdelegateメソッドなどはドキュメントからコピーペーストして使っていた。やはり覚えられるもんじゃないんだな。スラスラ完成させて拍手で講義終了。
Read More

第4回講義(2009/04/13、要iTunes)聴講。第3回に引き続きUIKit(Cocoaフレームワーク)の説明。Delegate、MVC、Nibファイルについてなどフレームワークの骨格に関する内容だった。正直あんまり感想がない。

  • CocoaのMVCの構造はいわゆるデザインパターンのMVCと構造が異なる(ModelとViewに依存関係がない)。MVCパターンは、「MとVとCの依存関係をできるだけ対等になくしてやる」という思想の上に成り立っていると考えると、Cocoa型MVC(Controller司令塔型)は異端児と言える。といっても僕も少し前までCocoa型MVCをMVCパターンだと思っていたが(MVCオルタナティブ – boreal-kiss.com)。
  • 後半はNibファイルの話だがわかりにくかった。
  • 放送用のためか、生徒の質問(マイク無し)を先生が繰り返すし発言するようになっていた。ラジオリスナー側には今まで聞こえなかったのでこれは非常に助かる。
Read More

ピッカーを回転させたときのカチカチというクリック音を無効にするにはUIPickerViewのsetSoundsEnabled:メソッドで引数をNOに設定してやればよい。しかしメソッド自体は定義されているのに、UIKit.h(UIPikerView.h)で宣言されていないらしく、そのまま使うとコンパイル時に警告がでる。警告は出るが呼び出しは可能なので気にならない人はそれでOK。気になる人は新たに宣言を追加してやればよい。UIKit.hやUIPickerView.hに直接書き込むのは怖いのでカテゴリを用意する(宣言だけ必要なのでヘッダーファイルのみ作成)。

//  UIPickerView-Mute.h
 
#import <UIKit/UIKit.h>
 
@interface UIPickerView (Mute)
 
-(void) setSoundsEnabled:(BOOL)enabled;
 
@end

あとはUIPickerViewを呼び出すファイル内にインクルードしてやれば先ほどの警告が出なくなる。

#import "UIPickerView-Mute.h"
 
UIPickerView *picker = [[UIPickerView alloc] init];
[picker setSoundsEnabled:NO];
Read More

第3回講義(2009/04/08、要iTunes)聴講。丸々一時間メモリ管理に関する話で有益だった。カスタムクラスを作成しながらretainカウント方式とプロパティ属性(setter/getter)の説明などが行われる。ライブコーディングなし。講義で特に良いと思ったのは以下。

  • autoreleaseの仕組みについての詳細な説明。iPhone関連書籍は数あれど、実際どのタイミングでautoreleaseが機能しているのかまで言及しているものは少ない(Aaron Hillegass本に書かれているがこれはCocoa専門書)。その場しのぎで作成したオブジェクト以外にautoreleaseを使うと危険なことがわかった。
  • プロパティ属性(@propertyと@synthesize)を使うことで実際に書く手間が省けたコード内容の説明。こちらもiPhone関連本には詳細は載っていない場合が多い(Appleのドキュメントには書かれている)。一番知りたかったatomic, nonatomicについては講義では触れていなかった。

おまけ

retainカウント方式はコードが煩雑になりやすいのでちょっとした書き忘れなどが原因で無駄なメモリを使わせることが多々ある(iPhoneはメモリ使用量がシビアなのでこれが致命傷になることも)。しかもどこでrelease忘れが生じているか中々見つからず無駄な時間ばかりとられることになる。そういうときはLLVM/Clang Static Analyzerがおすすめ。ビルド内容(?)を読み取って、ソースコードの問題点(release忘れ箇所)を見やすいhtmlで出力してくれる。コマンドラインによる動作が障壁になるかもしれないが小難しい操作ではない。実際の使用状況はclangでObjective-Cプログラムを静的解析 – きりかリポーツを参照ください。

Read More

第2回講義(2009/04/06)(要iTunes)が早くも公開されていたので聴講した。しかしどうなんだろうこれ。内容は以下のとおり。

  • OOPの概念について
  • Objective-Cの文法について
  • Cocoa (Foundation)の代表的なクラスについて

初心者(心得のない人)向けな内容だが、初心者が納得できるような説明でもないし、かといって知ってる人が聴講して新しく知識を得そうな内容もなく、そのへんに売ってる教本の最初の数十ページを棒読みしたような講義内容だった。プレゼンターがテンパってたのが痛い。期待してたライブコーディングもなくて残念。次回こそは。

おまけ

特殊用語の発音が生で聞けるのは楽しい。

  • Cocoa -> ココー
  • Xib -> ズィブ
Read More

スタンフォード大学の今期(2009年4月スタート)のコンピューターサイエンス(?)の授業でiPhone Application Programingというものがある。学生がappleのエンジニアから直接iPhone applicationの作成ノウハウを学び、いくつかの実習アプリケーションを作成して、最終的にオリジナルのアプリケーションをapp storeで販売(?)できるところまでを授業にしているコースらしく、講義概要をきいているだけでワクワクしてしまう。受講学生のみの恩恵かと思ったらそうではなく、その授業内容全てがiTunesでpodcastとして無料配布され、一般人もpodcast経由で授業内容を傍聴することができる点が素晴らしい。それの第一回(2009/04/01)が公開されていたので聴講した。以下その感想。

  • 英語自体はアメリカ人にしては聞きやすい方だと思う。ただしアメリカ人の同僚を持つ身からの意見なので、アメリカ英語に不慣れな(場数をこなす暇のない)日本人は日本人がスタンフォード大のiPhone開発講義を見るときのいろいろなどを参考にして聴講すると良いかもしれない。英語に関して中々興味深い内容が書かれている。
  • いきなりInterface Builder(IB)オンリーでデモンストレーションをしている。教鞭に立つエンジニア自身もユニークだと認めているIBに対して圧倒的に説明不足。少なくとも初見者には何をやっているのか理解不能だと思う。
  • 後半のライブコーディングが悪い意味で面白い。デモappが最終的にクラッシュし、原因不明のまま終わる。おそらくIBの接続ミスなどが原因だと思われるが、自分がよくやる(嫌気の差す)ミスをappleのエンジニアが実演してくれたのがとてもうれしい。変な自信と勇気が湧いた。
  • 技術的な話。ライブコーディングでViewControllerを作成せず、ヘルパークラスとしてNSObjectのサブクラスを使っていた。これが僕としては目から鱗で、今回の収穫としたい。

ということでまとめると第一回講義は、iPhoneアプリケーション開発を知らない人には意味不明、知ってる人には不要な内容、な感じ。しかしながら講義一発撮りがそのまま公開されるという点は(ミスも含めて)雰囲気が伝わってよいと思う。この流れでapp配布まで持ち込むらしいので今後の展開に期待!

関連リンク

Read More