65

So this HTML code submits the data in the correct format for me.

<form action="https://www.example.com/register.php" method="post" enctype="multipart/form-data">
    Name: <input type="text" name="userName"><BR />
    Email: <input type="text" name="userEmail"><BR />
    Password: <input type="text" name="userPassword"><BR />
    Avatar: <input type="file" name="avatar"><BR />
    <input type="submit">
</form>

I've looked into a good number of articles on how to do a multipart/form-data POST on iOS, but none really explain what to do if there were normal parameters as well as the file upload.

Could you please help me with the code to POST this in Obj-C?

Thanks!

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Ali Almohsen
  • 1,311
  • 3
  • 13
  • 24

4 Answers4

178

The process is as follows:

  1. Create dictionary with the userName, userEmail, and userPassword parameters.

    NSDictionary *params = @{@"userName"     : @"rob",
                             @"userEmail"    : @"rob@email.com",
                             @"userPassword" : @"password"};
    
  2. Determine the path for the image:

    NSString *path = [[NSBundle mainBundle] pathForResource:@"avatar" ofType:@"png"];
    
  3. Create the request:

    NSString *boundary = [self generateBoundaryString];
    
    // configure the request
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"POST"];
    
    // set content type
    
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField: @"Content-Type"];
    
    // create body
    
    NSData *httpBody = [self createBodyWithBoundary:boundary parameters:params paths:@[path] fieldName:fieldName];
    
  4. This is the method used above to build the body of the request:

    - (NSData *)createBodyWithBoundary:(NSString *)boundary
                            parameters:(NSDictionary *)parameters
                                 paths:(NSArray *)paths
                             fieldName:(NSString *)fieldName {
        NSMutableData *httpBody = [NSMutableData data];
    
        // add params (all params are strings)
    
        [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", parameterKey] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue] dataUsingEncoding:NSUTF8StringEncoding]];
        }];
    
        // add image data
    
        for (NSString *path in paths) {
            NSString *filename  = [path lastPathComponent];
            NSData   *data      = [NSData dataWithContentsOfFile:path];
            NSString *mimetype  = [self mimeTypeForPath:path];
    
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, filename] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:data];
            [httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    
        [httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
        return httpBody;
    }
    
  5. The above uses the following utility methods:

    @import MobileCoreServices;    // only needed in iOS
    
    - (NSString *)mimeTypeForPath:(NSString *)path {
        // get a mime type for an extension using MobileCoreServices.framework
    
        CFStringRef extension = (__bridge CFStringRef)[path pathExtension];
        CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extension, NULL);
        assert(UTI != NULL);
    
        NSString *mimetype = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType));
        assert(mimetype != NULL);
    
        CFRelease(UTI);
    
        return mimetype;
    }
    
    - (NSString *)generateBoundaryString {
        return [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    }
    
  6. Then submit the request. There are many, many options here.

    For example, if using NSURLSession, you could create NSURLSessionUploadTask:

    NSURLSession *session = [NSURLSession sharedSession];  // use sharedSession or create your own
    
    NSURLSessionTask *task = [session uploadTaskWithRequest:request fromData:httpBody completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error = %@", error);
            return;
        }
    
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"result = %@", result);
    }];
    [task resume];
    

    Or you could create a NSURLSessionDataTask:

    request.HTTPBody = httpBody;
    
    NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error = %@", error);
            return;
        }
    
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"result = %@", result);
    }];
    [task resume];
    

    The above assumes that the server is just returning text response. It's better if the server returned JSON, in which case you'd use NSJSONSerialization rather than NSString method initWithData.

    Likewise, I'm using the completion block renditions of NSURLSession above, but feel free to use the richer delegate-based renditions, too. But that seems beyond the scope of this question, so I'll leave that to you.

But hopefully this illustrates the idea.


I'd be remiss if I didn't point that, much easier than the above, you can use AFNetworking, repeating steps 1 and 2 above, but then just calling:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer]; // only needed if the server is not returning JSON; if web service returns JSON, remove this line
NSURLSessionTask *task = [manager POST:urlString parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
    NSError *error;
    if (![formData appendPartWithFileURL:[NSURL fileURLWithPath:path] name:@"avatar" fileName:[path lastPathComponent] mimeType:@"image/png" error:&error]) {
        NSLog(@"error appending part: %@", error);
    }
}  progress:nil success:^(NSURLSessionTask *task, id responseObject) {
    NSLog(@"responseObject = %@", responseObject);
} failure:^(NSURLSessionTask *task, NSError *error) {
    NSLog(@"error = %@", error);
}];

