iPhoneレシピ4:UIActionSheetにプログレスバーを表示する

ポイント

UIActionSheetの高さはタイトルの改行か、setNumberOfRowsメソッドで設定する。ただしsetNumberOfRowsはドキュメントには載っていないメソッド。
UIActionSheetはメインスレッドで表示しないと、ボタンが効かないっぽい。また、表示とその他の処理を同一スレッド内で行うと、処理終了までUIActionSheetが表示されない。

ソース

Download source

UIActionSheetの組み立て部分はこんな感じ。

- (void)viewDidLoad {
    [super viewDidLoad];
	actionSheet = [[UIActionSheet alloc] initWithTitle:@"Please wait\n\n\n\n" 
											  delegate:self 
									 cancelButtonTitle:NSLocalizedString(@"Cancel",nil) 
								destructiveButtonTitle:nil 
									 otherButtonTitles:nil];
	progressBar = [[UIProgressView alloc] initWithFrame:CGRectMake(30.0f, 40.0f, 240.0f, 90.0f)];
	progressBar.progressViewStyle = UIProgressViewStyleDefault;
	progressBar.progress = 0.0f;
	[actionSheet addSubview:progressBar];
	
	progressLabel = [[UILabel alloc] initWithFrame:CGRectMake(30.0f, 50.0f, 240.0f, 20.0f)];
	progressLabel.backgroundColor = [UIColor clearColor];
	progressLabel.textColor = [UIColor whiteColor];
	progressLabel.font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
	[actionSheet addSubview:progressLabel];
}

表示と処理のスレッド分けはこんな感じ。

- (IBAction)start {
	cancelFlag = NO;
	// UIActionSheetはメインスレッドで表示しないとボタンが効かないっぽい。
	[self showSheet];
	[self performSelectorInBackground:@selector(doProcess) withObject:nil];
}

- (void)showSheet {
	progressBar.progress = 0;
	progressLabel.text = NSLocalizedString(@"Processing...", nil);
	[actionSheet showInView:self.view];
}

// 何かしらの処理
- (void)doProcess {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try {
		for (int i=0; i<100; i++) {
			if (cancelFlag) return;

			// Background threadで動いている時はUIProgressViewをMainThreadで動かすこともできる。
			// どちらで動かすべきかはよく分からない。
//			[self performSelectorOnMainThread:@selector(progress:) withObject:[NSNumber numberWithFloat:0.01f] waitUntilDone:NO];
			[self performSelectorInBackground:@selector(progress:) withObject:[NSNumber numberWithFloat:0.01f]];
			[NSThread sleepForTimeInterval:0.1f];
		}
		[actionSheet dismissWithClickedButtonIndex:-1 animated:YES];
	}
	@finally {
		[pool release];
	}
}

- (void)progress:(NSNumber *)amount {
	progressBar.progress += [amount floatValue];
	NSLog(@"%f", progressBar.progress);
}

///////////////////////////////////////////////////////////////////
// UIActionSheetDelegate implements
//
- (void)actionSheet:(UIActionSheet *)sheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
	if (buttonIndex == sheet.cancelButtonIndex) {
		cancelFlag = YES;
	}
}

iPhoneレシピ5:zipファイルを解凍する

ポイント

iPhoneにはzlibが入っているので、それを利用して解凍する。
zlibを使うためには、libz***.dylibへのリンクが必要。そのためには、xcodeで「グループとファイル」ビュー上で右クリック→「追加」→「既存のフレームワークを追加」でibz***.dylibを追加すればよい。

生のzlibをそのまま使うのはちょっと面倒だが、zlibと一緒に配布されているminizipをさらにObjective-Cでラップした「HetimaUnZip.framework」をHetimaさんが公開している。

HetimaUnZipはCocoaを使っているため、iPhoneで利用できるよう、Cocoaへの依存部分および不要なminizipファイルの削除と、メモリ上ではなく、直接ファイルに解凍するメソッドを追加したものを利用する。

オリジナルのFrameworkをiPhone上で使う方法が分からないため、とりあえず直接ソースのまま取り込んで使う。
改変したHetimaUnZipはここからダウンロードできる。

HetimaUnzip改変版

ソース

NSString *filePath = @"zipファイルへのパス";
HetimaUnZipContainer *unzipContainer = [[HetimaUnZipContainer alloc] initWithZipFile:filePath];
[unzipContainer setListOnlyRealFile:YES];
	
HetimaUnZipItem *item;
NSEnumerator *contentsEnum = [[unzipContainer contents] objectEnumerator];

