Uploading large images using Base64 and JSON

11,567

Solution 1

I finally decided to upload the Base64 image splitting it into smaller substrings. In order to do so, and as I needed many NSURLConnections, I created a subclass named TagConnection which gives a tag for each connection so that there's no possible confusion between them.

Then I created a TagConnection property in MyViewController with the purpose of accessing it from any function. As you can see, there's the -startAsyncLoad:withTag: function that allocs and inits the TagConnection and the -connection:didReceiveData: one which deletes it when I receive a response from the server.

Referring to the -uploadImage function, firstly, it converts the image into string and then splits it and put the chunks inside the JSON request. It is called until the variable offset is larger than the string length which means that all the chunks have been uploaded.

You can also prove that every chunk has been successfully uploaded by checking the server response every time and only calling the -uploadImage function when it returns success.

I hope this has been a useful answer. Thanks.

TagConnection.h

@interface TagConnection : NSURLConnection {
    NSString *tag;
}

@property (strong, nonatomic) NSString *tag;

- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)tag;

@end

TagConnection.m

#import "TagConnection.h"

@implementation TagConnection

@synthesize tag;

- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)tag {
    self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];

    if (self) {
        self.tag = tag;
    }
    return self;
}

- (void)dealloc {
    [tag release];
    [super dealloc];
}

@end

MyViewController.h

#import "TagConnection.h"

@interface MyViewController : UIViewController

@property (strong, nonatomic) TagConnection *conn;

MyViewController.m

#import "MyViewController.h"

@interface MyViewController ()

@end

@synthesize conn;

bool stopSending = NO;
int chunkNum = 1;
int offset = 0;

- (IBAction) uploadImageButton:(id)sender {

    [self uploadImage];

}

- (void) startAsyncLoad:(NSMutableURLRequest *)request withTag:(NSString *)tag {

    self.conn = [[[TagConnection alloc] initWithRequest:request delegate:self startImmediately:YES tag:tag] autorelease];

}

- (void) uploadImage {

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.mywebpage.com/upload.json"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1000.0];

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *path = [NSString stringWithFormat:@"%@/design%i.png", docDir, designNum];
    NSLog(@"%@",path);

    NSData *imageData = UIImagePNGRepresentation([UIImage imageWithContentsOfFile:path]);
    [Base64 initialize];
    NSString *imageString = [Base64 encode:imageData];

    NSUInteger length = [imageString length];
    NSUInteger chunkSize = 1000;

    NSUInteger thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset;
    NSString *chunk = [imageString substringWithRange:NSMakeRange(offset, thisChunkSize)];
    offset += thisChunkSize;

    NSArray *keys = [NSArray arrayWithObjects:@"design",@"design_id",@"fragment_id",nil];
    NSArray *objects = [NSArray arrayWithObjects:chunk,@"design_id",[NSString stringWithFormat:@"%i", chunkNum],nil];
    NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];

    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:kNilOptions error:&error];

    [request setHTTPMethod:@"POST"];
    [request setValue:[NSString stringWithFormat:@"%d",[jsonData length]] forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:jsonData];

    [self startAsyncLoad:request withTag:[NSString stringWithFormat:@"tag%i",chunkNum]];

    if (offset > length) {
        stopSending = YES;
    }

}

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

    NSError *error;
    NSArray *responseData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
    if (!responseData) {
        NSLog(@"Error parsing JSON: %@", error);
    } else {
        if (stopSending == NO) {
            chunkNum++;
            [self.conn cancel];
            self.conn = nil;
            [self uploadImage];
        } else {
            NSLog(@"---------Image sent---------");
        }
    }

}

@end

Solution 2

Please don't think this is the last option, this is just my observation.

I think you should send that NSData in chunks instead of complete Data. I have seen such methodology in YouTube Video Uploading case.They send the Large set of NSData (NSData of Video File) in Chunks of many NSData.

They uses the Same Methodology for uploading the large data.

So should do google about the Youtube data Uploading API.And you should search out that method , YouTube Uploader Uses.

I hope it may help you .

Share:
11,567
IOS_DEV
Author by

IOS_DEV

iOS developer.

Updated on June 05, 2022

Comments

  • IOS_DEV
    IOS_DEV almost 2 years

    I am using this function to upload an image to a server using JSON. In order to do so, I first convert the image to NSData and then to NSString using Base64. The method works fine when the image is not very large but when I try to upload a 2Mb image, it crashes.

    The problem is that the server doesn't receive my image even though the didReceiveResponse method is called as well as the didReceiveData which returns (null). At first I thought it was a time out issue but even setting it to 1000.0 it still doesn't work. Any idea? Thanks for your time!

    Here's my current code:

     - (void) imageRequest {
    
       NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.myurltouploadimage.com/services/v1/upload.json"]];
    
       NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
       NSString *path = [NSString stringWithFormat:@"%@/design%i.png",docDir, designNum];
       NSLog(@"%@",path);
    
       NSData *imageData = UIImagePNGRepresentation([UIImage imageWithContentsOfFile:path]);
       [Base64 initialize];
       NSString *imageString = [Base64 encode:imageData];
    
       NSArray *keys = [NSArray arrayWithObjects:@"design",nil];
       NSArray *objects = [NSArray arrayWithObjects:imageString,nil];
       NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
    
       NSError *error;
       NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:kNilOptions error:&error];
    
       [request setHTTPMethod:@"POST"];
       [request setValue:[NSString stringWithFormat:@"%d",[jsonData length]] forHTTPHeaderField:@"Content-Length"];
       [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
       [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
       [request setHTTPBody:jsonData];
    
       [[NSURLConnection alloc] initWithRequest:request delegate:self];
    
       NSLog(@"Image uploaded");
    
    }
    
     - (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    
       NSLog(@"didReceiveResponse");
    
    }
    
     - (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    
       NSLog(@"%@",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
    
    }
    
  • IOS_DEV
    IOS_DEV about 11 years
  • Janak Nirmal
    Janak Nirmal about 11 years
    Can you please help for server side code too ? I am unable to do this in php side !
  • IOS_DEV
    IOS_DEV about 11 years
    I'm sorry but that's not up to me.