if (!task) {
    NSLog(@"Creation of task failed.");
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    Note that you can also generate a UUID using NSUUID: `NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];` – Olivier Nov 07 '14 at 15:56
  • Yes, if you don't need to support iOS versions prior to 6.0, that's a very good way to do it. – Rob Nov 07 '14 at 16:16
  • 3
    hi, your answer is very usefull, but I just wanted to ask what's the fieldName NSString exactly, cause I don't see you creating it anywhere in your code, and I'm not sure what I'm exactly supposed to use there – Yannis P. Nov 27 '14 at 15:29
  • 4
    The `fieldName` is whatever field name your server code is looking for when identifying the uploads. For example, if your server code was written in PHP, this is whatever field name the server code uses in its `$_FILES` references. In the example included in the original question, this field name was `avatar`. – Rob Nov 27 '14 at 16:06
  • 1
    Very throughout answer. – EmilyJ Jan 04 '15 at 16:38
  • My network kit that should make such things easier. https://github.com/prcela/network-kit/blob/master/README.md – Krešimir Prcela Mar 02 '15 at 15:12
  • How do we send if there are many such dictionaries in an array (I mean array of dictionaries with name, ID, location, image etc., keys in each dictionary). Does AFNetworking has a solution to this? – Revanth Jun 10 '15 at 13:14
  • @Revanth - When requests take that sort of complicated structure, often people would create JSON request. If doing it yourself, build the body using `NSJSONSerialization`. If using using AFNetworking, you'd use the default `AFJSONRequestSerializer` rather than overriding the `requestSerializer` with `AFHTTPRequestSerializer`. The only trick is that the server code needs to change to parse JSON request. But if you search for that (e.g. "PHP JSON request"), you should find lots of references. – Rob Jun 10 '15 at 13:57
  • Thanks for the response @Rob. I am using AFNetworking I get your point to convert the post data into JSON. How to convert to JSON if one of my parameters in each dictionary is a UIImage? I think no AFNetworking method handles UIImage object. Convert UIImage to NSData? can you pls post some code? – Revanth Jun 10 '15 at 17:19
  • You cannot just insert a `UIImage` in a JSON request. But what you _can_ do is to take that `UIImage`, get the `NSData` for it (either by, ideally, going back to the original asset or, if you can't, using something like `UIImagePNGRepresentation`) and then use base64 encoding to convert that to a string. Then you can then include that base64 string in your dictionary that you send via JSON. And then your server can base64 decode in your server code. If you search "base 64 image json" or something like that, you'll probably get lots of references. – Rob Jun 10 '15 at 18:10
  • I am getting problem for nested dictionary parameter.. could you please explain how can we pass nested dictionary with image data...?? Thanks in advance.... – Jagdev Sendhav Feb 19 '17 at 05:43
  • @UserDev If you have both a nested dictionary you want to upload as well as a binary image, you theoretically could do a multipart request where the first part was JSON and the second part was the image file. That's a pretty strange format. Or, perhaps better, I'd do a plain old JSON request where the image data was base-64 encoded and just put it in the dictionary under a separate key, and then the server could base-64 decode the image data. – Rob Feb 19 '17 at 07:41
  • @Alshcompiler - You've attempted to edit this twice. Please stop it. You insist that `appendPartWithFileURL` method doesn't return `BOOL` value, but it does. Please refer to [the current AFNetworking documentation](http://cocoadocs.org/docsets/AFNetworking/3.1.0/Protocols/AFMultipartFormData.html#//api/name/appendPartWithFileURL:name:fileName:mimeType:error:) if you have any question. – Rob Mar 26 '17 at 07:26
  • @Rob just like this your every answer helped me to understand concepts more clearly! – Anurag Sharma Jun 07 '17 at 10:32
  • can I omit the _fieldName_?@Rob – iSrinivasan27 Nov 21 '17 at 06:09
  • Thanks @Rob for the answer. – Sunny Feb 22 '18 at 06:07
  • This worked great for me except the service I was using (Google Cloud Print) wouldn't accept my png, with error `Could Not Convert To PDF`. When I changed `NSData *data = [NSData dataWithContentsOfFile:path];` to `UIImage *testImage = [UIImage imageNamed:@"test-image"]; NSData *data = UIImagePNGRepresentation(testImage);` I was able to upload the image successfully. Thanks! – charmingToad May 10 '18 at 22:37
  • Excellent. Worked out of the box for me. Thanks. – Mark Coniglio Feb 12 '22 at 05:44
3

POST multiple images using multipart or form-data with Objective-C

-(void)multipleimageandstring
{
    NSString *urlString=@"URL NAME";

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init] ;
    [request setURL:[NSURL URLWithString:urlString]];
    [request setHTTPMethod:@"POST"];

    NSMutableData *body = [NSMutableData data];

    NSString *boundary = @"---------------------------14737809831466499882746641449";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request addValue:contentType forHTTPHeaderField:@"Content-Type"];

    // file
    float low_bound = 0;
    float high_bound =5000;
    float rndValue = (((float)arc4random()/0x100000000)*(high_bound-low_bound)+low_bound);//image1
    int intRndValue = (int)(rndValue + 0.5);
   NSString *str_image1 = [@(intRndValue) stringValue];

    UIImage *chosenImage1=[UIImage imageNamed:@"Purchase_GUI_curves-12 copy.png"];

    NSData *imageData = UIImageJPEGRepresentation(chosenImage1, 90);
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"files\"; filename=\"%@.png\"\r\n",str_image1] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithData:imageData]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];





    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"name\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Nilesh" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"apipassword\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithString:app.password] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"adminId\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithString:app.adminId] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    // close form
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    // set request body
    [request setHTTPBody:body];

    //return and test
    NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];

    NSLog(@"%@", returnString);

}
user3335966
  • 2,673
  • 4
  • 30
  • 33