// プログレスバー表示用に全ファイルのサイズを取得
expectedUmcompressedContentSize = 0;
for (item in contentsEnum) {
	expectedUmcompressedContentSize += [item uncompressedSize];
	LOG(@"zip\tpath:%@\t%d", [item path], [item uncompressedSize]);
}

// 解凍
contentsEnum = [[unzipContainer contents] objectEnumerator];
for (item in contentsEnum) {
	NSString *path = [[NSFileManager defaultManager] suggestFilePath:[APPLICATION_DOC_DIR stringByAppendingPathComponent:[item path]]];
	BOOL result = [item extractTo:path delegate:self];
	if (!result) {
		NSString *err = [NSString stringWithFormat:NSLocalizedString(@"Failed to extract %@.", nil), [item path]];
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:err delegate:actionSheet cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
		[alert show];
		[alert release];
	}
}

// HetimaUnZipItemDelegate
// HetimaUnZipItem#extractTo:delegate:の解凍状況はdelegateのメソッドで取得できる。
- (void)item:(HetimaUnZipItem *)item didExtractDataOfLength:(NSUInteger)length {
	progressBar.progress = progressBar.progress + ((long double)length / (long double)expectedUmcompressedContentSize) * 0.5f;
	LOG(@"Extracting %f", progressBar.progress);
}

iPhoneレシピ6:zipファイルをダウンロードし、Documentsディレクトリに解凍する


下記4つのレシピを組み合わせて、zipファイルをダウンロードし、Documentsディレクトリに解凍する方法。

ポイント

基本的には上記のレシピの中にあるとおり。
ただしiPhone SDKレシピ3:UIProgressViewの使い方 - Random Noteにある、UIProgressViewのprogressプロパティを別スレッドで設定する、という部分の別スレッド呼び出しについては、NSURLConnectionとHetimaUnZipItemの中に隠蔽されている。

ソース

Downloader.zip

UIActionSheetを用意しておいて、

- (void)viewDidLoad {
    [super viewDidLoad];
	actionSheet = [[UIActionSheet alloc] initWithTitle:@"Please wait...\n\n\n\n" 
											  delegate:self 
									 cancelButtonTitle:NSLocalizedString(@"Cancel",nil) 
								destructiveButtonTitle:nil 
									 otherButtonTitles:nil];
	progressBar = [[UIProgressView alloc] initWithFrame:CGRectMake(30.0f, 40.0f, 240.0f, 90.0f)];
	progressBar.progressViewStyle = UIProgressViewStyleDefault;
	progressBar.progress = 0.0f;
	[actionSheet addSubview:progressBar];
	
	progressLabel = [[UILabel alloc] initWithFrame:CGRectMake(30.0f, 50.0f, 240.0f, 20.0f)];
	progressLabel.backgroundColor = [UIColor clearColor];
	progressLabel.textColor = [UIColor whiteColor];
	progressLabel.font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
	progressLabel.text = NSLocalizedString(@"Downloading...", nil);
	[actionSheet addSubview:progressLabel];
	
	downloaderLock = [[NSLock alloc] init];
}

ダウンロードを開始し、

- (IBAction) download:(id)sender {
	[urlField resignFirstResponder];
	downloadedContentLength = 0;
	progressBar.progress = 0;
	NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:[urlField text]]];
	
	[[NSFileManager defaultManager] clearTmpDirectory];
	progressLabel.text = NSLocalizedString(@"Downloading...", nil);
	[actionSheet showInView:self.view];
	downloader = [[URLDownload alloc] initWithRequest:req directory:APPLICATION_TMP_DIR delegate:self];
}

ダウンロードが終わったら解凍する。だけ。

- (void)downloadDidFinish:(URLDownload *)download {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	LOG(download.filePath);
	progressLabel.text = NSLocalizedString(@"Extracting...", nil);
	
	HetimaUnZipContainer *unzipContainer = [[HetimaUnZipContainer alloc] initWithZipFile:download.filePath];
	[unzipContainer setListOnlyRealFile:YES];
	
	if ([[unzipContainer contents] count] == 0) {
		NSString *err = NSLocalizedString(@"No zip file is found.", nil);
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:err delegate:actionSheet cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
		[alert show];
		[alert release];
	} else {
		HetimaUnZipItem *item;
		NSEnumerator *contentsEnum = [[unzipContainer contents] objectEnumerator];
		expectedUmcompressedContentSize = 0;
		for (item in contentsEnum) {
			expectedUmcompressedContentSize += [item uncompressedSize];
			LOG(@"zip\tpath:%@\t%d", [item path], [item uncompressedSize]);
		}
		contentsEnum = [[unzipContainer contents] objectEnumerator];
		for (item in contentsEnum) {
			NSString *path = [[NSFileManager defaultManager] suggestFilePath:[APPLICATION_DOC_DIR stringByAppendingPathComponent:[item path]]];
			BOOL result = [item extractTo:path delegate:self];
			if (!result) {
				NSString *err = [NSString stringWithFormat:NSLocalizedString(@"Failed to extract %@.", nil), [item path]];
				UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:err delegate:actionSheet cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
				[alert show];
				[alert release];
			}
		}
	}
	
	
	[unzipContainer release];
	[pool release];
	[self dismissActionSheet];
}

