八王子にいってきた

南米ペルー料理のお店でした

人生初の八王子。iPhoneのおかげで無事到着。
全員はじめまして、こんにちは。

Perlよりもサーバーサイドの話題が多い感じで割とのんびりと話してました。

参加した目的

今後大勢の前でしゃべる必要があるので練習するために参加してみた。

反省

  • 印刷すると思ったよりコードが長い事に気づいた。控えよう。
  • 着席5分後にビールを飲んだら眠くなり、最初の1時間ぐらい人の話聞いてなかった気がする。控えよう。
  • 自分の言いたい事をたくさん記述しておくと、読み切るので忙しくなるので途中で省略したくなる。控えよう。

収穫

  • 次の仕事は「ターゲットブラウザがIE6でHTML+CSS+jQuery」だったけど、万が一の場合に備えて発注先候補が見つかった。よかった。
  • また非同期が流行るらしい
  • GANCが流行る、かもしれない

印象に残った発言

GWに作りたいものは? => 「時間」

まとめ

八王子pmはそんなに怖くなかったよ。

jquery.tmpl

近況

そんなわけで明日から新天地です。
まったくPerl関係ない仕事ですが、HTML+CSS+jQueryなので結構好きな仕事です。はい。

そんなわけでjQueryをちょい勉強。

javascriptのテンプレート

jQuery1.5から標準で使えるそうです

<!DOCTYPE html> 
<html lang="ja"> 
<head> 
<meta charset="UTF-8" />
<meta name="author" content="" /> 
<meta name="keywords" content="" /> 
<meta name="description" content="" /> 
<title>Testing jquery.tmpl</title> 
<link type="text/css" href="css/prj.min.css?v=2" rel="stylesheet" media="screen" /> 
<link type="text/css" href="css/print.css?v=2" rel="stylesheet" media="print" /> 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script> 
<script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js" type="text/javascript"></script> 
 
<script id="movieTemplate" type="text/x-jquery-tmpl"> 
<li><b>${Name}</b> (${ReleaseYear})</li>
</script> 
<script type="text/javascript"> 
 
$(document).ready( function()
{
 
    var movies = [
       { Name: "The Red Violin",  ReleaseYear: "1998" },
       { Name: "Eyes Wide Shut",  ReleaseYear: "1999" },
       { Name: "The Inheritance", ReleaseYear: "1976" },
    ];
 
    $( "#movieTemplate" ).tmpl( movies ).appendTo( "#movieList" );
 
    $("span").appendTo("#foo");        
 
});
</script> 
</head> 
 
<body> 
 
<ul id="movieList"></ul> 
 
</body> 

今日の一曲

mas que nada - 佐藤正美

http://www.youtube.com/watch?v=f7ffm_nex-Y

Testing ruby product 'keydown'

I tried to install keydown in my MacOSX.
And I wrote it down here.

git clone

% cd ~/project
% git clone https://github.com/infews/keydown.git

PATH & RUBYLIB

% echo 'RUBYLIB=$HOME/project/keydown/lib:$RUBYLIB
export RUBYLIB

PATH=$HOME/project/keydown/bin:$PATH
export PATH
' >> ~/.zshrc_custom
% source ~/.zshrc_custom

gem install

% gem install albino                                         [~/project]
Fetching: posix-spawn-0.3.5.gem (100%)
Building native extensions.  This could take a while...
Fetching: albino-1.3.2.gem (100%)
Successfully installed posix-spawn-0.3.5
Successfully installed albino-1.3.2
2 gems installed
Installing ri documentation for posix-spawn-0.3.5...
Installing ri documentation for albino-1.3.2...
Installing RDoc documentation for posix-spawn-0.3.5...
Installing RDoc documentation for albino-1.3.2...

% gem install rdiscount                                                                      [~/project]
Fetching: rdiscount-1.6.8.gem (100%)
Building native extensions.  This could take a while...
Successfully installed rdiscount-1.6.8
1 gem installed
Installing ri documentation for rdiscount-1.6.8...
Installing RDoc documentation for rdiscount-1.6.8...

making slides

create presentation.

