iPhoneレシピ6:zipファイルをダウンロードし、Documentsディレクトリに解凍する
下記4つのレシピを組み合わせて、zipファイルをダウンロードし、Documentsディレクトリに解凍する方法。
- iPhone SDKレシピ2:NSURLConnectionを使ってファイルをダウンロードする - Random Note
- iPhone SDKレシピ3:UIProgressViewの使い方 - Random Note
- iPhoneレシピ4:UIActionSheetにプログレスバーを表示する - Random Note
- iPhoneレシピ5:zipファイルを解凍する - Random Note
ポイント
基本的には上記のレシピの中にあるとおり。
ただしiPhone SDKレシピ3:UIProgressViewの使い方 - Random Noteにある、UIProgressViewのprogressプロパティを別スレッドで設定する、という部分の別スレッド呼び出しについては、NSURLConnectionとHetimaUnZipItemの中に隠蔽されている。
ソース
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]; } }
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はここからダウンロードできる。
ソース
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レシピ4:UIActionSheetにプログレスバーを表示する
ポイント
UIActionSheetの高さはタイトルの改行か、setNumberOfRowsメソッドで設定する。ただしsetNumberOfRowsはドキュメントには載っていないメソッド。
UIActionSheetはメインスレッドで表示しないと、ボタンが効かないっぽい。また、表示とその他の処理を同一スレッド内で行うと、処理終了までUIActionSheetが表示されない。
ソース
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 SDKレシピ3:UIProgressViewの使い方
ポイント
UIProgressViewのprogressプロパティは、ある処理をしているスレッド中で変更しても、その処理が終了するまで画面に反映されない。
そこで、performSelectorInBackground:withObject:メソッドを利用して、バックグラウンドでprogressプロパティを変更する。
ソース
// tmpViewController.h #import <UIKit/UIKit.h> @interface tmpViewController : UIViewController { IBOutlet UIProgressView *progressBar; } @property(nonatomic, retain) IBOutlet UIProgressView *progressBar; - (IBAction)start; - (void) progress:(NSNumber *)amount; @end // tmpViewController.m #import "tmpViewController.h" @implementation tmpViewController @synthesize progressBar; - (IBAction)start { progressBar.progress = 0; for (int i=0; i<100; i++) { [self performSelectorInBackground:@selector(progress:) withObject:[NSNumber numberWithFloat:0.01f]]; [NSThread sleepForTimeInterval:0.1f]; } } // バックグラウンドでprogressを変更 - (void)progress:(NSNumber *)amount { progressBar.progress += [amount floatValue]; } - (void)dealloc { [progressBar release]; [super dealloc]; } @end