GoCalf Blog 1/100 Algo&Math; 1/100 IT&Game; 1/100 Info&Sharing; 1/100 Personal&Other.

9十二/1110

iPhone开发:在UIAlertView中显示进度条

progress_icon
Part 2 of 4 in the series iOS开发笔记

今天这个问题是,在一个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大概长得是这样的:

progress_alert

带进度条的alert窗口


参考:

Series Navigation<< iPhone开发:隐藏系统状态栏iPhone开发:自定义控件RangeSlider(范围滑动条) >>
不一定相关的相关文章:
  1. iPhone开发:可拉伸的图片
  2. iPhone开发:自定义控件RangeSlider(范围滑动条)
  3. iPhone开发:隐藏系统状态栏

作者: Calf

评论 (10) 引用 (0)
  1. 准备开发App啦,赞的

  2. 方法挺好,但是苹果iOS文档 View and Window Architecture 最后一条是这样的:
     
    Do Not Customize Controls by Embedding Subviews
    Although it is technically possible to add subviews to the standard system controls—objects that inherit from UIControl—you should never customize them in this way. Controls that support customizations do so through explicit and well-documented interfaces in the control class itself. For example, the UIButton class contains methods for setting the title and background images for the button. Using the defined customization points means that your code will always work correctly. Circumventing these methods, by embedding a custom image view or label inside the button, might cause your application to behave incorrectly now or at some point in the future if the button’s implementation changes.
    不知有没有更好的解决方法。

    • 这确实是个问题,而且对于UIAlertView,文档中也专门说到:“The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.”。

      如果不这样搞,那只能是自己写一个类似的alert view了,工作量倒也不是特别大。

    • 最近由于要自定义AlertView的样式(改变背景图案,改变按钮图案,修改字体等),索性就重新写了一个CustomAlertView,除了可以修改样式外,还可以直接支持添加子元素。

  3. 我想一下.... processData 你是在哪里调用的呢? 我没办法模拟出你截图的效果

    • 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]];
      }
  4. 额....我采用了一种方法..不过不知道为什么不行,如果有时间的话,能不能帮我看看...
    代码如下
    //在 按钮事件里开启 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];
      }

Leave a comment

(required)

还没有引用.