ダウンロード中の進行状況はURLDownloadDelegateで取得。

- (void)download:(URLDownload *)download didReceiveResponse:(NSURLResponse *)response {
	expectedContentLength = [response expectedContentLength];
}
- (void)download:(URLDownload *)download didReceiveDataOfLength:(NSUInteger)length {
	progressBar.progress = progressBar.progress + ((long double)length / (long double)expectedContentLength) * 0.5f;
	LOG(@"Download: %f", progressBar.progress);
}

解凍中の進行状況はHetimaUnZipItemDeletegateで取得。

- (void)item:(HetimaUnZipItem *)item didExtractDataOfLength:(NSUInteger)length {
	progressBar.progress = progressBar.progress + ((long double)length / (long double)expectedUmcompressedContentSize) * 0.5f;
	LOG(@"Extracting %f", progressBar.progress);
}

終わったらdismissActionSheetを呼びだし、後始末。
UIActionSheetが非表示になった時にダウンロードに使ったURLDownloadをリリースしている。

- (void)dismissActionSheet {
	if (actionSheet) {
		[actionSheet dismissWithClickedButtonIndex:-1 animated:YES];
	}
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
	[self releaseDownloader];
}

- (void)releaseDownloader {
	[downloaderLock lock];
	if (downloader != nil) {
		[downloader release];
		downloader = nil;
	}
	[[NSFileManager defaultManager] clearTmpDirectory];
	[downloaderLock unlock];
}

途中キャンセルもできる。

// なぜかactionSheetCancelが呼ばれないので、こちらで代用
- (void)actionSheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)buttonIndex {
	if (buttonIndex == sheet.cancelButtonIndex) {
		[downloader cancel];
	}
}

窓使いの憂鬱の設定 for Parallels Desktop on Mac

Parallelsのバージョンが4.0になってから、キーボードのリマップをParallelsがするようになった。
が、あまりに使いづらいので窓使いの憂鬱に戻した。
前のバージョンのParallelsで使っていたときの設定に、commandキー(Windowsキー)とcontrolの入れかえを追加してある。

include "109.mayu"      # 109 キーボード設定


keymap Global                                        #(こうやって

key U-変換                          =   ひらがな ひらがな    #ひらがな

key IL-U-変換                          =   ひらがな ひらがな    #ひらがな(IME オン時)

key IC-U-変換                       =   Return ひらがな ひらがな    #ひらがな(IME オン時-変換中)

key S-U-変換                        =   半角/全角 ひらがな 無変換    #カタカナ
key IL-S-U-変換                   =   ひらがな 無変換   #カタカナ(IME オン時)

key IC-S-U-変換                   =   Return ひらがな 無変換   #カタカナ(IME オン時-変換中)

key U-無変換                        =  半角/全角 半角/全角             #英数

key IL-U-無変換                        =  半角/全角             #英数(IME オン時)

key IC-U-無変換                     =   Return 半角/全角   #英数(IME オン時-変換中)

mod Windows -= LeftWindows
mod control += LeftWindows

mod control -= LeftControl
mod Windows += LeftControl

key *LeftControl = *LeftWindows
key *LeftWindows = *LeftControl

さくらインターネットでSubversion

svn+sshで接続できるSubversionさくらインターネットのサーバ上に構築した時のインストールログ。

環境

FreeBSD 6.1-RELEASE-p23
subversion-1.5.4

ログ

基本的にさくらインターネットでSubversion » サイキョウラインを参考にした。

%mkdir -p $HOME/local/src
%cd local/src
%wget http://subversion.tigris.org/downloads/subversion-1.5.4.tar.bz2
%tar jxf subversion-1.5.4.tar.bz2
%wget http://subversion.tigris.org/downloads/subversion-deps-1.5.4.tar.gz
%tar zxf subversion-deps-1.5.4.tar.gz
%cd subversion-1.5.4
%./configure --prefix=$HOME/local --without-serf
%make all install clean
%cd $HOME
%mkdir -p svn/repos
%$HOME/local/bin/svnadmin craete svn/repos