% cd ~/project
% keydown generate my_presentation                                                           [~/project]
      create  my_presentation
      create  my_presentation/my_presentation.md
      create  my_presentation/css/my_presentation.css
      create  my_presentation/css/rocks.css
      create  my_presentation/images
      create  my_presentation/images/cc.large.png
      create  my_presentation/images/flickr.png
      create  my_presentation/js/my_presentation.js
      create  my_presentation/js/rocks.js

change directory.

% cd my_presentation

edit my_presentation.md to avoid problem I don't know well.

perl -pi -e 's/``` ruby//g' my_presentation.md
perl -pi -e 's/def method/    def method/g' my_presentation.md
perl -pi -e 's/puts "Hello, World"/    puts "Hello, World"/g' my_presentation.md
perl -pi -e 's/end/    end/g' my_presentation.md
perl -pi -e 's/```//g' my_presentation.md

make slides.

% keydown slides my_presentation.md                                          [~/project/my_presentation]
Creating Keydown presentation from my_presentation.md
      create  css/keydown.css
      create  my_presentation.html

open my_presentation.html with Firefox.

% open -a 'Firefox' my_presentation.html

screenshot.

リモート画像を取得してUIImageViewを作成する

概要

MWPhotoBrowserというとてもシャープに動作するPhotoBrowserがあるのですが、こいつの仕組みを調査してみるために簡単な箇所だけ抜き出して実装してみたというお話です。

https://github.com/mwaterfall/MWPhotoBrowser

実装内容

  • ImageViewを内包したMyPhotoクラスを準備
  • [MyPhoto image]を実行するとisAvailableの場合は画像を返す。
  • そうでない場合はリモート画像を非同期通信にて取得開始。
  • ひとまずスピナーを表示する
  • BackGroundで画像取得処理が終了する。
  • MyPhotoはdelegateオブジェクトに対してphotoDidFinishLoadingかphotoDidFailToLoadへの通知を行う。
  • 画像が取得できている場合は画像を再描画

コード

model
// MyPhoto.h
#import <Foundation/Foundation.h>
@class MyPhoto;

// Delegate
@protocol MyPhotoDelegate <NSObject>
- (void)photoDidFinishLoading:(MyPhoto *)photo;
- (void)photoDidFailToLoad:(MyPhoto *)photo;
@end

@interface MyPhoto : NSObject {
	NSURL    *_photoURL;
	UIImage  *_photoImage;	
	BOOL      _workingInBackground;
}

+ (MyPhoto *)photoWithURL:(NSURL *)url;
- (id)initWithURL:(NSURL *)url;

// Public methods
- (BOOL)isImageAvailable;
- (UIImage *)image;
- (UIImage *)obtainImage;
- (void)obtainImageInBackgroundAndNotify:(id <MyPhotoDelegate>)notifyDelegate;
- (void)releasePhoto;


@end
#import "Global.h"
#import "MyPhoto.h"

// Private
@interface MyPhoto ()

// Properties
@property (retain) UIImage *photoImage;
@property ()       BOOL     workingInBackground;

// Private Methods
- (void)doBackgroundWork:(id <MyPhotoDelegate>)delegate;

@end

@implementation MyPhoto

@synthesize photoImage = _photoImage;
@synthesize workingInBackground = _workingInBackground;

#pragma mark Class Methods

+ (MyPhoto *)photoWithURL:(NSURL *)url {
	LOG_METHOD;
	
	return [[[MyPhoto alloc] initWithURL:url] autorelease];
}

#pragma mark NSObject

- (id)initWithURL:(NSURL *)url {
	LOG_METHOD;
	
	if ((self = [super init])) {
		_photoURL = [url copy];
	}
	return self;
}

- (void)dealloc {
	LOG_METHOD;
	
	[_photoURL release];
	[_photoImage release];
	[super dealloc];
}

#pragma mark Photo

- (BOOL)isImageAvailable {
	LOG_METHOD;
	
	return (self.photoImage != nil);
}

- (UIImage *)image {
	LOG_METHOD;
	
	return self.photoImage;
}

