iPhone SDK: formatting a numeric value with NSNumberFormatter

While working on an application that downloads a PDF document into a UIWebView, I wrote some code to display a progress bar while the user waits for the file to be downloaded. It turned out to be very simple to do this, but I hit a snag after trying to also display the amount of bytes that has been retrieved so far. Something like “0.45 Mb of 4.56 Mb”.

Problem was that by default NSNumberFormatter displays number with 3 decimal places, and I needed to customize that a bit, so I could get only 2 decimal places, and always a number before the decimal separator. For instance, getting “0.45 Mb” instead of just “.45 Mb”. The magical solution was the setPositiveFormat method, which allows the developer to specify these parameters.

Here’s the code that I ended up using, and it works great:

- (void)createProgressionAlertWithMessage:(NSString *)message {
    progressAlert = [[UIAlertView alloc] initWithTitle:message message:@"Please wait..." delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
    // Create the progress bar and add it to the alert
    progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(30.0f, 80.0f, 225.0f, 90.0f)];
    [progressAlert addSubview:progressView];
    [progressView setProgressViewStyle:UIProgressViewStyleBar];
 
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(90.0f, 90.0f, 225.0f, 40.0f)];
    label.backgroundColor = [UIColor clearColor];
    label.textColor = [UIColor whiteColor];
    label.font = [UIFont systemFontOfSize:12.0f];
    label.text = @"";
    label.tag = kDownloadCounterTag;
    [progressAlert addSubview:label];
 
    [progressAlert show];
    [progressAlert release];
}

That’s the method that creates the UIAlertView that will hold the progress bar, and also the UIProgressView itself to display the progress of the download. I added an extra label in there to finally display the actual bytes of the file as it is being streamed over.

For the actual download I’m using NSURLConnection so I can download the file asynchronously, and receive information while the download is progressing.

- (void)viewWillAppear:(BOOL)animated {
    NSString *file = [NSString stringWithFormat:@"http://domain.com/download?id=%@", self.docID];
    NSURL *fileURL = [NSURL URLWithString:file];
 
    NSURLRequest *req = [NSURLRequest requestWithURL:fileURL];
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:req delegate:self];
 
    [self createProgressionAlertWithMessage:@"Downloading document"];
}
 
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    [self.fileData setLength:0];
    self.totalFileSize = [NSNumber numberWithLongLong:[response expectedContentLength]];
}
 
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.fileData appendData:data];
 
    NSNumber *resourceLength = [NSNumber numberWithUnsignedInteger:[self.fileData length]];
    NSNumber *progress = [NSNumber numberWithFloat:([resourceLength floatValue] / [self.totalFileSize floatValue])];
    progressView.progress = [progress floatValue];
 
    const unsigned int bytes = 1024 * 1024;
    UILabel *label = (UILabel *)[progressAlert viewWithTag:kDownloadCounterTag];
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
    [formatter setPositiveFormat:@"##0.00"];
    NSNumber *partial = [NSNumber numberWithFloat:([resourceLength floatValue] / bytes)];
    NSNumber *total = [NSNumber numberWithFloat:([self.totalFileSize floatValue] / bytes)];
    label.text = [NSString stringWithFormat:@"%@ MB of %@ MB", [formatter stringFromNumber:partial], [formatter stringFromNumber:total]];
    [formatter release];
}
 
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [progressAlert dismissWithClickedButtonIndex:0 animated:YES];
}

I’m hoping this is useful to someone else. Let me know if there’s a better to do some of the things I’m doing here.

9 Comments »

  1. Waqas Qureshi said,

    April 20, 2009 @ 5:07 am

    Hi, I used this code of snippet in my app but it doesn’t show exact downloading percentage, so can you tell me the exact value of kDownloadCounterTag, means what would you set the default value.

  2. Harry said,

    June 10, 2009 @ 8:14 am

    @Waqas Just set it to 1.

  3. Enea said,

    August 20, 2009 @ 7:49 pm

    Hello i have an app already and Im pretty new at iphone development if i were to just copy and past your code into my view controller would that work? i cant seem to figure out how to make a simple app just for this. If you could help that would be a big relief.

  4. Tianli said,

    September 16, 2009 @ 9:06 pm

    Thanks man! This is what I am looking for.
    It really saved me a lot of time.

    \ \ | | / /
    ( 0 0 )
    ( 9 )
    v

  5. Tianli said,

    September 17, 2009 @ 8:52 am

    It’s great! Thanks you very much.
    BTW: I’ve put your blog to my bookmarks.

  6. Lee Armstrong said,

    December 26, 2009 @ 6:26 am

    Would you be able to post this into a sample project? I have pasted the code in but lots of undeclared values….

    Can you help please?

  7. Joe Schorn said,

    January 21, 2010 @ 4:58 pm

    Thanks! You just saved me from banging my head against the wall.

  8. arvind said,

    January 25, 2010 @ 5:45 am

    Hi prada,

    thanks a lot for your code , its very much what i wanted for the project i’m working on. :)

    i’m having a doubt on the mem management in your code.
    in the delegate method ‘connectiondidReceiveData’ you’re allocating numberformatter which shld have retain count of 1.
    how is this getting released? and also this delegate method is called multiple times, so
    multiple formatter objects would be existing ?
    i’m new to mem management in obj-c.

    thanks for your time. :)

  9. Dave P said,

    March 21, 2010 @ 11:13 am

    Hello,

    I m using your code and I really thank you for this …

    The only issue is how to declare ” totalFileSize” as long long integer: i tried many things but the result is not the real file size.

    Thank you.
    Dave

RSS feed for comments on this post · TrackBack URI

Leave a Comment