最後に、.cshrcでpathに$HOME/local/binを追加。
以上でsvn+sshで利用できるSubversionのレポジトリが出来た。

備考

最初、subversion-1.5.4.tar.bz2だけをダウンロードした状態で

%./configure --prefix=$HOME/local

とconfigureしたら、

configure: WARNING: APR not found
The Apache Portable Runtime (APR) library cannot be found.
Please install APR on this system and supply the appropriate
--with-apr option to 'configure'

or

get it with SVN and put it in a subdirectory of this source:

   svn co \
    http://svn.apache.org/repos/asf/apr/apr/branches/0.9.x \
    apr

Run that right here in the top level of the Subversion tree.
Afterwards, run apr/buildconf in that subdirectory and
then run configure again here.

Whichever of the above you do, you probably need to do
something similar for apr-util, either providing both
--with-apr and --with-apr-util to 'configure', or
getting both from SVN with:

   svn co \
    http://svn.apache.org/repos/asf/apr/apr-util/branches/0.9.x \
    apr-util

configure: error: no suitable apr found

と怒られた。上記のメッセージではapacheからaprを取ってこいと言っているが、Subversionのサイト上にsubversion-deps-1.5.4.tar.gzというファイルがあったので落として解凍してみたら、必要そうなものがいろいろsubversion-1.5.4ディレクトリに追加された。

この状態でもう一度configureすると、今度は

configure: WARNING: we have configured without BDB filesystem support


You don't seem to have Berkeley DB version 4.0.14 or newer
installed and linked to APR-UTIL.  We have created Makefiles which
will build without the Berkeley DB back-end; your repositories will
use FSFS as the default back-end.  You can find the latest version of
Berkeley DB here:
  http://www.oracle.com/technology/software/products/berkeley-db/index.html

というWARNINGが出た。
調べてみると、FSFSというのはSubversionに新しく追加されたFile Systemで、どうもBerkley DBをback-endにするよりよさげな感じ。なのでこのままmakeすることにした。

%make all install clean

今度は

/usr/lib/crt1.o(.text+0x72): In function `_start':
: undefined reference to `main'
*** Error code 1

Stop in /home/***/local/src/subversion-1.5.4/serf.
*** Error code 1

Stop in /home/***/local/src/subversion-1.5.4.

というわけわからんエラーで止まる。調べてみてもぴったり当てはまる件がなかったが、似たような事例の中に1つ--without-serfをつけてconfigureするという解決策があった。serfというのは「The serf library is a C-based HTTP client library built upon the Apache Portable Runtime 」とのことなので、svn+sshで使おうとしている今回の場合はなくても問題なさそう。
ということで、試しに--without-serfを付けてconfigureし、make installすると、見事成功。

ソースからインストールしたことがなかったので、どこかおかしいところはあるかもしれないが、今のところ問題なく動いている。

MacBook Proのインストールログ

環境設定

  • キーボード
    • キーのリピート速度を最速
    • リピート入力認識までの時間を最短
  • トラックパッド
    • タップでクリックをON
    • ドラッグをON
    • 副ボタンのクリックをON
  • Dock
    • Dockを自動的に隠すをON
  • Terminal
  • Safari
    • defaults write com.apple.Safari IncludeDebugMenu -bool true
    • defaults write com.apple.Safari TargetedClicksCreateTabs -bool true
  • Fontsをインストール
.bashrc
export PATH=/opt/local/bin:/opt/local/sbin:$PATH
export MANPATH=/opt/local/man:$MANPATH
.zshrc.mine
export PATH=/opt/local/bin:/opt/local/sbin:$PATH
export MANPATH=/opt/local/man:$MANPATH
.zshrc

http://journal.mycom.co.jp/column/zsh/010/index.htmlより拝借。

# users generic .zshrc file for zsh(1)

## Environment variable configuration
#
# LANG
#
export LANG=ja_JP.UTF-8

## Default shell configuration
#
# set prompt
#
autoload colors
colors
case ${UID} in
0)
  PROMPT="%B%{${fg[red]}%}%/#%{${reset_color}%}%b "
  PROMPT2="%B%{${fg[red]}%}%_#%{${reset_color}%}%b "
  SPROMPT="%B%{${fg[red]}%}%r is correct? [n,y,a,e]:%{${reset_color}%}%b "
  [ -n "${REMOTEHOST}${SSH_CONNECTION}" ] && 
    PROMPT="%{${fg[white]}%}${HOST%%.*} ${PROMPT}"
  ;;
