Sending multiple Images to Web server - ios

I am working in an app, where i need to send 3 images to a Web Server. I don't know the perfect method that works fast and efficient.
I have 3 UIImageView that capture image data from camera or photo album. Below,I am using AFNetworking to send 1 image to Web Server.
NSString *imgPath = [[NSBundle mainBundle]pathForResource:#"Default" ofType:#"png"];
NSData *imgData = UIImagePNGRepresentation([UIImage imageWithContentsOfFile:imgPath]);
NSData *imagVIewData = UIImageJPEGRepresentation(imageView1.image,90);
if (imagVIewData) {
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://myurl.com/xxx.php]];
NSMutableURLRequest *myRequest = [client multipartFormRequestWithMethod:#"POST" path:Nil parameters:Nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:imagVIewData name:#"file_upload" fileName:#"123.jpg" mimeType:#"images/jpeg"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:myRequest];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes",totalBytesWritten,totalBytesExpectedToWrite);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"upload complete");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#",operation.responseString);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation];
}
}
i need someone advice to send 3 different images from 3 UIImageViews. Is it possible with this program or do i need to work through different methods?
any idea?

The majority of the code you have can actually be kept. Why not try putting all the JPEG representations of the images into an array
NSArray *myArrayOfImages = #[Image1,Image2,Image3]
NSArray *myArrayOfNames = #[strings..]
NSArray *myArrayOfFileNames = #[strings..]
Then within the constructing body with block parameter put something like this..
for(int i=0; i < myArrayOfImages.length; i++){
NSData *temp = [myArrayOfImages objectAtIndex:i];
NSString *tempFile = [myArrayOfNames objectAtIndex:i]
NSString *tempFile = [myArrayOfFileNames objectAtIndex:i]
[formData appendPartWithFileData:temp name:tempName fileName:tempFile mimeType:#"images/jpeg"];
}
you could also use a dictionary or whatever data structure you want, point is you just loop over and append within the constructing block.

Related

Jsonobject + Image Multipart AFNetworking

We are trying to send multi-part request to server using AFNetworking. We need to send one json object and two image files. Following is the curl request for same.
curl -X POST http://localhost:8080/Circle/restapi/offer/add -H "Content-Type: multipart/form-data" -F "offerDTO={"code": null,"name": "Merry X'Mas - 1","remark": "25% discount on every purchase","validityDate": "22-12-2014","domainCode": "DO - 1","merchantCode": "M-4","isApproved": false,"discountValue": 25,"discountType": "PERCENTAGE"};type=application/json" -F "image=#Team Page.png;type=image/png" -F "letterhead=#Team Page.png;type=image/png"
I know this should be fairly easy as I've implemented the server as well as android code for same. And my friend is working on iOS part of this. Also I searched a lot on google, but did not get anything useful. So, I know its against the rules of StackOverflow, but can you guys give me the code for same using AfNetworking? If not please redirect me to examples on same lines.
Edit:
Please find below code that we tried:
UIImage *imageToPost = [UIImage imageNamed:#"1.png"];
NSData *imageData = UIImageJPEGRepresentation(imageToPost, 1.0);
offerDTO = [[NSMutableDictionary alloc]init];
[offerDTO setObject(angry)"" forKey:#"code"];
[offerDTO setObject:[NSString stringWithFormat:#"Testing"] forKey:#"discountDiscription"];
[offerDTO setObject:[NSString stringWithFormat:#"Test"] forKey:#"remark"];
[offerDTO setObject:#"07-05-2015" forKey:#"validityDate"];
[offerDTO setObject:#"C-7" forKey:#"creatorCode"];
[offerDTO setObject:#"M-1" forKey:#"merchantCode"];
[offerDTO setObject:[NSNumber numberWithBool:true] forKey:#"isApproved"];
[offerDTO setObject:#"2.4" forKey:#"discountValue"];
[offerDTO setObject:[NSString stringWithFormat:#"FREE"] forKey:#"discountType"];
NSURL *urlsss = [NSURL URLWithString:#"http://serverurl:8180"];
AFHTTPClient *client= [AFHTTPClient clientWithBaseURL:urlsss];
NSMutableURLRequest *request = [client multipartFormRequestWithMethod:#"POST"
path:#"/restapi/offer/add" parameters:nil constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
{
NSData *myData = [NSJSONSerialization dataWithJSONObject:offerDTO
options:NSJSONWritingPrettyPrinted
error:NULL];
[formData appendPartWithFileData:imageData name:#"image"
fileName:#"image.jpg"
mimeType:#"image/jpeg"];
[formData appendPartWithFileData:imageData name:#"letterhead"
fileName:#"image.jpg"
mimeType:#"image/jpeg"];
[formData appendPartWithFormData:myData name:#"offerDTO"];
}
];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSDictionary *jsons = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:nil];
}
failure:^(AFHTTPRequestOperation operation, NSError error)
{
NSLog(#"error: %#", [operation error]);
}
];
A couple of observations:
Your example is AFNetworking 1.x. AFNetworking 3.x rendition might look like:
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:#"1" withExtension:#"png"];
// If you need to build dictionary dynamically as in your question, that's fine,
// but sometimes array literals are more concise way if the structure of
// the dictionary is always the same.
// Also note that these keys do _not_ match what are present in the `curl`
// so please double check these keys (e.g. `discountDiscription` vs
// `discountDescription` vs `name`)!
NSDictionary *offerDTO = #{#"code" : #"",
#"discountDescription" : #"Testing",
#"remark" : #"Test",
#"validityDate" : #"07-05-2015",
#"creatorCode" : #"C-7",
#"merchantCode" : #"M-1",
#"isApproved" : #YES,
#"discountValue" : #2.4,
#"discountType" : #"FREE"};
// `AFHTTPSessionManager` is AFNetworking 3.x equivalent to `AFHTTPClient` in AFNetworking 1.x
NSURL *baseURL = [NSURL URLWithString:#"http://serverurl:8180"];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
// The `POST` method both creates and issues the request
[manager POST:#"/restapi/offer/add" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
NSError *error;
BOOL success;
success = [formData appendPartWithFileURL:fileURL
name:#"image"
fileName:#"image.jpg"
mimeType:#"image/png"
error:&error];
NSAssert(success, #"Failure adding file: %#", error);
success = [formData appendPartWithFileURL:fileURL
name:#"letterhead"
fileName:#"image.jpg"
mimeType:#"image/png"
error:&error];
NSAssert(success, #"Failure adding file: %#", error);
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:offerDTO options:0 error:&error];
NSAssert(jsonData, #"Failure building JSON: %#", error);
// You could just do:
//
// [formData appendPartWithFormData:jsonData name:#"offerDTO"];
//
// but I now notice that in your `curl`, you set the `Content-Type` for the
// part, so if you want to do that, you could do it like so:
NSDictionary *jsonHeaders = #{#"Content-Disposition" : #"form-data; name=\"offerDTO\"",
#"Content-Type" : #"application/json"};
[formData appendPartWithHeaders:jsonHeaders body:jsonData];
} progress:^(NSProgress * _Nonnull uploadProgress) {
// do whatever you want here
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(#"responseObject = %#", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"error = %#", error);
}];
You are creating an operation here, but never add it to a queue to start it. I assume you do that elsewhere. It's worth noting that AFHTTPSessionManager doesn't support operations like the deprecated AFHTTPRequestOperationManager or AFHTTPClient used to. The above code just starts the operation automatically.
Note, AFNetworking now assumes the response will be JSON. Given that your code suggests the response is JSON, then note that no JSONObjectWithData is needed, as that's done for you already.
Right now your code is (a) creating UIImage; (b) converting it back to a NSData; and (c) adding that to the formData. That is inefficient for a number of reasons:
Specifically, by taking the image asset, loading it into a UIImage, and then using UIImageJPEGRepresentation, you may be making the resulting NSData considerably larger than the original asset. You might consider just grabbing the original asset, bypassing UIImage altogether, and sending that (obviously, if you're sending PNG, then change the mime-type, too).
The process of adding NSData to the request can result in larger memory footprint. Often if you supply a file name, it can keep the peak memory usage a bit lower.
you can pass your NSdictionary directly to manger post block in parametersfield
UIImage *imageToPost = [UIImage imageNamed:#"1.png"];
NSData *imageData = UIImageJPEGRepresentation(imageToPost, 1.0);
NSDictionary *offerDTO = #{#"code" : #"",
#"discountDescription" : #"Testing",
#"remark" : #"Test",
#"validityDate" : #"07-05-2015",
#"creatorCode" : #"C-7",
#"merchantCode" : #"M-1",
#"isApproved" : #YES,
#"discountValue" : #2.4,
#"discountType" : #"FREE"};
NSURL *baseURL = [NSURL URLWithString:#"http://serverurl:8180"];
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];
[manager POST:#"/restapi/offer/add" parameters:offerDTO constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:imageData name:#"image"
fileName:#"image.jpg"
mimeType:#"image/jpeg"];
[formData appendPartWithFileData:imageData name:#"letterhead"
fileName:#"image.jpg"
mimeType:#"image/jpeg"];
[formData appendPartWithHeaders:jsonHeaders body:jsonData];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"responseObject = %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error = %#", error);
}]
;

Memory pressure issue while downloading multiple files using AFNetworking

In my application i am trying to download thousands of images (each image size with a maximum of 3mb) and 10's of videos (each video size with a maximum of 100mb) and saving it in Documents Directory.
To achieve this i am using AFNetworking
Here my problem is i am getting all the data successfully when i am using a slow wifi (around 4mbps), but the same downloading if i am doing under a wifi with a speed of 100mbps the application is getting memory warning while downloading images and memory pressure issue while downloading videos and then application is crashing.
-(void) AddVideoIntoDocument :(NSString *)name :(NSString *)urlAddress{
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlAddress]];
[theRequest setTimeoutInterval:1000.0];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:theRequest];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:name];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
//NSLog(#"Download = %f", (float)totalBytesRead / totalBytesExpectedToRead);
}];
[operation start];
}
-(void)downloadRequestedImage : (NSString *)imageURL :(NSInteger) type :(NSString *)imgName{
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageURL]];
[theRequest setTimeoutInterval:10000.0];
AFHTTPRequestOperation *posterOperation = [[AFHTTPRequestOperation alloc] initWithRequest:theRequest];
posterOperation.responseSerializer = [AFImageResponseSerializer serializer];
[posterOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//NSLog(#"Response: %#", responseObject);
UIImage *secImg = responseObject;
if(type == 1) { // Delete the image from DB
[self removeImage:imgName];
}
[self AddImageIntoDocument:secImg :imgName];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image request failed with error: %#", error);
}];
[posterOperation start];
}
The above code i am looping according to the number of videos and images that i have to download
What is the reason behind that behaviour
I even have screen shots of memory allocation for both the scenarios
Please Help
Adding code for saving the downloaded images also
-(void)AddImageIntoDocument :(UIImage *)img :(NSString *)str{
if(img) {
NSData *pngData = UIImageJPEGRepresentation(img, 0.4);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePathName =[[paths objectAtIndex:0]stringByAppendingPathComponent:str];
[pngData writeToFile:filePathName atomically:YES];
}
else {
NSLog(#"Network Error while downloading the image!!! Please try again.");
}
}
The reason for this behavior is that you're loading your large files into memory (and presumably it's happening quickly enough that you app isn't having a chance to respond to memory pressure notifications).
You can mitigate this by controlling the peak memory usage by not loading these downloads into memory. When download large files, it's often better to stream them directly to persistent storage. To do this with AFNetworking, you can set the outputStream of the AFURLConnectionOperation, and it should stream the contents directly to that file, e.g.
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingPathComponent:[url lastPathComponent]]; // use whatever path is appropriate for your app
operation.outputStream = [[NSOutputStream alloc] initToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"successful");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failure: %#", error);
}];
[self.downloadQueue addOperation:operation];
BTW, you'll notice that I'm not just calling start on these requests. Personally, I always add them to a queue for which I've specified the maximum number of concurrent operations:
self.downloadQueue = [[NSOperationQueue alloc] init];
self.downloadQueue.maxConcurrentOperationCount = 4;
self.downloadQueue.name = #"com.domain.app.downloadQueue";
I think this is less critical regarding memory usage than the streaming of the results directly to a outputStream using persistent storage, but I find this is another mechanism for managing system resources when initiating many concurrent requests.
You can start using NSURLSession's downloadTask.
I think this will resolve your issue.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://someSite.com/somefile.zip"]];
[[NSURLSession sharedSession] downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
// Use location (it's file URL in your system)
}];

AFNetworking 2.0 download multiple images with completion

I'm trying to figure out a way to download multiple images with AFNewtorking 2.0. I've read a lot of posts here in SO, but can't find the answer I'm looking for, hope you guys can help me.
The problem is that I want to know when all of the downloads finished and if all images where downloaded.
So I have an array with image URL's ant trying to do something like this.
for(NSString *photoUrlString in self.photos){
NSURL *url = [NSURL URLWithString:photoUrlString];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image error: %#", error);
}];
[requestOperation start];
}
I've found some answers with putting these requests into a queue and setting max concurrent operations to 1. But don't know how that works really.
Any help is appreciated, thanks in advance!
for(Photo *photo in array){
//form the path where you want to save your downloaded image to
NSString *constPath = [photo imageFullPath];
//url of your photo
NSURL *url = [NSURL URLWithString:photo.serverPath];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
op.responseSerializer = [AFImageResponseSerializer serializer];
op.outputStream = [NSOutputStream outputStreamToFileAtPath:constPath append:NO];
op.queuePriority = NSOperationQueuePriorityLow;
[op setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead){
}];
op.completionBlock = ^{
//do whatever you want with the downloaded photo, it is stored in the path you create in constPath
};
[requestArray addObject:op];
}
NSArray *batches = [AFURLConnectionOperation batchOfRequestOperations:requestArray progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
//after all operations are completed this block is called
if (successBlock)
successBlock();
}];
[[NSOperationQueue mainQueue] addOperations:batches waitUntilFinished:NO];
Try this:
// _group, _queue are iVar variable
dispatch_group_t *_group = dispatch_group_create();
dispatch_queue_t *_queue = dispatch_queue_create("com.company.myqueue2", NULL);
// all files download
for(int i = 0 ; i < numberOfFileDownloads; i++){
dispatch_group_async(_group, _queue, ^{
// here is background thread;
// download file
});
}
// all files are download successfully, this method is called
dispatch_group_notify(_group, _queue, ^{
}
Check out +[AFURLConnectionOperation batchOfRequestOperations:progressBlock:completionBlock:]
Although it's not documented, implementation is self-explanatory. Also it allows you to monitor the progress.
You will need to have an array of HTTP operations prior to using this method (this is if you decided to stick to NSURLConnection-based implementation of AFNetworking).

iOS: manage a AFHTTPRequestOperationManager

In my app I should download some JSON files, then I store these URL in a plist as you ca see in my code. After I create an 'AFHTTPRequestOperationManager' and I create a loop where I add some operation for the numbers of my 'url_list'.
NSString* plistPath = [[NSBundle mainBundle] pathForResource:#"url_json" ofType:#"plist"];
NSArray *url_list = [NSArray arrayWithContentsOfFile:plistPath];
self.manager = [AFHTTPRequestOperationManager manager];
for (id element in url_list){
NSURL *url = [NSURL URLWithString:element];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFHTTPResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[self.manager.operationQueue addOperation:op];
}
Now this code should be fine, but I want to have two information:
what's the way to know the progress value of my 'manager'?, because I want to know the state of all operation in a single progress value
I want to know when an operation finish, because when an operation finish I should pass 'responseObject' to a method that parse this data
Can you help me?
Take a look at AFNetworking batching documentation:
https://github.com/AFNetworking/AFNetworking#batch-of-operations
It gives you an option to assign progress block which is called on single operation completion and on top of that you can assign completion block which will be called when all operations are completed.
If you need you can still assign completion block to single operation to parse responseObjects.

Uploading images to server

I am trying to upload an image to a server (that is already built) and I am getting errors like Request has timed out. Other methods of sending text and fetch data from the server are working properly. However, sending an image I found it hard to do it.
I am using the following code at the moment:
-(void)uploadImage:(NSData*)image callbackBlock: (void (^)(BOOL success)) callbackBlock
{
NSString *path = [NSString stringWithFormat:#"upload"];
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:image, #"image", nil];
[params addEntriesFromDictionary:self.sessionManager.authParameters];
NSMutableURLRequest *request = [self multipartFormRequestWithMethod:#"POST" path:path parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData>formData){
[formData appendPartWithFormData:image name:#"Image"];
}];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"!!!Response object: %#",responseObject);
callbackBlock(YES);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#",error.description);
callbackBlock(NO);
}];
[self enqueueHTTPRequestOperation:operation];
}
Do you have any idea what the problem is? Can you give me some suggestions or possible errors on the above code.
Thank you very much.
You can send your image as a base64 encoded text... This should work.
You can use this category to create base64 encoded image:
https://github.com/l4u/NSData-Base64

Resources