14

I want to upload multiple images in background using a single uploadTaskWithRequest method. While trying the following code returns Upload tasks from NSData are not supported in background sessions...please how to achieve this

func createRequest (param : NSDictionary ,imagearray :NSMutableArray, strURL : String) -> NSURLRequest {

    let boundary = generateBoundaryString()

    let url = NSURL(string: strURL)
    let request = NSMutableURLRequest(URL: url!)

    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    request.HTTPMethod = "POST"
    request.HTTPBody = createBodyWithParameters(param, image_array:imagearray,boundary: boundary);

    return request
}

   func createBodyWithParameters(parameters: NSDictionary,image_array:NSMutableArray,boundary: String) -> NSData {
 let body = NSMutableData()         
for (key, value) in parameters {
      if(value is String || value is NSString){
            body.appendString("--\(boundary)\r\n")
            body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
            body.appendString("\(value)\r\n")
        }
    }
    var i = 0;
    for image in image_array {
        let filename = "image\(i).jpg"
        let data = UIImagePNGRepresentation(image as! UIImage);
        let mimetype = "image/png"
        body.appendString("--\(boundary)\r\n")
        body.appendString("Content-Disposition: form-data; name=\"\(self.filePathKey)\"; filename=\"\(filename)\"\r\n")
        body.appendString("Content-Type: \(mimetype)\r\n\r\n")
        body.appendData(data!)
        body.appendString("\r\n")
        i += 1;
    }

    body.appendString("--\(boundary)--\r\n")
    //        NSLog("data %@",NSString(data: body, encoding: NSUTF8StringEncoding)!);
    return body
}

func postrequestwithformdata(requesturl:String,postparams:NSDictionary,postformadata:NSMutableArray,requestId:Int)  
{

    self.requestid = requestId;
    let requestformdata = self.createRequest(postparams, imagearray: postformadata, strURL: requesturl);
    let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(Contants.identifier)
    let session: NSURLSession = NSURLSession(configuration:configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue());
    let task: NSURLSessionUploadTask = session.uploadTaskWithRequest(requestformdata, fromData: requestformdata.HTTPBody!);
    task.resume();

}
Luke Van In
  • 5,215
  • 2
  • 24
  • 45
siva ganesh
  • 173
  • 1
  • 7

1 Answers1

20

To upload in a background session, the data must first saved to a file.

  1. Save the data to file using writeToFile:options:.
  2. Call NSURLSession uploadTaskWithRequest:fromFile: to create the task. Note that the request must not contain the data in the HTTPBody otherwise the upload will fail.
  3. Handle completion in the URLSession:didCompleteWithError: delegate method.

You may also want to handle uploads which complete while the app is in the background.

  1. Implement application:handleEventsForBackgroundURLSession:completionHandler in the AppDelegate.
  2. Create an NSURLSession with the provided identifier.
  3. Respond to the delegate methods as per a usual upload (e.g. handle the response in URLSession:didCompleteWithError:)
  4. Call URLSessionDidFinishEventsForBackgroundURLSession when you have completed processing the event.

To make this easier to manage, create one NSURLSession per upload task, each with a unique identifier.

Refer to the URL Session Programming Guide for implementation details.

Example AppDelegate:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, NSURLSessionDelegate, NSURLSessionTaskDelegate {

    var window: UIWindow?

    typealias CompletionHandler = () -> Void

    var completionHandlers = [String: CompletionHandler]()

    var sessions = [String: NSURLSession]()


    func upload(request: NSURLRequest, data: NSData)
    {
        // Create a unique identifier for the session.
        let sessionIdentifier = NSUUID().UUIDString

        let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!
        let fileURL = directoryURL.URLByAppendingPathComponent(sessionIdentifier)

        // Write data to cache file.
        data.writeToURL(fileURL, atomically: true);

        let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(sessionIdentifier)

        let session: NSURLSession = NSURLSession(
            configuration:configuration,
            delegate: self,
            delegateQueue: NSOperationQueue.mainQueue()
        )

        // Store the session, so that we don't recreate it if app resumes from suspend.
        sessions[sessionIdentifier] = session

        let task = session.uploadTaskWithRequest(request, fromFile: fileURL)

        task.resume()
    }

    // Called when the app becomes active, if an upload completed while the app was in the background.
    func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: CompletionHandler) {

        let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(identifier)

        if sessions[identifier] == nil {

            let session = NSURLSession(
                configuration: configuration,
                delegate: self,
                delegateQueue: NSOperationQueue.mainQueue()
            )

            sessions[identifier] = session
        }

        completionHandlers[identifier] = completionHandler
    }

    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {

        // Handle background session completion handlers.
        if let identifier = session.configuration.identifier {

            if let completionHandler = completionHandlers[identifier] {
                completionHandler()
                completionHandlers.removeValueForKey(identifier)
            }

            // Remove session
            sessions.removeValueForKey(identifier)
        }

        // Upload completed.
    }
}

