I am trying to upload images selected by a user from their gallery, and then keep uploading them even if the user puts my app in background/suspended state and then starts using some other app.
But, along with image I want to send some dictionary parameters in httpBody (like the "Pro_Id") of the request keeping the image in multipart uploading
Background upload multiple images using single NSURLSession uploadTaskWithRequest
I have done the following as answered in Background upload multiple images using single NSURLSession uploadTaskWithRequest:
To upload in a background session, the data must first saved to a file.
- Save the data to file using writeToFile:options:.
- 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.
- Handle completion in the URLSession:didCompleteWithError: delegate method.
- You may also want to handle uploads which complete while the app is in the background.
Implement 1. 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.
struct Media {
let key: String
let filename: String
let data: Data
let mimeType: String
init?(withImage image: UIImage, forKey key: String) {
self.key = key
self.mimeType = "image/jpeg"
self.filename = "kyleleeheadiconimage234567.jpg"
guard let data = image.jpegData(compressionQuality: 0.7) else { return nil }
self.data = data
}
}
@IBAction func postRequest(_ sender: Any) {
let uploadURL: String = "http://abc.xyz.com/myapi/v24/uploadimageapi/sessionID?rtype=json"
let imageParams = [
"isEdit":"1",
"Pro_Id":"X86436",
"Profileid":"c0b7b9486b9257041979e6a45",
"Type":"MY_PLAN",
"Cover":"Y"]
guard let mediaImage = Media(withImage: UIImage(named: "5MB")!, forKey: "image") else { return }
let imageData = mediaImage.data
let randomFilename = "myImage"
let fullPath = getDocumentsDirectory().appendingPathComponent(randomFilename)
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: imageData, requiringSecureCoding: false)
try data.write(to: fullPath)
} catch {
print("Couldn't write file")
}
guard let url = URL(string: uploadURL) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
let boundary = generateBoundary()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.addValue("Client-ID f65203f7020dddc", forHTTPHeaderField: "Authorization")
request.addValue("12.2", forHTTPHeaderField: "OSVersion")
request.addValue("keep-alive", forHTTPHeaderField: "Connection")
let dataBody = createDataBody(withParameters: imageParams, media: nil, boundary: boundary)
request.httpBody = dataBody
ImageUploadManager.shared.imageUploadBackgroundTask = UIApplication.shared.beginBackgroundTask {
print(UIApplication.shared.backgroundTimeRemaining)
// upon completion, we make sure to clean the background task's status
UIApplication.shared.endBackgroundTask(ImageUploadManager.shared.imageUploadBackgroundTask!)
ImageUploadManager.shared.imageUploadBackgroundTask = UIBackgroundTaskIdentifier.invalid
}
let session = ImageUploadManager.shared.urlSession
session.uploadTask(with: request, fromFile: fullPath).resume()
}
func generateBoundary() -> String {
return "Boundary-\(NSUUID().uuidString)"
}
func createDataBody(withParameters params: Parameters?, media: [Media]?, boundary: String) -> Data {
let lineBreak = "\r\n"
var body = Data()
if let parameters = params {
for (key, value) in parameters {
body.append("--\(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
body.append("\(value + lineBreak)")
}
}
if let media = media {
for photo in media {
body.append("--\(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.filename)\"\(lineBreak)")
body.append("Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
body.append(photo.data)
body.append(lineBreak)
}
}
body.append("--\(boundary)--\(lineBreak)")
return body
}
class ImageUploadManager: NSObject, URLSessionDownloadDelegate {
static var shared: ImageUploadManager = ImageUploadManager()
var imageUploadBackgroundTask: UIBackgroundTaskIdentifier?
private override init() { super.init()}
lazy var urlSession: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: " ***My-Background-Upload-Session-********* ")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
config.isDiscretionary = false
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())//nil)
}()
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
print("Progress \(downloadTask) \(progress)")
}
}
//first this method gets called after the call coming to appDelegate's handleEventsForBackgroundURLSession method, then moves to didCompleteWithError
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("************* Download finished ************* : \(location)")
try? FileManager.default.removeItem(at: location)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("************* Task completed ************* : \n\n\n \(task), error: \(error) \n************\n\n\n")
UIApplication.shared.endBackgroundTask(self.imageUploadBackgroundTask!)
self.imageUploadBackgroundTask = UIBackgroundTaskIdentifier.invalid
print(task.response)
if error == nil{
}else{
}
}
}
I should be getting a success message saying that image uploaded but Instead I am getting error message in response saying please specify "pro_ID" in the request.
Is there anything wrong with my implementation? And, how do other ios apps upload images in the background, they too must be sending some data to tell to which object in the backend does that image belongs to?