- (UIImage *)obtainImage {
	LOG_METHOD;
	
	// 画像が表示できる状態でない場合は、表示できるようになんとかする
	if (!self.photoImage) {
		
		// Load
		UIImage *img = nil;
			
		if (_photoURL) { 
			
			LOG(@"%@", _photoURL);
			
			// Read image from URL and return
			NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_photoURL];
			[request setValue:@"http://refer.example.com" forHTTPHeaderField:@"Referer"];
			
			NSError *error = nil;
			NSURLResponse *response = nil;
			NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
			
			//[request release];
			
			if (data) {
				img = [[UIImage alloc] initWithData:data];
			} else {
				LOG(@"Photo from URL error: %@", error);
			}
			
		}
		
		// Force the loading and caching of raw image data for speed
		//		[img decompress];		
		
		// Store
		self.photoImage = img;
		[img release];
		
	}
	
	return [[self.photoImage retain] autorelease];
}

// Release if we can get it again from path or url
- (void)releasePhoto {
	LOG_METHOD;
	
	if (self.photoImage && ( _photoURL)) {
		self.photoImage = nil;
	}
}

// Obtain image in background and notify the browser when it has loaded
- (void)obtainImageInBackgroundAndNotify:(id <MyPhotoDelegate>)delegate {
	LOG_METHOD;
	
	if (self.workingInBackground == YES) return; // Already fetching
	self.workingInBackground = YES;
	[self performSelectorInBackground:@selector(doBackgroundWork:) withObject:delegate];
}

// Run on background thread
// Download image and notify delegate
- (void)doBackgroundWork:(id <MyPhotoDelegate>)delegate {
	LOG_METHOD;
	
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	// Load image
	UIImage *img = [self obtainImage];
	
	// Notify delegate of success or fail
	if (img) {
		[(NSObject *)delegate performSelectorOnMainThread:@selector(photoDidFinishLoading:) withObject:self waitUntilDone:NO];
	} else {
		[(NSObject *)delegate performSelectorOnMainThread:@selector(photoDidFailToLoad:) withObject:self waitUntilDone:NO];		
	}
	
	// Finish
	self.workingInBackground = NO;
	
	[pool release];
}

@end
ViewController
// ImageViewerViewController.h
#import <UIKit/UIKit.h>
#import "MyPhoto.h"

@interface ImageViewerViewController : UIViewController <MyPhotoDelegate> {
	MyPhoto *_photo;
	UIImageView *_photoImageView;
	UIActivityIndicatorView *_spinner;
}

-(void)displayImage;

@end
// ImageViewerViewController.m
#import "ImageViewerViewController.h"
#import "MyPhoto.h"

@implementation ImageViewerViewController

// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
	LOG_METHOD;
	[super loadView];

	// Setup photo frame
	_photo = [MyPhoto photoWithURL:[NSURL URLWithString:@"http://farm6.static.flickr.com/5296/5425314834_535d56f8aa_b.jpg"]];
	[_photo retain];
	
	_photoImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
	_photoImageView.backgroundColor = [UIColor whiteColor];
	[self.view addSubview:_photoImageView];
	[_photoImageView retain];

	// Spinner
	_spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
	_spinner.hidesWhenStopped = YES;
	_spinner.center = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0);
	_spinner.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin |
	UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
	[self.view addSubview:_spinner];	
}


- (void)viewDidLoad {
	LOG_METHOD;
    [super viewDidLoad];

	[self displayImage];	
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	LOG_METHOD;
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}


- (void)didReceiveMemoryWarning {
	LOG_METHOD;
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc. that aren't in use.
}