To upload multiple images in a single request, the images must first be encoded into the multipart/formdata MIME type, as you have done. The difference being that this entire MIME message must be saved to a single file, which is the file that is uploaded to the server.

Here is an example which shows how to do this. It works by serialising the MIME parts directly to a file. You could also build up the message in an NSData, although you risk running into memory limitations when handling large files.

func uploadImages(request: NSURLRequest, images: [UIImage]) {

    let uuid = NSUUID().UUIDString
    let boundary = String(count: 24, repeatedValue: "-" as Character) + uuid

    // Open the file
    let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!

    let fileURL = directoryURL.URLByAppendingPathComponent(uuid)
    let filePath = fileURL.path!

    NSFileManager.defaultManager().createFileAtPath(filePath, contents: nil, attributes: nil)

    let file = NSFileHandle(forWritingAtPath: filePath)!


    // Write each image to a MIME part.
    let newline = "\r\n"

    for (i, image) in images.enumerate() {

        let partName = "image-\(i)"
        let partFilename = "\(partName).png"
        let partMimeType = "image/png"
        let partData = UIImagePNGRepresentation(image)

        // Write boundary header
        var header = ""
        header += "--\(boundary)" + newline
        header += "Content-Disposition: form-data; name=\"\(partName)\"; filename=\"\(partFilename)\"" + newline
        header += "Content-Type: \(partMimeType)" + newline
        header += newline

        let headerData = header.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        print("")
        print("Writing header #\(i)")
        print(header)

        print("Writing data")
        print("\(partData!.length) Bytes")

        // Write data
        file.writeData(headerData!)
        file.writeData(partData!)
    }

    // Write boundary footer
    var footer = ""
    footer += newline
    footer += "--\(boundary)--" + newline
    footer += newline

    print("")
    print("Writing footer")
    print(footer)

    let footerData = footer.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
    file.writeData(footerData!)

    file.closeFile()

    // Add the content type for the request to multipart.
    let outputRequest = request.copy() as! NSMutableURLRequest

    let contentType = "multipart/form-data; boundary=\(boundary)"
    outputRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")


    // Start uploading files.
    upload(outputRequest, fileURL: fileURL)
}
Luke Van In
  • 5,215
  • 2
  • 24
  • 45
  • but i have lot of image form data, how to handle this ? – siva ganesh Jul 13 '16 at 11:41
  • Please update your question to clarify what you are trying to do. As it is currently, your question does not mention anything about multiple images. – Luke Van In Jul 13 '16 at 11:43
  • Have a look at [this tutorial](http://www.kaleidosblog.com/how-to-upload-images-using-swift-2-send-multipart-post-request), which shows how to use MIME encoding to encode multiple images into a single upload request. – Luke Van In Jul 13 '16 at 11:54
  • In practice it is probably easier to just issue multiple upload requests. Give each request the same shared identifier so that the server can correlate and aggregate the uploads. Separate uploads is also *safer* since it's easier to resume if one upload fails. – Luke Van In Jul 13 '16 at 11:55
  • i had included code used to create a form data and then make it as post body – siva ganesh Jul 13 '16 at 12:16
  • for image in image_array { let filename = "image\(i).jpg" let data = UIImagePNGRepresentation(image as! UIImage); let mimetype = "image/png" body.appendString("--\(boundary)\r\n") body.appendString("Content-Disposition: form-data; name=\"\(self.filePathKey)\"; filename=\"\(filename)\"\r\n") body.appendString("Content-Type: \(mimetype)\r\n\r\n") body.appendData(data!) body.appendString("\r\n") i += 1; } – siva ganesh Jul 13 '16 at 12:19
  • Please include this code, and any other related code in createRequest, in your question. – Luke Van In Jul 13 '16 at 13:12
  • 1
    Updated the answer to show how to encode multiple image files to a single MIME multipart message file for upload. – Luke Van In Jul 13 '16 at 13:54
  • @LukeVanIn your 1st answer looks incomplete. You are not writing data to any file. The file will be empty. Plus, can you tell me if you save the file in .CachesDirectory, how can you delete it after the upload is complete. – Karanveer Singh Sep 06 '16 at 07:15
  • 1
    @KaranveerSingh The data is written in `file.writeData(partData!)`. iOS will automatically remove files in the caches directory when the system runs low on space. Or you can use `NSFileManager` to delete the file. – Luke Van In Sep 10 '16 at 15:25
  • 1
    @KaranveerSingh I re-read your comment and noticed you were referring to the first example. I have edited the answer to include the line to write the file using `NSData.writeToURL:atomically:`. – Luke Van In Sep 10 '16 at 15:40
  • How can i get the json response from the request? – Amber K Feb 15 '19 at 06:48
  • Can we call upoadTask with multipart string included in with http body? – Kiran Jasvanee Jul 02 '20 at 11:59
  • It is bad practice to create a session for each task and your app will get poor performance and less allocated resources by the system if you do so. – Rivera Jul 16 '20 at 16:26