*)
  PROMPT="%{${fg[red]}%}%/%%%{${reset_color}%} "
  PROMPT2="%{${fg[red]}%}%_%%%{${reset_color}%} "
  SPROMPT="%{${fg[red]}%}%r is correct? [n,y,a,e]:%{${reset_color}%} "
  [ -n "${REMOTEHOST}${SSH_CONNECTION}" ] && 
    PROMPT="%{${fg[white]}%}${HOST%%.*} ${PROMPT}"
  ;;
esac

# auto change directory
#
setopt auto_cd

# auto directory pushd that you can get dirs list by cd -[tab]
#
setopt auto_pushd

# command correct edition before each completion attempt
#
setopt correct

# compacked complete list display
#
setopt list_packed

# no remove postfix slash of command line
#
setopt noautoremoveslash

# no beep sound when complete list displayed
#
setopt nolistbeep

## Keybind configuration
#
# emacs like keybind (e.x. Ctrl-a goes to head of a line and Ctrl-e goes 
# to end of it)
#
bindkey -e

# historical backward/forward search with linehead string binded to ^P/^N
#
autoload history-search-end
zle -N history-beginning-search-backward-end history-search-end
zle -N history-beginning-search-forward-end history-search-end
bindkey "^p" history-beginning-search-backward-end
bindkey "^n" history-beginning-search-forward-end
bindkey "\\ep" history-beginning-search-backward-end
bindkey "\\en" history-beginning-search-forward-end

## Command history configuration
#
HISTFILE=~/.zsh_history
HISTSIZE=10000
SAVEHIST=10000
setopt hist_ignore_dups # ignore duplication command history list
setopt share_history # share command history data

## Completion configuration
#
autoload -U compinit
compinit

## Alias configuration
#
# expand aliases before completing
#
setopt complete_aliases # aliased ls needs if file/dir completions work

alias where="command -v"
alias j="jobs -l"

case "${OSTYPE}" in
freebsd*|darwin*)
  alias ls="ls -G -w"
  ;;
linux*)
  alias ls="ls --color"
  ;;
esac

alias la="ls -a"
alias lf="ls -F"
alias ll="ls -l"

alias du="du -h"
alias df="df -h"

alias su="su -l"

## terminal configuration
#
unset LSCOLORS
case "${TERM}" in
xterm)
  export TERM=xterm-color
  ;;
kterm)
  export TERM=kterm-color
  # set BackSpace control character
  stty erase
  ;;
cons25)
  unset LANG
  export LSCOLORS=ExFxCxdxBxegedabagacad
  export LS_COLORS='di=01;34:ln=01;35:so=01;32:ex=01;31:bd=46;34:cd=43;34:su=41;30:sg=46;30:tw=42;30:ow=43;30'
  zstyle ':completion:*' list-colors \
    'di=;34;1' 'ln=;35;1' 'so=;32;1' 'ex=31;1' 'bd=46;34' 'cd=43;34'
  ;;
esac

# set terminal title including current directory
#
case "${TERM}" in
kterm*|xterm*)
  precmd() {
    echo -ne "\033]0;${USER}@${HOST%%.*}:${PWD}\007"
  }
  export LSCOLORS=exfxcxdxbxegedabagacad
  export LS_COLORS='di=34:ln=35:so=32:pi=33:ex=31:bd=46;34:cd=43;34:su=41;30:sg=46;30:tw=42;30:ow=43;30'
  zstyle ':completion:*' list-colors \
    'di=34' 'ln=35' 'so=32' 'ex=31' 'bd=46;34' 'cd=43;34'
  ;;
esac

## load user .zshrc configuration file
#
[ -f ~/.zshrc.mine ] && source ~/.zshrc.mine

UITextFieldでリターンキーが押された時にキーボードを隠す

iPhoneでUITextFieldを使う際、リターンキー押下時にキーボードを隠したいことがある。
そのような時は、UITextFieldの「Did End On Exit」をIBActionにバインドし、そのIBActionの中で、UITextFieldインスタンスのresignFirstResponderメソッドを呼んでやればよい。


//View.h
@interface View : UIView {
	IBOutlet UITextField *textField;
}

- (IBAction)download: (id)sender;


//View.m
@implementation View
- (IBAction) download: (id)sender {
	[textField resignFirstResponder];
	// あとは自由に。
}
@end