此範例主要是呈現一個非同步取得網頁資料的方式,當資料在下載時,先呈現一個 loading 的狀態,等資料取得後再更新。
裡頭呈現 loading 狀態的程式碼,參考:11-ThreadedFlickrTableView.zip, 2010 Winter, CS 193P iPhone Application Development
程式碼:
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
WebBrowserViewController *t = [[[WebBrowserViewController alloc] initWithNibName:nil bundle:nil] autorelease];
[window addSubview:t.view];
// Override point for customization after application launch
[window makeKeyAndVisible];
return YES;
}
WebBrowserViewController *t = [[[WebBrowserViewController alloc] initWithNibName:nil bundle:nil] autorelease];
[window addSubview:t.view];
// Override point for customization after application launch
[window makeKeyAndVisible];
return YES;
}
WebGetOperation.h
#import <Foundation/Foundation.h>
@interface WebGetOperation : NSOperation {
NSString *url;
NSString *indexData;
NSString *indexURL;
id target;
SEL action;
}
-(id)initWithWebURL:(NSString *)theURL indexData:(NSString *)theIndexData indexURL:(NSString *)theIndexURL target:(id)theTarget action:(SEL)theAction;
@end
@interface WebGetOperation : NSOperation {
NSString *url;
NSString *indexData;
NSString *indexURL;
id target;
SEL action;
}
-(id)initWithWebURL:(NSString *)theURL indexData:(NSString *)theIndexData indexURL:(NSString *)theIndexURL target:(id)theTarget action:(SEL)theAction;
@end
WebGetOperation.m
#import "WebGetOperation.h"
@implementation WebGetOperation
-(id)initWithWebURL:(NSString *)theURL indexData:(NSString *)theIndexData indexURL:(NSString *)theIndexURL target:(id)theTarget action:(SEL)theAction
{
if( ( self = [super init] ) )
{
url = [theURL retain];
indexData = [theIndexData retain];
indexURL= [theIndexURL retain];
target = [theTarget retain];
action = theAction;
}
return self;
}
- (void)delloc
{
[url release];
[target release];
[super dealloc];
}
- (void)main
{
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:data, indexData, url, indexURL, nil];
[target performSelectorOnMainThread:action withObject:result waitUntilDone:NO];
[data release];
}
@end
@implementation WebGetOperation
-(id)initWithWebURL:(NSString *)theURL indexData:(NSString *)theIndexData indexURL:(NSString *)theIndexURL target:(id)theTarget action:(SEL)theAction
{
if( ( self = [super init] ) )
{
url = [theURL retain];
indexData = [theIndexData retain];
indexURL= [theIndexURL retain];
target = [theTarget retain];
action = theAction;
}
return self;
}
- (void)delloc
{
[url release];
[target release];
[super dealloc];
}
- (void)main
{
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:data, indexData, url, indexURL, nil];
[target performSelectorOnMainThread:action withObject:result waitUntilDone:NO];
[data release];
}
@end
WebBrowserViewController.h
#import <UIKit/UIKit.h>
@interface WebBrowserViewController : UIViewController {
UIActivityIndicatorView *spinner;
UILabel *loadingLabel;
NSOperationQueue *opQueue;
UIWebView *webView;
}
@end
@interface WebBrowserViewController : UIViewController {
UIActivityIndicatorView *spinner;
UILabel *loadingLabel;
NSOperationQueue *opQueue;
UIWebView *webView;
}
@end
WebBrowserViewController.m
#import "WebBrowserViewController.h"
#import "WebGetOperation.h"
@implementation WebBrowserViewController
- (void)waitForLoading
{
if(!spinner) // from CS193P - 2010 Winter, 11-ThreadedFlickrTableView.zip
{
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[spinner startAnimating];
loadingLabel = [[UILabel alloc] initWithFrame:CGRectZero];
loadingLabel.font = [UIFont systemFontOfSize:20];
loadingLabel.textColor = [UIColor grayColor];
loadingLabel.text = @"Loading...";
[loadingLabel sizeToFit];
static CGFloat bufferWidth = 8.0;
CGFloat totalWidth = spinner.frame.size.width + bufferWidth + loadingLabel.frame.size.width;
CGRect spinnerFrame = spinner.frame;
spinnerFrame.origin.x = (self.view.bounds.size.width - totalWidth) / 2.0;
spinnerFrame.origin.y = (self.view.bounds.size.height - spinnerFrame.size.height) / 2.0;
spinner.frame = spinnerFrame;
[self.view addSubview:spinner];
CGRect labelFrame = loadingLabel.frame;
labelFrame.origin.x = (self.view.bounds.size.width - totalWidth) / 2.0 + spinnerFrame.size.width + bufferWidth;
labelFrame.origin.y = (self.view.bounds.size.height - labelFrame.size.height) / 2.0;
loadingLabel.frame = labelFrame;
[self.view addSubview:loadingLabel];
}
}
- (void)didFinishWithResult:(NSDictionary *)result
{
if( spinner )
{ //NSLog(@"%@",[[NSString alloc] initWithData:[result objectForKey:@"data"] encoding:NSASCIIStringEncoding]);
//[webView loadHTMLString:[[NSString alloc] initWithData:[result objectForKey:@"data"] encoding:NSASCIIStringEncoding] baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
//[webView loadData:[result objectForKey:@"data"] MIMEType:@"text/html" textEncodingName:nil baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
[webView loadData:[result objectForKey:@"data"] MIMEType:@"image/png" textEncodingName:@"binart" baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
// from CS193P - 2010 Winter, 11-ThreadedFlickrTableView.zip - Begin
[spinner stopAnimating];
[spinner removeFromSuperview];
[spinner release];
spinner = nil;
[loadingLabel removeFromSuperview];
[loadingLabel release];
loadingLabel = nil;
// End - from CS193P - 2010 Winter, 11-ThreadedFlickrTableView.zip
}
}
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
opQueue = [[NSOperationQueue alloc] init];
[opQueue setMaxConcurrentOperationCount:1];
webView = [[UIWebView alloc] init];
[webView setFrame:[[self view] frame]];
[[self view] addSubview:webView];
}
return self;
}
- (void)dealloc {
[webView release];
[opQueue release];
[super dealloc];
}
- (void)viewDidLoad {
[super viewDidLoad];
// sync loading...
//[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]]];
// async loading
//WebGetOperation *getData = [[WebGetOperation alloc] initWithWebURL:@"http://www.gogole.com/" indexData:@"data" indexURL:@"url" target:self action:@selector(didFinishWithResult:)];
WebGetOperation *getData = [[WebGetOperation alloc] initWithWebURL:@"http://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Googlelogo.png/300px-Googlelogo.png" indexData:@"data" indexURL:@"url" target:self action:@selector(didFinishWithResult:)];
[opQueue addOperation:getData];
[getData release];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self waitForLoading];
}
- (void)didReceiveMemoryWarning {
// 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 {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
@end
#import "WebGetOperation.h"
@implementation WebBrowserViewController
- (void)waitForLoading
{
if(!spinner) // from CS193P - 2010 Winter, 11-ThreadedFlickrTableView.zip
{
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[spinner startAnimating];
loadingLabel = [[UILabel alloc] initWithFrame:CGRectZero];
loadingLabel.font = [UIFont systemFontOfSize:20];
loadingLabel.textColor = [UIColor grayColor];
loadingLabel.text = @"Loading...";
[loadingLabel sizeToFit];
static CGFloat bufferWidth = 8.0;
CGFloat totalWidth = spinner.frame.size.width + bufferWidth + loadingLabel.frame.size.width;
CGRect spinnerFrame = spinner.frame;
spinnerFrame.origin.x = (self.view.bounds.size.width - totalWidth) / 2.0;
spinnerFrame.origin.y = (self.view.bounds.size.height - spinnerFrame.size.height) / 2.0;
spinner.frame = spinnerFrame;
[self.view addSubview:spinner];
CGRect labelFrame = loadingLabel.frame;
labelFrame.origin.x = (self.view.bounds.size.width - totalWidth) / 2.0 + spinnerFrame.size.width + bufferWidth;
labelFrame.origin.y = (self.view.bounds.size.height - labelFrame.size.height) / 2.0;
loadingLabel.frame = labelFrame;
[self.view addSubview:loadingLabel];
}
}
- (void)didFinishWithResult:(NSDictionary *)result
{
if( spinner )
{ //NSLog(@"%@",[[NSString alloc] initWithData:[result objectForKey:@"data"] encoding:NSASCIIStringEncoding]);
//[webView loadHTMLString:[[NSString alloc] initWithData:[result objectForKey:@"data"] encoding:NSASCIIStringEncoding] baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
//[webView loadData:[result objectForKey:@"data"] MIMEType:@"text/html" textEncodingName:nil baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
[webView loadData:[result objectForKey:@"data"] MIMEType:@"image/png" textEncodingName:@"binart" baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
// from CS193P - 2010 Winter, 11-ThreadedFlickrTableView.zip - Begin
[spinner stopAnimating];
[spinner removeFromSuperview];
[spinner release];
spinner = nil;
[loadingLabel removeFromSuperview];
[loadingLabel release];
loadingLabel = nil;
// End - from CS193P - 2010 Winter, 11-ThreadedFlickrTableView.zip
}
}
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
opQueue = [[NSOperationQueue alloc] init];
[opQueue setMaxConcurrentOperationCount:1];
webView = [[UIWebView alloc] init];
[webView setFrame:[[self view] frame]];
[[self view] addSubview:webView];
}
return self;
}
- (void)dealloc {
[webView release];
[opQueue release];
[super dealloc];
}
- (void)viewDidLoad {
[super viewDidLoad];
// sync loading...
//[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]]];
// async loading
//WebGetOperation *getData = [[WebGetOperation alloc] initWithWebURL:@"http://www.gogole.com/" indexData:@"data" indexURL:@"url" target:self action:@selector(didFinishWithResult:)];
WebGetOperation *getData = [[WebGetOperation alloc] initWithWebURL:@"http://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Googlelogo.png/300px-Googlelogo.png" indexData:@"data" indexURL:@"url" target:self action:@selector(didFinishWithResult:)];
[opQueue addOperation:getData];
[getData release];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self waitForLoading];
}
- (void)didReceiveMemoryWarning {
// 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 {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
@end
呈現:
此例為下載一個圖檔:http://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Googlelogo.png/300px-Googlelogo.png
@ - (void)didFinishWithResult:(NSDictionary *)result
[webView loadData:[result objectForKey:@"data"] MIMEType:@"image/png" textEncodingName:@"binart" baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
[webView loadData:[result objectForKey:@"data"] MIMEType:@"image/png" textEncodingName:@"binart" baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
此例為下載一個網頁:http://www.google.com,但使用 loadHTMLString 時,碰到編碼的問題,這是因為抓回來的資料是 Big5 編碼,但 iPhone 呈現是以 UTF-8 為主體,有一種修正的方式是把抓到的資料在轉成 UTF-8 ,但這樣太累了,且編碼我是從抓回來的 HTML code 看到的,並不是每個網頁都是 Big5 喔
@ - (void)didFinishWithResult:(NSDictionary *)result
[webView loadHTMLString:[[NSString alloc] initWithData:[result objectForKey:@"data"] encoding:NSASCIIStringEncoding] baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
[webView loadHTMLString:[[NSString alloc] initWithData:[result objectForKey:@"data"] encoding:NSASCIIStringEncoding] baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
此例為下載一個網頁:http://www.google.com,改用 loadData 並指定 MIME Type 為 text/html 即可處理,這才是正解囉!
@ - (void)didFinishWithResult:(NSDictionary *)result
[webView loadData:[result objectForKey:@"data"] MIMEType:@"text/html" textEncodingName:nil baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
[webView loadData:[result objectForKey:@"data"] MIMEType:@"text/html" textEncodingName:nil baseURL:[NSURL URLWithString:[result objectForKey:@"url"]]];
其他的筆記:
- 若只是純粹想用 UIWebView 把指定的 URL 取出來看看,並且不需上述的非同步方式,那其實很簡單,只需覆蓋掉以下函數:
- (void)viewDidLoad {
[super viewDidLoad];
// sync loading...
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]]];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
以上就不會出現 loading 的等待字樣 - 在使用 UIWebView 時,如果只是純粹用 [view addSubview:webView]; 時,結果仍是一片空白,那是因為沒有指定呈現的 frame ,可以試試這個:
[webView setFrame:[[self view] frame]];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]]];
[[self view] addSubview:webView]; - 在使用 NSOperation 時,要留意 target 是否仍存在,也就是晚點等 main 跑完後會做類似的動作:
[target performSelectorOnMainThread:action withObject:result waitUntilDone:NO];
這時候,要留意 target 是否還存在。當初有試過一個物件,類似當它在 init 時就會用 NSOperation 去做事,然後再測試這段程式時,很快地用以下的方式:
TestNSOperation *t = [[TestNSOperation alloc] init];
[t release];
當 t 裡頭使用 NSOperation 做事花較多時間時,將導致做完後沒辦法把結果丟回 t ,如此一來程式就會不正常結束,這都需要小心使用。
沒有留言:
張貼留言