- (void)viewDidUnload {
	LOG_METHOD;
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


- (void)dealloc {
	LOG_METHOD;
	[_photo release];
	[_photoImageView release];
	[_spinner release];
    [super dealloc];
}

#pragma mark -
#pragma mark Photos

- (void)displayImage {
	LOG_METHOD;
	
	// Get image or obtain in background
	if ([_photo isImageAvailable]) {
		[_spinner stopAnimating];
		
		// Setup photo frame
		CGRect photoImageViewFrame;
		photoImageViewFrame.origin = CGPointZero;
		photoImageViewFrame.size = [_photo image].size;
		_photoImageView.frame = photoImageViewFrame;
		_photoImageView.image = [_photo image];
	} else {
		// MyPhotoに画像取得処理を委譲。取得完了通知は
		// photoDidFinishLoadingあるいはphotoDidFailToLoadへ行う。
		[_spinner startAnimating];
		[_photo obtainImageInBackgroundAndNotify:self];
	}
}

#pragma mark -
#pragma mark PhotoDelegate

- (void)photoDidFinishLoading:(MyPhoto *)photo {
	LOG_METHOD;
	[_spinner stopAnimating];
	[self displayImage];	
}

- (void)photoDidFailToLoad:(MyPhoto *)photo {
	LOG_METHOD;
	[_spinner stopAnimating];

	// 本来はここに失敗した場合の処理を記述する
}

@end

感想

とりあえず途中で大変な作業量になりそうなのでこの辺で切り上げました。

MWPhotoBrowserは内部で相互参照していたりして読むのが大変でした。
でもよくこんなにややこしい仕組みを実装して公開して下さったものです。感謝。


でももうちょっと再利用しやすいといいな…

Objective-Cのプライベートメソッド

@privateが使えるのはメンバ変数(インスタンス変数)のみ

プライベートなインスタンス変数は次のように宣言する事ができます。

//MyClass.h
@interface MyClass : NSObject {
 @private
  id myInstanceVariable_;
}

// public methods
- (id)myInstanceVariable;
- (void)setMyInstanceVariable:(id)theVar;
@end

ですが、privateなメソッドを記述する場合、.hファイルではなく.mファイルへ以下のように記述をします。

//MyClass.m
@interface MyClass()
-(void) privateMethod;
@end

@implemention MyClass
// ここからヘッダーファイルに記述されている処理を実装する
// privateMethodの実装もここにできる
@end

つまり、プライベートメソッドに実装ファイル側の冒頭にinterface宣言と実装部をまとめて記述します。

Objective-C的にはこれはカテゴリーと呼ばれる機能です。

カテゴリーの補足説明

Perl寄りのHackerに向けて解説しておくと、
CPANモジュールにメソッド追加したい場合はそのモジュールを継承してからメソッドを追加する方法が一般的だと思います。

そんなときObjective-Cの場合は「親クラス+追加するメソッド名」をファイル名として既存クラスにメソッドを追加します。
以下、Twitter-OAuth-iPhoneで記述されている、NSStirngにURLEncodingの処理を追加したいのでカテゴリ機能を使って
機能を追加している例です。

// NSString+URLEncoding.h
#import <Foundation/Foundation.h>


@interface NSString (OAURLEncodingAdditions)

- (NSString *)URLEncodedString;
- (NSString *)URLDecodedString;

@end
@implementation NSString (OAURLEncodingAdditions)

- (NSString *)URLEncodedString 
{
    NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                           (CFStringRef)self,
                                                                           NULL,
																		   CFSTR("!*'();:@&=+$,/?%#[]"),
                                                                           kCFStringEncodingUTF8);
    [result autorelease];
	return result;
}

- (NSString*)URLDecodedString
{
	NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
																						   (CFStringRef)self,
																						   CFSTR(""),
																						   kCFStringEncodingUTF8);
    [result autorelease];
	return result;	
}
@end

まとめ

Objective-Cではインスタンス変数をプライベート宣言する事はできるけど、メソッドをプライベートにする場合はカテゴリーをつかってね

ViewがTapされた時を検知するときはdelegate使うんだぜ!!

tapされた事を検知する処理はよく使う処理なので定型化したい。
次のようにDelegateして使うViewを用意しておく

#import <Foundation/Foundation.h>

@protocol MyViewTapDelegate;

@interface MyViewTap : UIView {
	id <MyViewTapDelegate> tapDelegate;
}
@property (nonatomic, assign) id <MyViewTapDelegate> tapDelegate;
- (void)handleSingleTap:(UITouch *)touch;
- (void)handleDoubleTap:(UITouch *)touch;
- (void)handleTripleTap:(UITouch *)touch;
@end

