How to show the progress of copying a large file in iOS?

I am writing an iOS app. In my app, I want to copy some files from one folder to another. But because some files is too large, it will take a long time to finish the copy. So I want to add a progress bar to show the percentage of the copy. But I find that the file manager has no callback method to get the percentage. Does anyone have the good solution to it?

Answers


In high level :

  1. Run your copying process in a seperate thread (T1)
  2. Run another thread (T2) which reads periodically (say every 100ms) the destination file current_size.
  3. Calculate the percentage : current_size / total_size
  4. Update you progress bar ui element

I've created a simple class with @giorashc approach.

If anyone needs something like this feel free to use it.

The .h

#import <UIKit/UIKit.h>

@protocol IDCopyUtilsDelegate;

@interface IDCopyUtils : NSObject

@property (nonatomic, weak) id<IDCopyUtilsDelegate> delegate;

- (void)copyFileAtPath:(NSString *)sourcePath toPath:(NSString *)targetPath;

@end

// 3. Definition of the delegate's interface
@protocol IDCopyUtilsDelegate <NSObject>

- (void)setCopyProgress:(float)progress;
- (void)didFinishedCopyWithError:(NSError *)error;

@end

The .m

#import "IDCopyUtils.h"

@interface IDCopyUtils()

@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) NSString *sourcePath;
@property (nonatomic, strong) NSString *targetPath;

@end

@implementation IDCopyUtils

- (void)copyFileAtPath:(NSString *)sourcePath toPath:(NSString *)targetPath
{
    self.sourcePath = sourcePath;
    self.targetPath = targetPath;

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;

    if ([fileManager fileExistsAtPath:self.targetPath] == YES) {
        [fileManager removeItemAtPath:self.targetPath error:&error];
    }

    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.100
                                     target:self
                                   selector:@selector(checkFileSize)
                                   userInfo:nil
                                    repeats:YES];

    [self performSelector:@selector(startCopy) withObject:nil afterDelay:0.5];

}

- (void)checkFileSize
{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSDictionary *attributesSource = [[NSFileManager defaultManager] attributesOfItemAtPath:self.sourcePath error:NULL]; unsigned long long fileSize = [attributesSource fileSize];

        NSDictionary *attributesTarget = [[NSFileManager defaultManager] attributesOfItemAtPath:self.targetPath error:NULL]; unsigned long long fileSizeTarget = [attributesTarget fileSize];

        double progress = (float)fileSizeTarget / (float)fileSize;

        if (self.delegate && [self.delegate respondsToSelector:@selector(setCopyProgress:)])
        {
            [self.delegate setCopyProgress:progress];
        }

        NSLog(@"Size: %f", progress);
    });
}

- (void)startCopy
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSError *error;

        if ([fileManager fileExistsAtPath:self.targetPath] == YES) {
            [fileManager removeItemAtPath:self.targetPath error:&error];
        }

        if ([fileManager fileExistsAtPath:self.targetPath] == NO) {
            [fileManager copyItemAtPath:self.sourcePath toPath:self.targetPath error:&error];

            [self.timer invalidate];
            self.timer = nil;

            if (self.delegate && [self.delegate respondsToSelector:@selector(didFinishedCopyWithError:)])
            {
                [self.delegate didFinishedCopyWithError:error];
            }
        }
    });
}

@end

You can use it like this (for example):

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"iso"];
NSString *targetPath = [documentsDirectory stringByAppendingPathComponent:@"test.iso"];

IDCopyUtils *copyUtils = [[IDCopyUtils alloc] init];
copyUtils.delegate = self;
[copyUtils copyFileAtPath:sourcePath toPath:targetPath];

And you will we able to update you progress view and get notified when the file did finidhed copying using the delegate methods.


Need Your Help

Detect If Current Record is Deleted on Access Bound Form

ms-access access-vba

If user #1 deletes a record in our Access 2007 database (SQL Server ODBC Linked Tables), other users will likely show #Deleted# in that record (on Datasheet View form). On some occasions this can c...

android disable recent apps Key

android android-launcher

I have a custom home app a launcher

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.