今天這個問題是,在一個iPhone程序中,我要在後臺做大量的數據處理,希望在界面上顯示一個進度條(Progress Bar)使得用戶瞭解處理進度。這個進度條應該是在一個模態的窗口中,使界
今天這個問題是,在一個iPhone程序中,我要在後臺做大量的數據處理,希望在界面上顯示一個進度條(Progress Bar)使得用戶瞭解處理進度。這個進度條應該是在一個模態的窗口中,使界面上其他控件無法被操作。怎麼用最簡單的方法來實現這個功能?UIAlertView是一個現成的模態窗口,如果能把進度條嵌入到它裏面就好了。
以下內容適用於iOS 2.0+。
我們知道,如果要顯示一個alert窗口(比如用來顯示錯誤或警告信息、詢問用戶是否確認某操作等等),只要簡單地創建一個UIAlertView對象,再調用其show方法即可。示意代碼如下:
1
2
3
4
5
6
7 UIAlertView* alertView = [[[UIAlertView alloc] initWithTitle:@"Title"
message:@"Message"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil]
autorelease];
[alertView show];
如果要添加一個進度條,只要先創建並設置好一個UIProgressView的實例,再利用addSubbiew方法添加到alertView中即可。
在實際應用中,我可能需要在類中保存進度條的對象實例,以便更新其狀態,因此先在自己的ViewController類中添加成員變量:
1
2
3
4
5
6
7
8
9 // MySampleViewController.h
#import <UIKit/UIKit.h>
@interface MySampleViewController : UIViewController {
@private
UIProgressView* progressView_;
}
@end
接下來寫一個叫做showProgressAlert的方法來創建並顯示帶有進度條的alert窗口,其中高亮的部分就是把進度條添加到alertView中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 - (void)showProgressAlert:(NSString*)title withMessage:(NSString*)message {
UIAlertView* alertView = [[[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:nil]
autorelease];
progressView_ = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
progressView_.frame = CGRectMake(30, 80, 225, 30);
[alertView addSubview:progressView_];
[alertView show];
}
爲了讓數據處理的子進程能夠方便地修改進度條的值,再添加一個簡單的方法:
1
2
3 - (void)updateProgress:(NSNumber*)progress {
progressView_.progress = [progress floatValue];
}
另外,數據處理完畢後,我們還需要讓進度條以及alertView消失,由於之前並沒有保存alertView的實例,可以通過進度條的superview訪問之:
1
2
3
4
5
6
7
8
9
10
11
12
13 - (void)dismissProgressAlert {
if (progressView_ == nil) {
return;
}
if ([progressView_.superview isKindOfClass:[UIAlertView class]]) {
UIAlertView* alertView = (UIAlertView*)progressView_.superview;
[alertView dismissWithClickedButtonIndex:0 animated:NO];
}
[progressView_ release];
progressView_ = nil;
}
假設處理數據的方法叫processData,當然它會在一個單獨的線程中運行,下面的片段示意瞭如何更新進度條狀態,以及最後如何讓它消失。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 - (void)processData:(int)total {
for (int i = 0; i < total; ++i) {
// Update UI to show progess.
float progress = (float)i / total;
NSNumber* progressNumber = [NSNumber numberWithFloat:progress];
[self performSelectorOnMainThread:@selector(updateProgress:)
withObject:progressNumber
waitUntilDone:NO];
// Process.
// do it.
}
// Finished.
[self performSelectorOnMainThread:@selector(dismissProgressAlert)
withObject:nil
waitUntilDone:YES];
// Other finalizations.
}
在實際使用中,帶進度條的alert view大概長得是這樣的:
processData需要在另外一個線程中。我目前用到了兩種執行的方式:
一種是在單獨的線程中處理數據,並通過performSelectorOnMainThread方法通知主線程刷新UI。比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL);
[instanceOfAVAssetWriterInput requestMediaDataWhenReadyOnQueue:mediaInputQueue usingBlock:^(void) {
// This block will be called asynchronously by Grand Central Dispatch periodically.
while ([instanceOfAVAssetWriterInput.readyForMoreMediaData) {
// processing ...
// .......
// Update UI to show progress.
NSNumber* progressNumber = [NSNumber numberWithFloat:progress];
[self performSelectorOnMainThread:@selector(updateProgress:)
withObject:progressNumber
waitUntilDone:NO];
} // Ends of while
} // Ends of ^block
dispatch_release(mediaInputQueue);
另一個方式也差不多,是直接調用一個異步處理方法(比如AVAssetExportSession類的-(void)exportAsynchronouslyWithCompletionHandler:(void (^)(void))handler)。但由於這種異步處理方法是不受用戶代碼干擾的,所以再開一個NSTimer,當timer觸發的時候更新UI:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 - (void)mothod1 {
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:kTimerInterval
target:self
selector:@selector(timerFired:)
userInfo:nil
repeats:YES];
[instanceOfAVAssetExportSession_ exportAsynchronouslyWithCompletionHandler:^(void) {
[self performSelectorOnMainThread:@selector(onSaveFinished)
withObject:nil
waitUntilDone:YES];
}];
}
- (void)timerFired:(NSTimer*)theTimer {
[self updateProgress:[NSNumber numberWithFloat:instanceOfAVAssetExportSession_.progress]];
}
進度條刷新不了的情況
額....我採用了一種方法..不過不知道爲什麼不行,如果有時間的話,能不能幫我看看...
代碼如下
//在 按鈕事件裏開啓 AlertView
-(IBAction) click :(id)sender
{
[self showProgressAlert:@"title" withMessage:@"message"];
}
//在按鈕事件 裏面觸發 processData 方法
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex==1) {// 這個按鈕 是提示框的確定按鈕
[self processData:1000];
}
}
但是 .. 進度條不動.....
可能的原因
這可能是因爲你沒有在單獨的線程中處理數據。在主線程中,如果你的函數仍在運行,UI基本上不會被更新的。
你可以用NSThread來創建新的線程,並在新的線程中處理數據,比如這樣(注意這裏的processData跟我上面提供的有一些小的差異,一方面把它的參數改成NSObject的子類,另一方面在其內部添加了autorelease pool):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 - (void)progressButtonClicked:(id)sender {
[self showProgressAlert:@"Working" withMessage:@"Processing data"];
[NSThread detachNewThreadSelector:@selector(processData:)
toTarget:self
withObject:[NSNumber numberWithInt:100]];
}
- (void)processData:(NSNumber*)total {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
int totalData = [total intValue];
for (int i = 0; i < totalData; ++i) {
// Update UI to show progess.
float progress = (float)i / totalData;
NSNumber* progressNumber = [NSNumber numberWithFloat:progress];
[self performSelectorOnMainThread:@selector(updateProgress:)
withObject:progressNumber
waitUntilDone:NO];
// Process.
[NSThread sleepForTimeInterval:0.1];
}
// Finished.
[self performSelectorOnMainThread:@selector(dismissProgressAlert)
withObject:nil
waitUntilDone:YES];
// Other finalizations.
[pool release];
[NSThread exit];
}