@protocol MyViewTapDelegate <NSObject>
@optional
- (void)view:(UIView *)view singleTapDetected:(UITouch *)touch;
- (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch;
- (void)view:(UIView *)view tripleTapDetected:(UITouch *)touch;
@end
#import "MyViewTap.h"

@implementation MyViewTap

@synthesize tapDelegate;

- (id)init {
	if ((self = [super init])) {
		self.userInteractionEnabled = YES;
	}
	return self;
}

- (id)initWithFrame:(CGRect)frame {
	if ((self = [super initWithFrame:frame])) {
		self.userInteractionEnabled = YES;
	}
	return self;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
	UITouch *touch = [touches anyObject];
	NSUInteger tapCount = touch.tapCount;
	switch (tapCount) {
		case 1:
			[self handleSingleTap:touch];
			break;
		case 2:
			[self handleDoubleTap:touch];
			break;
		case 3:
			[self handleTripleTap:touch];
			break;
		default:
			break;
	}

	[[self nextResponder] touchesEnded:touches withEvent:event];
}

- (void)handleSingleTap:(UITouch *)touch {
	if ([tapDelegate respondsToSelector:@selector(view:singleTapDetected:)])
		[tapDelegate view:self singleTapDetected:touch];
}

- (void)handleDoubleTap:(UITouch *)touch {
	if ([tapDelegate respondsToSelector:@selector(view:doubleTapDetected:)])
		[tapDelegate view:self doubleTapDetected:touch];
}

- (void)handleTripleTap:(UITouch *)touch {
	if ([tapDelegate respondsToSelector:@selector(view:tripleTapDetected:)])
		[tapDelegate view:self tripleTapDetected:touch];
}

@end

上記のViewを別のViewにかぶせて使います。

- (id)initWithFrame:(CGRect)frame {
		// Tap view for background
		_tapView = [[MyViewTap alloc] initWithFrame:frame];
		_tapView.tapDelegate = self;
		_tapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
		_tapView.backgroundColor = [UIColor blackColor];
		[self addSubview:_tapView];

		...
}

- (void)handleSingleTap:(CGPoint)touchPoint {
	// singleTapの場合はこのメソッドが呼ばれる
}
- (void)handleDoubleTap:(CGPoint)touchPoint {
	// DoubleTapの場合
}
- (void)handleTripleTap:(CGPoint)touchPoint {
	// TripleTapの場合
}

ユーザーから見るとベースとなっているViewの前に透明なViewが存在していて、
TapがSingleかDoubleかTripleかという事だけを判定して残りの処理はベースとなっているViewに委譲しています。

擬人化するとたぶんこんな感じじゃないかな?

BaseView「おう、ユーザーが選択した処理がAなのかBなのかCなのかを俺に言ってくれ。ただし、それに対する結果は俺が決める。どんな処理するかの決定権は俺に委譲してくれだぜ!!」
TapView「あいー」

間違ってたらだれかがきっと通りすがってコメントしてくれるさ。

iPhoneにアカウント情報を保存してみました。

やりたいこと

  • UserIDを保存したい
  • Passwordを保存したい
  • UserIDは見られても構わない
  • Passwordは誰にも見られたくない

UserIDはPinfoに保存する

こちらを参考にしました。

http://d.hatena.ne.jp/tomute/20091121/1258884514

要点としてはユーザー名をNSUserDefaultsに保存して、パスワードはセキュリテリを高めるためにKeyChainを使います。

- (void)saveUserInfo {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *oldUsername = [defaults objectForKey:@"USERNAME"];
    NSError *error;

	// ユーザ名が変更になっていた場合は、古いユーザ名で保存したパスワードを削除
	if (![oldUsername isEqualToString:_usernameField.text]) {
        [SFHFKeychainUtils deleteItemForUsername:oldUsername andServiceName:@"Test App" error:&error];
    }
    
	// ユーザ名はNSUserDefaultsを使って保存
    [defaults setObject:_usernameField.text forKey:@"USERNAME"];
    
	// ラッパークラスを利用してパスワードをKeyChainに保存
    [SFHFKeychainUtils storeUsername:_usernameField.text andPassword:_passwordField.text forServiceName:@"Test App" updateExisting:YES error:&error];
    [self.navigationItem.rightBarButtonItem setEnabled:NO];
	
	// saveが完了したらその旨をユーザーに通知
	UIAlertView * alert = [[[UIAlertView alloc] initWithTitle:@"saved" 
													 message:@"complete" 
													delegate:nil 
										   cancelButtonTitle:nil 
										   otherButtonTitles:@"OK", nil] autorelease];
	[alert show];
}

画面

設定画面を開く

saveボタンを押すとアカウントが保存され、アラートが表示されます。