2

Try to use this for both video and image data with different mime types.

NSDictionary *param;

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

// 1. Create `AFHTTPRequestSerializer` which will create your request.
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
NSMutableURLRequest *request;

NSData *fileData;
if ([objDoc.url containsString:@".mp4"]) {
    manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"application/json"];
    [serializer setValue:@"video/mp4" forHTTPHeaderField:@"Content-Type"];
    manager.requestSerializer = serializer;
}

// 2. Create an `NSMutableURLRequest`.

NSLog(@"filename =%@",objDoc.url);
request= [serializer multipartFormRequestWithMethod:@"POST" URLString:strUrl parameters:param constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {

    if ([objDoc.url containsString:@".mp4"]) {
        [formData appendPartWithFileData:fileData
                                    name:@"File"
                                fileName:@"video.mp4"
                                mimeType:@"video/mp4"];

    }else{
        [formData appendPartWithFileData:fileData
                                    name:@"File"
                                fileName:@"image.jpeg"
                                mimeType:@"image/jpeg"];
    }

} error:nil];

// 3. Create and use `AFHTTPRequestOperationManager` to create an `AFHTTPRequestOperation` from the `NSMutableURLRequest` that we just created.

self.objeDocument.isUploading = [NSNumber numberWithInt:1];

self.operation = [manager HTTPRequestOperationWithRequest:request
                                                  success:^(AFHTTPRequestOperation *operation, id responseObject) {

                                                      NSLog(@"Success %@", responseObject);
                                                  } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                                                      UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Error!" message:@"The document attached has failed to upload." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
                                                      [alert show];
                                                      [self.operation cancel];
                                                      NSLog(@"Failure %@", error.description);
                                                  }];


// 4. Set the progress block of the operation.
[self.operation setUploadProgressBlock:^(NSUInteger __unused bytesWritten,
                                         long long totalBytesWritten,
                                         long long totalBytesExpectedToWrite) {
    NSLog(@"Wrote %lld/%lld", totalBytesWritten, totalBytesExpectedToWrite);
    float progress = (float)totalBytesWritten/(float)totalBytesExpectedToWrite;


}];

// 5. Begin!
[self.operation start];
0

I struggled with this for a while, if you are looking for uploading multiple images or any other file types, you can do the following using AFNetworking 3.0

NSDictionary *params = @{key        : value,
                            ..... etc
                         };

 NSString *urlString = @"http://..... your endpoint url";

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer]; // only needed if the server is not returning JSON;  

NSURLSessionTask *task = [manager POST:urlString parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
    for (int x = 0 ; x< contentArray.count; x++) {
        AttachmentsModel *model = contentArray[x];

        if(model.type == ImageAttachmentType){
            [formData appendPartWithFileData:model.data name:model.name fileName:model.fileName mimeType:model.mimeType];

        }else if(model.type == AudioAttachmentType){
            NSURL *urlVideoFile = [NSURL fileURLWithPath:model.path];
            [formData appendPartWithFileURL:urlVideoFile name:model.name fileName:model.fileName mimeType:model.mimeType error:nil];
        }else{
            [formData appendPartWithFileURL:model.url name:model.name fileName:model.fileName mimeType:model.mimeType error:nil];

        }

    }
}  progress:nil success:^(NSURLSessionTask *task, id responseObject) {
    [Utility stopLoading];

     NSString *result = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
    NSLog(@"result = %@", result);

    id json = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:nil];


    if (block) {

        //your response comes here
    }

} failure:^(NSURLSessionTask *task, NSError *error) {
    NSLog(@"error = %@", error);

}];

if (!task) {
    NSLog(@"Creation of task failed.");
}

And here is how my AttachmentsModel looks like:

//  AttachmentsModel.h

typedef enum AttachmnetType{
    ImageAttachmentType,
    AudioAttachmentType,
    VideoAttachmentType
} AttachmnetType;

@interface AttachmentsModel : NSObject

@property (strong, nonatomic) NSString *path;
@property (strong, nonatomic) NSData *data;
@property (strong, nonatomic) NSString *mimeType;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *fileName;
@property (strong, nonatomic) NSURL *url;
Mutawe
  • 6,464
  • 3
  • 47
  • 90