Swift: Background upload using URLSession - ios

I'm trying to upload files to the s3 bucket using URLSession. I understood that to upload files in the background, I need to use uploadTask(with:fromFile:) method as mentioned here. So I am performing below steps to upload the file.
Create a background URLSession
lazy var session: URLSession = {
let bundleIdentifier = Bundle.main.bundleIdentifier!
let config = URLSessionConfiguration.background(withIdentifier: bundleIdentifier + ".background")
config.sharedContainerIdentifier = bundleIdentifier
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
Generate request with multipart data
Data Model
struct UploadRequest {
let destinationUrl: URL
let sourceURL: URL
let params: [String: String]
let fileName: String
let mimeType: String
}
private func requestAndPath(for
uploadParam: UploadRequest) -> (request: URLRequest,
filePath: URL)? {
// Create an empty file and append header, file content and footer to it
let uuid = UUID().uuidString
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let fileURL = directoryURL.appendingPathComponent(uuid)
let filePath = fileURL.path
FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
let file = FileHandle(forWritingAtPath: filePath)!
let boundary = UUID().uuidString
let newline = "\r\n"
do {
let partName = "file"
let data = try Data(contentsOf: uploadParam.sourceURL)
// Write boundary header
var header = ""
header += "--\(boundary)" + newline
header += "Content-Disposition: form-data; name=\"\(partName)\"; filename=\"\(uploadParam.fileName)\"" + newline
for (key, value) in uploadParam.params {
header += "Content-Disposition: form-data; name=\"\(key)" + newline
header += newline
header += value + newline
}
header += "Content-Type: \(uploadParam.mimeType)" + newline
header += newline
let headerData = header.data(using: .utf8, allowLossyConversion: false)
// Write data
file.write(headerData!)
file.write(data)
// Write boundary footer
var footer = ""
footer += newline
footer += "--\(boundary)--" + newline
footer += newline
let footerData = footer.data(using: .utf8, allowLossyConversion: false)
file.write(footerData!)
file.closeFile()
let contentType = "multipart/form-data; boundary=\(boundary)"
var urlRequest = URLRequest(url: uploadParam.destinationUrl)
urlRequest.httpMethod = "POST"
urlRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")
return (urlRequest, fileURL)
} catch {
debugPrint("Error generating url request")
}
return nil
}
Upload file
func uploadFile(request: UploadRequest) {
if let reqPath = requestAndPath(for: uploadRequest) {
let task = session.uploadTask(with: reqPath.request,
fromFile: reqPath.filePath)
task.resume()
}
}
When I call the uploadFile method, the delegate didSendBodyData is called once and the control goes to didCompleteWithError with error as nil. But the file is not uploaded to the s3 bucket. What could be the issue?
I am able to upload the file using Alamofire but since Alamofire doesn't support background upload, I would like to fallback to URLSession
Upload using Alamofire (default)
AF.upload(multipartFormData: { multipartFormData in
for (key, value) in uploadRequest.params {
if let data = value.data(using: String.Encoding.utf8, allowLossyConversion: false) {
multipartFormData.append(data, withName: key)
}
}
multipartFormData.append(
uploadRequest.sourceURL,
withName: "File",
fileName: uploadRequest.fileName,
mimeType: uploadRequest.mimeType
)
}, with: urlRequest).responseData { response in
if let responseData = response.data {
let strData = String(decoding: responseData, as: UTF8.self)
debugPrint("Response data \(strData)")
} else {
debugPrint("Error is \(response.error)")
}
}.uploadProgress { progress in
debugPrint("Progress \(progress)")
}

I made changes in the request body and wrote the data to the file and used uploadTask(with:fromFile:) method using the background session. urlSessionDidFinishEvents(forBackgroundURLSession:) will be called once the upload is completed when the app is in the background.
private func requestAndPath(for
uploadParam: UploadRequest) -> (request: URLRequest,
filePath: URL)? {
let uuid = UUID().uuidString
let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let fileURL = directoryURL.appendingPathComponent(uuid)
let filePath = fileURL.path
FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
let file = FileHandle(forWritingAtPath: filePath)!
let boundary = generateBoundary()
let lineBreak = "\r\n"
var body = Data()
for (key, value) in uploadParam.params {
body.append("--\(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
body.append("\(value + lineBreak)")
}
do {
let data = try Data(contentsOf: uploadParam.sourceURL)
body.append("--\(boundary + lineBreak)")
body.append("Content-Disposition: form-data; name=\"File\"; filename=\"\(uploadParam.fileName)\"\(lineBreak)")
body.append("Content-Type: \(uploadParam.mimeType + lineBreak + lineBreak)")
body.append(data)
body.append(lineBreak)
body.append("--\(boundary)--\(lineBreak)")
file.write(body)
file.closeFile()
var urlRequest = URLRequest(url: uploadParam.destinationUrl)
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
urlRequest.httpMethod = "POST"
return (urlRequest, fileURL)
} catch {
debugPrint("Error getting request")
}
return nil
}
func generateBoundary() -> String {
return UUID().uuidString
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
Reference: https://stackoverflow.com/a/58246456/696465

Related

How to create multipart form data request for image and json data using URLSession- Swift [duplicate]

i am having a problem with uploading image with multipart-form
here is my code i used from this answer
var request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
var boundary = generateBoundaryString()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = NSMutableData()
if self.img.image != nil {
var imageData = UIImagePNGRepresentation(self.img.image)
if imageData != nil {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"image.png\"\r\n")
body.appendString("Content-Type: image/png\r\n\r\n")
body.appendData(imageData!)
body.appendString("\r\n")
}
}
body.appendString("--\(boundary)--\r\n")
request.setValue("\(body.length)", forHTTPHeaderField:"Content-Length")
request.HTTPBody = body
then i use NSURLSession to apply the request
the server says that i didn't choose image to upload i only want to upload the image for now
do i have to use paths of images to upload any image or it's data is enough?
do i miss any thing , any help to understand this ?
No Need to use any library for upload images using multipart request.
Swift 4.2
func uploadImage(paramName: String, fileName: String, image: UIImage) {
let url = URL(string: "http://api-host-name/v1/api/uploadfile/single")
// generate boundary string using a unique per-app string
let boundary = UUID().uuidString
let session = URLSession.shared
// Set the URLRequest to POST and to the specified URL
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = "POST"
// Set Content-Type Header to multipart/form-data, this is equivalent to submitting form data with file upload in a web browser
// And the boundary is also set here
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var data = Data()
// Add the image data to the raw http request data
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
data.append(image.pngData()!)
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
// Send a POST request to the URL, with the data we created earlier
session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
if error == nil {
let jsonData = try? JSONSerialization.jsonObject(with: responseData!, options: .allowFragments)
if let json = jsonData as? [String: Any] {
print(json)
}
}
}).resume()
}
If you have any header to add, you can add it via urlRequest.setValue method.
Source: https://fluffy.es/upload-image-to-server/
My version that 100% works. Maybe it will help you.
let url = "http://server/upload"
let img = UIImage(contentsOfFile: fullPath)
let data: NSData = UIImageJPEGRepresentation(img, 1)
sendFile(url,
fileName:"one.jpg",
data:data,
completionHandler: completionHandler:{
(result:Bool, isNoInternetConnection:Bool) -> Void in
// ...
NSLog("Complete: \(result)")
}
)
func sendFile(
urlPath:String,
fileName:String,
data:NSData,
completionHandler: (NSURLResponse!, NSData!, NSError!) -> Void){
var url: NSURL = NSURL(string: urlPath)!
var request1: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request1.HTTPMethod = "POST"
let boundary = generateBoundary()
let fullData = photoDataToFormData(data,boundary:boundary,fileName:fileName)
request1.setValue("multipart/form-data; boundary=" + boundary,
forHTTPHeaderField: "Content-Type")
// REQUIRED!
request1.setValue(String(fullData.length), forHTTPHeaderField: "Content-Length")
request1.HTTPBody = fullData
request1.HTTPShouldHandleCookies = false
let queue:NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
request1,
queue: queue,
completionHandler:completionHandler)
}
// this is a very verbose version of that function
// you can shorten it, but i left it as-is for clarity
// and as an example
func photoDataToFormData(data:NSData,boundary:String,fileName:String) -> NSData {
var fullData = NSMutableData()
// 1 - Boundary should start with --
let lineOne = "--" + boundary + "\r\n"
fullData.appendData(lineOne.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 2
let lineTwo = "Content-Disposition: form-data; name=\"image\"; filename=\"" + fileName + "\"\r\n"
NSLog(lineTwo)
fullData.appendData(lineTwo.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 3
let lineThree = "Content-Type: image/jpeg\r\n\r\n"
fullData.appendData(lineThree.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 4
fullData.appendData(data)
// 5
let lineFive = "\r\n"
fullData.appendData(lineFive.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 6 - The end. Notice -- at the start and at the end
let lineSix = "--" + boundary + "--\r\n"
fullData.appendData(lineSix.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
return fullData
}
import Foundation
struct MultipartFormDataRequest {
private let boundary: String = UUID().uuidString
var httpBody = NSMutableData()
let url: URL
init(url: URL) {
self.url = url
}
func addTextField(named name: String, value: String) {
httpBody.appendString(textFormField(named: name, value: value))
}
private func textFormField(named name: String, value: String) -> String {
var fieldString = "--\(boundary)\r\n"
fieldString += "Content-Disposition: form-data; name=\"\(name)\"\r\n"
fieldString += "Content-Type: text/plain; charset=ISO-8859-1\r\n"
fieldString += "Content-Transfer-Encoding: 8bit\r\n"
fieldString += "\r\n"
fieldString += "\(value)\r\n"
return fieldString
}
func addDataField(fieldName: String, fileName: String, data: Data, mimeType: String) {
httpBody.append(dataFormField(fieldName: fieldName,fileName:fileName,data: data, mimeType: mimeType))
}
private func dataFormField(fieldName: String,
fileName: String,
data: Data,
mimeType: String) -> Data {
let fieldData = NSMutableData()
fieldData.appendString("--\(boundary)\r\n")
fieldData.appendString("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\r\n")
fieldData.appendString("Content-Type: \(mimeType)\r\n")
fieldData.appendString("\r\n")
fieldData.append(data)
fieldData.appendString("\r\n")
return fieldData as Data
}
func asURLRequest() -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
httpBody.appendString("--\(boundary)--")
request.httpBody = httpBody as Data
return request
}
}
extension NSMutableData {
func appendString(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
extension URLSession {
func dataTask(with request: MultipartFormDataRequest,
completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void)
-> URLSessionDataTask {
return dataTask(with: request.asURLRequest(), completionHandler: completionHandler)
}
}
Use this function to call upload file func
func uploadFile(file:Data, fileName: String, fileExtension: String){
var mimeType = "image/png"
if fileExtension == "PDF" {
mimeType = "application/pdf"
}
let url = "https://v2.convertapi.com/upload"
let request = MultipartFormDataRequest(url: URL(string: url)!)
request.addDataField(fieldName: "file", fileName: fileName, data: file, mimeType: mimeType)
URLSession.shared.dataTask(with: request, completionHandler: {data,urlResponse,error in
}).resume()
}
#Resources:
https://www.donnywals.com/uploading-images-and-forms-to-a-server-using-urlsession/
https://orjpap.github.io/swift/http/ios/urlsession/2021/04/26/Multipart-Form-Requests.html
public func UPLOADIMG(url: String,parameters: Dictionary<String,AnyObject>?,filename:String,image:UIImage, success:((NSDictionary) -> Void)!, failed:((NSDictionary) -> Void)!, errord:((NSError) -> Void)!) {
var TWITTERFON_FORM_BOUNDARY:String = "AaB03x"
let url = NSURL(string: url)!
var request:NSMutableURLRequest = NSMutableURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 10)
var MPboundary:String = "--\(TWITTERFON_FORM_BOUNDARY)"
var endMPboundary:String = "\(MPboundary)--"
//convert UIImage to NSData
var data:NSData = UIImagePNGRepresentation(image)
var body:NSMutableString = NSMutableString();
// with other params
if parameters != nil {
for (key, value) in parameters! {
body.appendFormat("\(MPboundary)\r\n")
body.appendFormat("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendFormat("\(value)\r\n")
}
}
// set upload image, name is the key of image
body.appendFormat("%#\r\n",MPboundary)
body.appendFormat("Content-Disposition: form-data; name=\"\(filename)\"; filename=\"pen111.png\"\r\n")
body.appendFormat("Content-Type: image/png\r\n\r\n")
var end:String = "\r\n\(endMPboundary)"
var myRequestData:NSMutableData = NSMutableData();
myRequestData.appendData(body.dataUsingEncoding(NSUTF8StringEncoding)!)
myRequestData.appendData(data)
myRequestData.appendData(end.dataUsingEncoding(NSUTF8StringEncoding)!)
var content:String = "multipart/form-data; boundary=\(TWITTERFON_FORM_BOUNDARY)"
request.setValue(content, forHTTPHeaderField: "Content-Type")
request.setValue("\(myRequestData.length)", forHTTPHeaderField: "Content-Length")
request.HTTPBody = myRequestData
request.HTTPMethod = "POST"
// var conn:NSURLConnection = NSURLConnection(request: request, delegate: self)!
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {
data, response, error in
if error != nil {
println(error)
errord(error)
return
}
var parseError: NSError?
let responseObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parseError)
if let responseDictionary = responseObject as? NSDictionary {
success(responseDictionary)
} else {
}
})
task.resume()
}
I suggest this repository.
You can add pod 'MultipartForm' in the Podfile and then follow the example in the repository's readme:
import MultipartForm
let form = MultipartForm(parts: [
MultipartForm.Part(name: "a", value: "1"),
MultipartForm.Part(name: "b", value: "2"),
MultipartForm.Part(name: "c", data: imageData, filename: "3.png", contentType: "image/png"),
])
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(form.contentType, forHTTPHeaderField: "Content-Type")
let task = session.uploadTask(with: request, from: form.bodyData)
task.resume()
It supports Swift Package Manager too.
extension URLRequest {
mutating func setMultipartFormDataBody(params: [String: (Data, filename: String?)]) {
let boundary = UUID().uuidString
self.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
for (key, (data, filename)) in params {
body.append("--\(boundary)\r\n")
if let filename = filename {
body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(filename)\"\r\n")
}
else {
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n")
}
body.append("\r\n")
body.append(data)
body.append("\r\n")
}
body.append("--\(boundary)--")
self.httpBody = body
}
}
extension Data {
mutating func append(_ s: String) {
self.append(s.data(using: .utf8)!)
}
}
The first thing I noticed is the application/octet-stream as Conten-Type, this is usually used when the file type is unknown. Some web frameworks/libraries will reject this content-type if an image is required.
Second, I can't see the post length anywhere, try to add it:
body.appendString("--\(boundary)--\r\n")
// set the content-length
request.setValue("\(body.length)", forHTTPHeaderField:"Content-Length")
class func postMultiPartdata( postdatadictionary: [AnyHashable: Any], apikey: String, completion: #escaping (Any) -> () ) {
if Utils().isConnectedToNetwork() == false
{
Utils().showMessage("Check internet")
return
}
let strURL = "http://redspark.biz/dropp/api/\(apikey)"
let url = URL(string: strURL)
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = "POST"
let body = NSMutableData();
let boundary = "---------------------------14737809831466499882746641449"
let contentType = "multipart/form-data; boundary=\(boundary)"
urlRequest.addValue(contentType, forHTTPHeaderField: "Content-Type")
for (key, value) in postdatadictionary {
if(value is Data)
{
let TimeStamp = "\(Date().timeIntervalSince1970 * 1000)"
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(TimeStamp)\"\r\n".data(using:.utf8)!)
body.append("field_mobileinfo_image\r\n".data(using: .utf8)!)
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"files[field_mobileinfo_image]\"; filename=\"img.jpg\"\r\n".data(using: .utf8)!)
body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
// var imgData: Data? = nil
// if let aKey = value as? Data {
// imgData = NSData(data: aKey) as Data
// }
body.append(value as! Data)
}
else
{
if let anEncoding = "--\(boundary)\r\n".data(using: .utf8) {
body.append(anEncoding)
}
if let anEncoding = "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8) {
body.append(anEncoding)
}
if let aKey = postdatadictionary[key], let anEncoding = "\(aKey)".data(using: .utf8) {
body.append(anEncoding)
}
if let anEncoding = "\r\n".data(using: .utf8) {
body.append(anEncoding)
}
}
}
if let anEncoding = "--\(boundary)--\r\n".data(using: .utf8) {
body.append(anEncoding)
}
// setting the body of the post to the reqeust
urlRequest.httpBody = body as Data
URLSession.shared.dataTask(with:urlRequest) { (data, response, error) in
if error != nil {
print(error!)
completion("")
} else {
var dictonary:NSDictionary?
do {
dictonary = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
if let myDictionary = dictonary
{
completion(myDictionary)
}
} catch let error as NSError {
completion(error)
}
}
Utils().HideLoader()
}.resume()
}
following those answers here is my implementation with a concrete example for video and audio. notice that the boundary between elements has such form --boundary while the last boundary is written --boudary--.
let url = URL(string: "https://...")!
let boundary = UUID().uuidString
var request = URLRequest(url: url)
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpMethod = "post"
var data = Data()
data.addMultiPart(boundary: boundary, name: "metadata", filename: "metadata.json", contentType: "application/json", data: metaData)
let ext = fileUrl.pathExtension.lowercased()
let isImage = ["jpg","jpeg","png"].contains(ext)
let contentType = isImage ? "image/\(ext)" : "video/\(ext)"
let mediaData = try! Data(contentsOf: fileUrl)
data.addMultiPart(boundary: boundary, name: "file", filename: fileUrl.lastPathComponent, contentType: contentType, data: mediaData)
data.addMultiPartEnd(boundary: boundary)
request.httpBody = data
let task = session.dataTask(with: request)
task.resume()
private extension Data {
mutating func addMultiPart(boundary: String, name: String, filename: String, contentType: String, data: Data) {
print("adding boundary: \(boundary), name: \(name), filename: \(filename), contentType: \(contentType) data length: \(data.count) ")
self.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
self.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
self.append("Content-Type: \(contentType)\r\n\r\n".data(using: .utf8)!)
self.append(data)
}
mutating func addMultiPartEnd(boundary: String) {
self.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
}
}
I implemented Upload image using Multi-part in Swift 4:
Here is the code. Please have a look
//MARK: Uplaod User Profile Pic
func uploadImageToServerFromApp(nameOfApi : NSString, parameters : NSString, uploadedImage : UIImage, withCurrentTask :RequestType, andDelegate :AnyObject)->Void {
if self.isConnectedToNetwork(){
currentTask = withCurrentTask
let myRequestUrl = NSString(format: "%#%#%#",GlobalConstants.KBaseURL,nameOfApi,parameters)
let url = (myRequestUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed))!
var request : NSMutableURLRequest = NSMutableURLRequest()
request = URLRequest(url: URL(string:url as String)!) as! NSMutableURLRequest
request.httpMethod = "POST"
let boundary = generateBoundaryString()
//define the multipart request type
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let image_data = UIImagePNGRepresentation(uploadedImage)
if(image_data == nil){
return
}
let body = NSMutableData()
let fname = "image.png"
let mimetype = "image/png"
//define the data post parameter
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"image\"\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append("hi\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"image\"; filename=\"\(fname)\"\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Type: \(mimetype)\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append(image_data!)
body.append("\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)--\r\n".data(using: String.Encoding.utf8)!)
request.httpBody = body as Data
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data = data, error == nil else { // check for fundamental networking error
// print("error=\(String(describing: error))")
self.showAlertMessage(title: "App name", message: "Server not responding, please try later")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
// print("statusCode should be 200, but is \(httpStatus.statusCode)")
// print("response = \(String(describing: response))")
self.delegate?.internetConnectionFailedIssue()
}else{
do {
self.responseDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! NSDictionary
// self.Responsedata = data as NSData
//self.responseDictionary = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: AnyObject] as NSDictionary;
self.delegate?.responseReceived()
} catch {
//print("error serializing JSON: \(error)")
}
}
}
task.resume()
}
else{
// print("Internet Connection not Available!")
self.showAlertMessage(title: "App Name", message: "No Internet Connection..")
}
}
func generateBoundaryString() -> String
{
return "Boundary-\(NSUUID().uuidString)"
}
Very good video and code:
https://www.youtube.com/watch?v=8GH0yMPvQFU
https://github.com/Kilo-Loco/URLSessionMPFD/blob/master/URLSessionMPFD/ViewController.swift
import UIKit
typealias Parameters = [String: String]
class ViewController: UIViewController {
#IBAction func getRequest(_ sender: Any) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
var request = URLRequest(url: url)
let boundary = generateBoundary()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let dataBody = createDataBody(withParameters: nil, media: nil, boundary: boundary)
request.httpBody = dataBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
}
#IBAction func postRequest(_ sender: Any) {
let parameters = ["name": "MyTestFile123321",
"description": "My tutorial test file for MPFD uploads"]
guard let mediaImage = Media(withImage: #imageLiteral(resourceName: "testImage"), forKey: "image") else { return }
guard let url = URL(string: "https://api.imgur.com/3/image") 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")
let dataBody = createDataBody(withParameters: parameters, media: [mediaImage], boundary: boundary)
request.httpBody = dataBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.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
}
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
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 = UIImageJPEGRepresentation(image, 0.7) else { return nil }
self.data = data
}
}
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 = UIImageJPEGRepresentation(image, 0.7) else { return nil }
self.data = data
}
}
func getHeaders(inAuthToken: String = "") -> HTTPHeaders {
var header : HTTPHeaders = [:]
header["Authorization"] = "Bearer \(self.token)"
print(header)
return header
}
func putRequestWithMultipart(url: String, parameters:[String: Any], completion: #escaping(Bool) -> Void) {
let url = "http://3.6.147.149:3000/api/v1/\(url)"
//
for (key, value) in parameters {
if let temp = value as? String {
multipartFormData.append(temp.data(using: .utf8)!, withName: key )
}
if let temp = value as? Int {
multipartFormData.append("\(temp)".data(using: .utf8)!, withName: key )
}
if let temp = value as? NSArray {
temp.forEach({ element in
let keyObj = key + "[]"
if let string = element as? String {
multipartFormData.append(string.data(using: .utf8)!, withName: keyObj)
} else
if let num = element as? Int {
let value = "\(num)"
multipartFormData.append(value.data(using: .utf8)!, withName: keyObj)
}
})
}
if let data = value as? Data {
if key == "image" {
multipartFormData.append(data, withName: "image", fileName: "\(Date.init().timeIntervalSince1970).png", mimeType: "image/png")
}
}
}
},
usingThreshold: UInt64.init(),
to: url,
method: .put,
headers: headers
) { (result) in
switch result {
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print("Upload Progress: \(progress.fractionCompleted)")
})
upload.responseJSON { response in
print(response)
if let data = response.data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] ?? [:]
print(json)
} catch {
print("Something went wrong")
}
completion(true)
}
}
case .failure(let encodingError):
print(encodingError)
completion(false)
}
}
}
}

Server response throwing Unable to process while uploading document/Photo to server in Swift

I am trying to uploading document to server. In Action sheet, User can choose Photo from gallery to Document to upload.
So, I am taking either photo or document and converting it into Data.
After that I am sending that data to server along with two parameters (keys, values) with multipart.
But, Unfortunately I am getting error like Unable to process from server, But same thing working in Android domain.
Here is my code:
func uploadDocument(documentId:Int, data: Data, filePath: String, categoryType: String, completion: #escaping uploadDocumentClosure) {
let url = "https://somedomain.com/uploadDocument"
let requestURL = URL(string: url)!
let request = NSMutableURLRequest(url: requestURL)
request.cachePolicy = .reloadIgnoringLocalCacheData
request.httpShouldHandleCookies = false
request.timeoutInterval = 30
request.httpMethod = "POST"
let filename = "avatar.png"
// generate boundary string using a unique per-app string
let boundary = UUID().uuidString
let docData = data
let idStr = "id"
let docTypeStr = "documentType"
let file = "file"
var data = Data()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer \(accessToken ?? "")", forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(docTypeStr)\"\r\n\r\n".data(using: .utf8)!)
data.append("\(categoryType)".data(using: .utf8)!)
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(file)\"\r\n\r\n".data(using: .utf8)!)
// Add the image data to the raw http request data
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
// data.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
//
if let dataa = "Content-Disposition: form-data; name=\"\(filename)\"; filename=\"image.jpg\"\r\n".data(using: .utf8) {
data.append(dataa)
}
data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
data.append(docData)
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
self.serviceManager.async(request: request as URLRequest) { (innerClosure) in
do {
let response = try innerClosure()
guard let json = response.jsonObject else {
completion({ throw JSONErrorType.parsingError })
return
}
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
let responseModel = try JSONDecoder().decode(EditProfilePhotoUploadResponse.self, from: jsonData)
completion({ return responseModel })
} catch {
completion({ throw error })
}
}
}
And the sever response is below
{
"status" : "E",
"message" : "Unable to process.",
"data" : null,
"messageList" : null
}
With status code 400 bad request.
In Android they simply sending below code and its working fine.
#Multipart
#POST("somedomain.com/uploadDocument")
suspend fun uploadDocument(
#Part file: MultipartBody.Part,
#Query("documentType") documentType: String
): GenericMessageResponse
Any suggestions?
After much debugging, I have fixed the issue with below solution.
I used below library
https://github.com/Fyrts/Multipart.git
Here my code changes are below
func uploadDocument(imageData: Data, filePath: String, fileName: String, categoryType: String, completion: #escaping uploadDocumentClosure) {
let url = "https://wwww.somedomain.com/uploadDocument"
let requestURL = URL(string: url)!
let imageeData = ImageData(name: fileName, filePath: filePath, imagedata: imageData)
let string = imageeData.filePath
let mimeType = string.mimeType() //here list of mimes we can get from predefined
let fileContents = try! Data(contentsOf: URL(string: filePath)!)
var message = Multipart(type: .formData)
message.append(Part.FormData(name: "documentType", value: "\(categoryType)"))
message.append(Part.FormData(name: "file", fileData: fileContents, fileName: "\(imageeData.name)", contentType: "\(mimeType)"))
var URLrequest = URLRequest(url: requestURL)
URLrequest.httpMethod = "POST"
URLrequest.setMultipartBody(message)
URLrequest.addValue("/(token)", forHTTPHeaderField: "Authorization")
self.serviceManager.async(request: URLrequest as URLRequest) { (innerClosure) in
do {
let response = try innerClosure()
guard let json = response.jsonObject else {
completion({ throw JSONErrorType.parsingError })
return
}
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
let responseModel = try JSONDecoder().decode(EditProfilePhotoUploadResponse.self, from: jsonData)
completion({ return responseModel })
} catch {
completion({ throw error })
}
}
}

Image upload with URLSession in Swift

Trying to create a simple script to upload an image with URLSession in Swift. In order to test the code I send a POST request to https://api.imagga.com/v2/uploads. I created an account to https://imagga.com in order to gain the credentials for uploading. The code for URLSession does not work.
It returns {"status":{"text":"No file for upload supplied, use the image or video keys.","type":"error"}}, with Status Code: 400.
The error with status code: 401 is for authorization issues, so I am doing something wrong with the format of the request.
When I use the Alamofire's implementation the image is uploaded successfully.
My credentials for imagga.com are stored as a struct:
import Foundation
struct ImaggaCredentials {
static let username = "myUsername"
static let password = "myPassword"
}
The code for URLSession upload task:
import SwiftUI
class URLSessionObserved: NSObject, ObservableObject {
#Published var uploadedAmount: Float = 0
lazy var urlSession: URLSession = {
let configuration = URLSessionConfiguration.default
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
func upload() {
let urlString = "https://api.imagga.com/v2/uploads"
let url = URL(string: urlString)!
let fileURL = Bundle.main.url(forResource: "image", withExtension: "jpeg")
guard let fileURL = fileURL else {
return
}
let loginString = String(format: "%#:%#", ImaggaCredentials.username, ImaggaCredentials.password)
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()
let boundary = "Boundary-\(UUID().uuidString)"
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
let fileName = fileURL.lastPathComponent
let mimetype = "image/" + fileName.components(separatedBy: ".")[1]
let paramName = "file"
let fileData = try? Data(contentsOf: fileURL)
guard let fileData = fileData else {
return
}
var data = Data()
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
data.append("Content-Type: \(mimetype)\r\n\r\n".data(using: .utf8)!)
data.append(fileData)
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
request.setValue(String(data.count), forHTTPHeaderField: "Content-Length")
let task = urlSession.uploadTask(with: request,
from: data,
completionHandler: { data, response, error in
print(response)
if(error != nil){
print("1--\(error!.localizedDescription)")
}
guard let data = data else {
print("no response data")
return
}
if let responseString = String(data: data, encoding: .utf8) {
print("2--\(responseString)")
}
})
task.resume()
}
}
The code for Alamofire upload task that works fine:
import SwiftUI
import Alamofire
import UIKit
class AlamofireObserved: NSObject, ObservableObject {
#Published var uploadedAmount: Float = 0
func upload() {
print("Button pressed (AlamofireObserved)")
let urlString = "https://api.imagga.com/v2/uploads"
let fileURL = Bundle.main.url(forResource: "image", withExtension: "jpeg")
guard let fileURL = fileURL else {
return
}
let fileName = fileURL.lastPathComponent
let withName = fileName.components(separatedBy: ".")[0]
let mimeType = "image/" + fileName.components(separatedBy: ".")[1]
let fileData = try? Data(contentsOf: fileURL)
guard let fileData = fileData else {
return
}
AF.upload(multipartFormData: { multipartFormData in
multipartFormData.append(fileData, withName: withName, fileName: fileName, mimeType: mimeType)
}, to: urlString)
.authenticate(username: ImaggaCredentials.username, password: ImaggaCredentials.password)
.validate()
.uploadProgress { progress in
print(Float(progress.fractionCompleted))
self.uploadedAmount = Float(progress.fractionCompleted)
}
.responseDecodable(of: UploadImageResponse.self) { response in
switch response.result {
case .failure(let error):
print("Error uploading file: \(error)")
case .success(let uploadResponse):
let resultID = uploadResponse.result.uploadID
print("Content uploaded with ID: \(resultID)")
}
}
}
}
The response type of the request:
import Foundation
struct UploadImageResponse: Codable {
let result: UploadResponseResult
let status: UploadResponseStatus
struct UploadResponseResult: Codable {
let uploadID: String
enum CodingKeys: String, CodingKey {
case uploadID = "upload_id"
}
}
struct UploadResponseStatus: Codable {
let text: String
let type: String
}
}

Upload Pdf, Docx and image file to server using Swift 4

I am new to swift, I have been trying to upload pdf, docx and image file from local storage of a Iphone. I have written a code but it doesn't work and I keep getting Status Code: 415 from the response. Here is my code:
func uploadfileToServer(completed: #escaping () -> ()){
let theTitle = labelTitle.text
guard let url = URL(string: "http://www.--------.com/assignment/post") else {return}
var request = URLRequest.init(url: url)
request.httpMethod = "POST"
request.addValue("cf7ab8c9d4efae82b575eabd6bec76cbb86c6108391e036387f3dd5356a582171519367747000", forHTTPHeaderField: "api_key")
let boundary = generateBoundaryString()
// Set Content-Type in HTTP header.
let boundaryConstant = boundary // This should be auto-generated.
let contentType = "multipart/form-data; boundary=" + boundaryConstant
let directory = NSTemporaryDirectory()
let fileName = NSUUID().uuidString
// This returns a URL? even though it is an NSURL class method
let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])
let fileNamee = fullURL?.path
let mimeType = "text/csv"
let fieldName = "uploadFile"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
var dataString = "--\(boundaryConstant)\r\n"
dataString += "\r\n"
dataString += "--\(boundaryConstant)--\r\n"
var theBody = Data()
let sectionID : String?
sectionID = nil
let str = "user_id=\(savedsesuid!)" + "&school_id=" + SCHOOL_ID + "&class_id=" + classID + "&section_id=\(sectionID)" + "&subject_id=\(id)"
if let b = str.data(using: .utf8) {
theBody.append(b)
}
let str1 = "&atitle=" + theTitle! + "&class_id=" + classID
if let c = str1.data(using: .utf8){
theBody.append(c)
}
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
var filePath = documentDirectory.appendingFormat("/")
filePath = filePath.appendingFormat("/Users/prashanna/Desktop/ebusiness_topic_1_handout.pdf")
let pdfData = NSData(contentsOfFile: filePath)
let file = "&afile=" + "\(pdfData)"
if let d = file.data(using: .utf8){
theBody.append(d)
}
print(theBody)
request.httpBody = theBody
URLSession.shared.dataTask(with: request) { (data, response, error) in
print(response)
if let httpResponse = response as? HTTPURLResponse {
let statuscode = httpResponse.statusCode
if statuscode == 401{
self.displayMessage(userMessage: "Sending Failed")
}else if statuscode == 200{
if error == nil{
do{
self.displayMessage(userMessage: "File Successfully Uploaded!")
DispatchQueue.main.async {
completed()
}
}
}
}
}
}.resume()
}
func generateBoundaryString() -> String {
return "Boundary-\(NSUUID().uuidString)"
}
Some solutions tell me to convert the file into Data and then send it to server while some say to directly add the file path to your body.
Need Help!
One fundamental mistake is that you are using dataTask instead of an uploadTask on your URLSession instance, e.g. uploadTask(with:from:completionHandler:)
Construction of Body Data
Here's a generic example from my own code (as requested in the comments below) of how body data might be constructed:
// imagesURLS is an optional array of URLs, i.e. imageURLS:[URL]?
if let imgURLs = imagesURLS {
for f in imgURLs {
let filename = f.lastPathComponent
let splitName = filename.split(separator: ".")
let name = String(describing: splitName.first)
let filetype = String(describing: splitName.last)
let imgBoundary = "\r\n--\(boundary)\r\nContent-Type: image/\(filetype)\r\nContent-Disposition: form-data; filename=\(filename); name=\(name)\r\n\r\n"
if let d = imgBoundary.data(using: .utf8) {
bodyData.append(d)
}
do {
let imgData = try Data(contentsOf:f, options:[])
bodyData.append(imgData)
}
catch {
// can't load image data
}
}
}
let closingBoundary = "\r\n--\(boundary)--"
if let d = closingBoundary.data(using: .utf8) {
bodyData.append(d)
}
The loop means that every item of data (in this case an image) is preceded by a boundary string and after the very last item of data the closing boundary string is added (i.e. the one ending in a double hyphen).
This works for me, in Swift4:
func uploadFiles(_ urlPath: [URL]){
if let url = URL(string: "YourURL"){
var request = URLRequest(url: url)
let boundary:String = "Boundary-\(UUID().uuidString)"
request.httpMethod = "POST"
request.timeoutInterval = 10
request.allHTTPHeaderFields = ["Content-Type": "multipart/form-data; boundary=----\(boundary)"]
for path in urlPath{
do{
var data2: Data = Data()
var data: Data = Data()
data2 = try NSData.init(contentsOf: URL.init(fileURLWithPath: path.absoluteString, isDirectory: true)) as Data
/* Use this if you have to send a JSON too.
let dic:[String:Any] = [
"Key":Value,
"Key":Value
]
for (key,value) in dic{
data.append("------\(boundary)\r\n")
data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
data.append("\(value)\r\n")
}
*/
data.append("------\(boundary)\r\n")
//Here you have to change the Content-Type
data.append("Content-Disposition: form-data; name=\"file\"; filename=\"YourFileName\"\r\n")
data.append("Content-Type: application/YourType\r\n\r\n")
data.append(data2)
data.append("\r\n")
data.append("------\(boundary)--")
request.httpBody = data
}catch let e{
//Your errors
}
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).sync {
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { (dataS, aResponse, error) in
if let erros = error{
//Your errors
}else{
do{
let responseObj = try JSONSerialization.jsonObject(with: dataS!, options: JSONSerialization.ReadingOptions(rawValue:0)) as! [String:Any]
}catch let e{
}
}
}).resume()
}
}
}
}
extension Data{
mutating func append(_ string: String, using encoding: String.Encoding = .utf8) {
if let data = string.data(using: encoding) {
append(data)
}
}
}
Sample Code for uploading image is:
func uploadImage(){
var imageToUpload:UIImage = UIImage()
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
if let dirPath = paths.first
{
let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent("Image2.png") //Your image name here
let image = UIImage(contentsOfFile: imageURL.path)
imageToUpload = image!
}
Alamofire.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(UIImageJPEGRepresentation(imageToUpload, 1)!, withName: "Prescription", fileName: "Profile_Image.jpeg", mimeType: "image/jpeg")
}, to:"you_URL_here")
{ (result) in
switch result {
case .success(let upload, _, _):
print(result)
upload.uploadProgress(closure: { (progress) in
print(progress)
})
upload.responseJSON { response in
//print response.result
print(response);
}
case .failure(let encodingError):
print(encodingError);
}
}
}

Upload image with multipart form-data iOS in Swift

i am having a problem with uploading image with multipart-form
here is my code i used from this answer
var request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
var boundary = generateBoundaryString()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = NSMutableData()
if self.img.image != nil {
var imageData = UIImagePNGRepresentation(self.img.image)
if imageData != nil {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"image.png\"\r\n")
body.appendString("Content-Type: image/png\r\n\r\n")
body.appendData(imageData!)
body.appendString("\r\n")
}
}
body.appendString("--\(boundary)--\r\n")
request.setValue("\(body.length)", forHTTPHeaderField:"Content-Length")
request.HTTPBody = body
then i use NSURLSession to apply the request
the server says that i didn't choose image to upload i only want to upload the image for now
do i have to use paths of images to upload any image or it's data is enough?
do i miss any thing , any help to understand this ?
No Need to use any library for upload images using multipart request.
Swift 4.2
func uploadImage(paramName: String, fileName: String, image: UIImage) {
let url = URL(string: "http://api-host-name/v1/api/uploadfile/single")
// generate boundary string using a unique per-app string
let boundary = UUID().uuidString
let session = URLSession.shared
// Set the URLRequest to POST and to the specified URL
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = "POST"
// Set Content-Type Header to multipart/form-data, this is equivalent to submitting form data with file upload in a web browser
// And the boundary is also set here
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var data = Data()
// Add the image data to the raw http request data
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
data.append(image.pngData()!)
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
// Send a POST request to the URL, with the data we created earlier
session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
if error == nil {
let jsonData = try? JSONSerialization.jsonObject(with: responseData!, options: .allowFragments)
if let json = jsonData as? [String: Any] {
print(json)
}
}
}).resume()
}
If you have any header to add, you can add it via urlRequest.setValue method.
Source: https://fluffy.es/upload-image-to-server/
My version that 100% works. Maybe it will help you.
let url = "http://server/upload"
let img = UIImage(contentsOfFile: fullPath)
let data: NSData = UIImageJPEGRepresentation(img, 1)
sendFile(url,
fileName:"one.jpg",
data:data,
completionHandler: completionHandler:{
(result:Bool, isNoInternetConnection:Bool) -> Void in
// ...
NSLog("Complete: \(result)")
}
)
func sendFile(
urlPath:String,
fileName:String,
data:NSData,
completionHandler: (NSURLResponse!, NSData!, NSError!) -> Void){
var url: NSURL = NSURL(string: urlPath)!
var request1: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request1.HTTPMethod = "POST"
let boundary = generateBoundary()
let fullData = photoDataToFormData(data,boundary:boundary,fileName:fileName)
request1.setValue("multipart/form-data; boundary=" + boundary,
forHTTPHeaderField: "Content-Type")
// REQUIRED!
request1.setValue(String(fullData.length), forHTTPHeaderField: "Content-Length")
request1.HTTPBody = fullData
request1.HTTPShouldHandleCookies = false
let queue:NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
request1,
queue: queue,
completionHandler:completionHandler)
}
// this is a very verbose version of that function
// you can shorten it, but i left it as-is for clarity
// and as an example
func photoDataToFormData(data:NSData,boundary:String,fileName:String) -> NSData {
var fullData = NSMutableData()
// 1 - Boundary should start with --
let lineOne = "--" + boundary + "\r\n"
fullData.appendData(lineOne.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 2
let lineTwo = "Content-Disposition: form-data; name=\"image\"; filename=\"" + fileName + "\"\r\n"
NSLog(lineTwo)
fullData.appendData(lineTwo.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 3
let lineThree = "Content-Type: image/jpeg\r\n\r\n"
fullData.appendData(lineThree.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 4
fullData.appendData(data)
// 5
let lineFive = "\r\n"
fullData.appendData(lineFive.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
// 6 - The end. Notice -- at the start and at the end
let lineSix = "--" + boundary + "--\r\n"
fullData.appendData(lineSix.dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false)!)
return fullData
}
import Foundation
struct MultipartFormDataRequest {
private let boundary: String = UUID().uuidString
var httpBody = NSMutableData()
let url: URL
init(url: URL) {
self.url = url
}
func addTextField(named name: String, value: String) {
httpBody.appendString(textFormField(named: name, value: value))
}
private func textFormField(named name: String, value: String) -> String {
var fieldString = "--\(boundary)\r\n"
fieldString += "Content-Disposition: form-data; name=\"\(name)\"\r\n"
fieldString += "Content-Type: text/plain; charset=ISO-8859-1\r\n"
fieldString += "Content-Transfer-Encoding: 8bit\r\n"
fieldString += "\r\n"
fieldString += "\(value)\r\n"
return fieldString
}
func addDataField(fieldName: String, fileName: String, data: Data, mimeType: String) {
httpBody.append(dataFormField(fieldName: fieldName,fileName:fileName,data: data, mimeType: mimeType))
}
private func dataFormField(fieldName: String,
fileName: String,
data: Data,
mimeType: String) -> Data {
let fieldData = NSMutableData()
fieldData.appendString("--\(boundary)\r\n")
fieldData.appendString("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\r\n")
fieldData.appendString("Content-Type: \(mimeType)\r\n")
fieldData.appendString("\r\n")
fieldData.append(data)
fieldData.appendString("\r\n")
return fieldData as Data
}
func asURLRequest() -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
httpBody.appendString("--\(boundary)--")
request.httpBody = httpBody as Data
return request
}
}
extension NSMutableData {
func appendString(_ string: String) {
if let data = string.data(using: .utf8) {
self.append(data)
}
}
}
extension URLSession {
func dataTask(with request: MultipartFormDataRequest,
completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void)
-> URLSessionDataTask {
return dataTask(with: request.asURLRequest(), completionHandler: completionHandler)
}
}
Use this function to call upload file func
func uploadFile(file:Data, fileName: String, fileExtension: String){
var mimeType = "image/png"
if fileExtension == "PDF" {
mimeType = "application/pdf"
}
let url = "https://v2.convertapi.com/upload"
let request = MultipartFormDataRequest(url: URL(string: url)!)
request.addDataField(fieldName: "file", fileName: fileName, data: file, mimeType: mimeType)
URLSession.shared.dataTask(with: request, completionHandler: {data,urlResponse,error in
}).resume()
}
#Resources:
https://www.donnywals.com/uploading-images-and-forms-to-a-server-using-urlsession/
https://orjpap.github.io/swift/http/ios/urlsession/2021/04/26/Multipart-Form-Requests.html
public func UPLOADIMG(url: String,parameters: Dictionary<String,AnyObject>?,filename:String,image:UIImage, success:((NSDictionary) -> Void)!, failed:((NSDictionary) -> Void)!, errord:((NSError) -> Void)!) {
var TWITTERFON_FORM_BOUNDARY:String = "AaB03x"
let url = NSURL(string: url)!
var request:NSMutableURLRequest = NSMutableURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 10)
var MPboundary:String = "--\(TWITTERFON_FORM_BOUNDARY)"
var endMPboundary:String = "\(MPboundary)--"
//convert UIImage to NSData
var data:NSData = UIImagePNGRepresentation(image)
var body:NSMutableString = NSMutableString();
// with other params
if parameters != nil {
for (key, value) in parameters! {
body.appendFormat("\(MPboundary)\r\n")
body.appendFormat("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendFormat("\(value)\r\n")
}
}
// set upload image, name is the key of image
body.appendFormat("%#\r\n",MPboundary)
body.appendFormat("Content-Disposition: form-data; name=\"\(filename)\"; filename=\"pen111.png\"\r\n")
body.appendFormat("Content-Type: image/png\r\n\r\n")
var end:String = "\r\n\(endMPboundary)"
var myRequestData:NSMutableData = NSMutableData();
myRequestData.appendData(body.dataUsingEncoding(NSUTF8StringEncoding)!)
myRequestData.appendData(data)
myRequestData.appendData(end.dataUsingEncoding(NSUTF8StringEncoding)!)
var content:String = "multipart/form-data; boundary=\(TWITTERFON_FORM_BOUNDARY)"
request.setValue(content, forHTTPHeaderField: "Content-Type")
request.setValue("\(myRequestData.length)", forHTTPHeaderField: "Content-Length")
request.HTTPBody = myRequestData
request.HTTPMethod = "POST"
// var conn:NSURLConnection = NSURLConnection(request: request, delegate: self)!
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {
data, response, error in
if error != nil {
println(error)
errord(error)
return
}
var parseError: NSError?
let responseObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parseError)
if let responseDictionary = responseObject as? NSDictionary {
success(responseDictionary)
} else {
}
})
task.resume()
}
I suggest this repository.
You can add pod 'MultipartForm' in the Podfile and then follow the example in the repository's readme:
import MultipartForm
let form = MultipartForm(parts: [
MultipartForm.Part(name: "a", value: "1"),
MultipartForm.Part(name: "b", value: "2"),
MultipartForm.Part(name: "c", data: imageData, filename: "3.png", contentType: "image/png"),
])
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(form.contentType, forHTTPHeaderField: "Content-Type")
let task = session.uploadTask(with: request, from: form.bodyData)
task.resume()
It supports Swift Package Manager too.
extension URLRequest {
mutating func setMultipartFormDataBody(params: [String: (Data, filename: String?)]) {
let boundary = UUID().uuidString
self.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
for (key, (data, filename)) in params {
body.append("--\(boundary)\r\n")
if let filename = filename {
body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(filename)\"\r\n")
}
else {
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n")
}
body.append("\r\n")
body.append(data)
body.append("\r\n")
}
body.append("--\(boundary)--")
self.httpBody = body
}
}
extension Data {
mutating func append(_ s: String) {
self.append(s.data(using: .utf8)!)
}
}
The first thing I noticed is the application/octet-stream as Conten-Type, this is usually used when the file type is unknown. Some web frameworks/libraries will reject this content-type if an image is required.
Second, I can't see the post length anywhere, try to add it:
body.appendString("--\(boundary)--\r\n")
// set the content-length
request.setValue("\(body.length)", forHTTPHeaderField:"Content-Length")
class func postMultiPartdata( postdatadictionary: [AnyHashable: Any], apikey: String, completion: #escaping (Any) -> () ) {
if Utils().isConnectedToNetwork() == false
{
Utils().showMessage("Check internet")
return
}
let strURL = "http://redspark.biz/dropp/api/\(apikey)"
let url = URL(string: strURL)
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = "POST"
let body = NSMutableData();
let boundary = "---------------------------14737809831466499882746641449"
let contentType = "multipart/form-data; boundary=\(boundary)"
urlRequest.addValue(contentType, forHTTPHeaderField: "Content-Type")
for (key, value) in postdatadictionary {
if(value is Data)
{
let TimeStamp = "\(Date().timeIntervalSince1970 * 1000)"
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(TimeStamp)\"\r\n".data(using:.utf8)!)
body.append("field_mobileinfo_image\r\n".data(using: .utf8)!)
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"files[field_mobileinfo_image]\"; filename=\"img.jpg\"\r\n".data(using: .utf8)!)
body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
// var imgData: Data? = nil
// if let aKey = value as? Data {
// imgData = NSData(data: aKey) as Data
// }
body.append(value as! Data)
}
else
{
if let anEncoding = "--\(boundary)\r\n".data(using: .utf8) {
body.append(anEncoding)
}
if let anEncoding = "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8) {
body.append(anEncoding)
}
if let aKey = postdatadictionary[key], let anEncoding = "\(aKey)".data(using: .utf8) {
body.append(anEncoding)
}
if let anEncoding = "\r\n".data(using: .utf8) {
body.append(anEncoding)
}
}
}
if let anEncoding = "--\(boundary)--\r\n".data(using: .utf8) {
body.append(anEncoding)
}
// setting the body of the post to the reqeust
urlRequest.httpBody = body as Data
URLSession.shared.dataTask(with:urlRequest) { (data, response, error) in
if error != nil {
print(error!)
completion("")
} else {
var dictonary:NSDictionary?
do {
dictonary = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
if let myDictionary = dictonary
{
completion(myDictionary)
}
} catch let error as NSError {
completion(error)
}
}
Utils().HideLoader()
}.resume()
}
following those answers here is my implementation with a concrete example for video and audio. notice that the boundary between elements has such form --boundary while the last boundary is written --boudary--.
let url = URL(string: "https://...")!
let boundary = UUID().uuidString
var request = URLRequest(url: url)
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpMethod = "post"
var data = Data()
data.addMultiPart(boundary: boundary, name: "metadata", filename: "metadata.json", contentType: "application/json", data: metaData)
let ext = fileUrl.pathExtension.lowercased()
let isImage = ["jpg","jpeg","png"].contains(ext)
let contentType = isImage ? "image/\(ext)" : "video/\(ext)"
let mediaData = try! Data(contentsOf: fileUrl)
data.addMultiPart(boundary: boundary, name: "file", filename: fileUrl.lastPathComponent, contentType: contentType, data: mediaData)
data.addMultiPartEnd(boundary: boundary)
request.httpBody = data
let task = session.dataTask(with: request)
task.resume()
private extension Data {
mutating func addMultiPart(boundary: String, name: String, filename: String, contentType: String, data: Data) {
print("adding boundary: \(boundary), name: \(name), filename: \(filename), contentType: \(contentType) data length: \(data.count) ")
self.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
self.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
self.append("Content-Type: \(contentType)\r\n\r\n".data(using: .utf8)!)
self.append(data)
}
mutating func addMultiPartEnd(boundary: String) {
self.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
}
}
I implemented Upload image using Multi-part in Swift 4:
Here is the code. Please have a look
//MARK: Uplaod User Profile Pic
func uploadImageToServerFromApp(nameOfApi : NSString, parameters : NSString, uploadedImage : UIImage, withCurrentTask :RequestType, andDelegate :AnyObject)->Void {
if self.isConnectedToNetwork(){
currentTask = withCurrentTask
let myRequestUrl = NSString(format: "%#%#%#",GlobalConstants.KBaseURL,nameOfApi,parameters)
let url = (myRequestUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed))!
var request : NSMutableURLRequest = NSMutableURLRequest()
request = URLRequest(url: URL(string:url as String)!) as! NSMutableURLRequest
request.httpMethod = "POST"
let boundary = generateBoundaryString()
//define the multipart request type
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let image_data = UIImagePNGRepresentation(uploadedImage)
if(image_data == nil){
return
}
let body = NSMutableData()
let fname = "image.png"
let mimetype = "image/png"
//define the data post parameter
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"image\"\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append("hi\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"image\"; filename=\"\(fname)\"\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Type: \(mimetype)\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append(image_data!)
body.append("\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)--\r\n".data(using: String.Encoding.utf8)!)
request.httpBody = body as Data
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data = data, error == nil else { // check for fundamental networking error
// print("error=\(String(describing: error))")
self.showAlertMessage(title: "App name", message: "Server not responding, please try later")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
// print("statusCode should be 200, but is \(httpStatus.statusCode)")
// print("response = \(String(describing: response))")
self.delegate?.internetConnectionFailedIssue()
}else{
do {
self.responseDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! NSDictionary
// self.Responsedata = data as NSData
//self.responseDictionary = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: AnyObject] as NSDictionary;
self.delegate?.responseReceived()
} catch {
//print("error serializing JSON: \(error)")
}
}
}
task.resume()
}
else{
// print("Internet Connection not Available!")
self.showAlertMessage(title: "App Name", message: "No Internet Connection..")
}
}
func generateBoundaryString() -> String
{
return "Boundary-\(NSUUID().uuidString)"
}
Very good video and code:
https://www.youtube.com/watch?v=8GH0yMPvQFU
https://github.com/Kilo-Loco/URLSessionMPFD/blob/master/URLSessionMPFD/ViewController.swift
import UIKit
typealias Parameters = [String: String]
class ViewController: UIViewController {
#IBAction func getRequest(_ sender: Any) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
var request = URLRequest(url: url)
let boundary = generateBoundary()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let dataBody = createDataBody(withParameters: nil, media: nil, boundary: boundary)
request.httpBody = dataBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
}
#IBAction func postRequest(_ sender: Any) {
let parameters = ["name": "MyTestFile123321",
"description": "My tutorial test file for MPFD uploads"]
guard let mediaImage = Media(withImage: #imageLiteral(resourceName: "testImage"), forKey: "image") else { return }
guard let url = URL(string: "https://api.imgur.com/3/image") 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")
let dataBody = createDataBody(withParameters: parameters, media: [mediaImage], boundary: boundary)
request.httpBody = dataBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.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
}
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
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 = UIImageJPEGRepresentation(image, 0.7) else { return nil }
self.data = data
}
}
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 = UIImageJPEGRepresentation(image, 0.7) else { return nil }
self.data = data
}
}
func getHeaders(inAuthToken: String = "") -> HTTPHeaders {
var header : HTTPHeaders = [:]
header["Authorization"] = "Bearer \(self.token)"
print(header)
return header
}
func putRequestWithMultipart(url: String, parameters:[String: Any], completion: #escaping(Bool) -> Void) {
let url = "http://3.6.147.149:3000/api/v1/\(url)"
//
for (key, value) in parameters {
if let temp = value as? String {
multipartFormData.append(temp.data(using: .utf8)!, withName: key )
}
if let temp = value as? Int {
multipartFormData.append("\(temp)".data(using: .utf8)!, withName: key )
}
if let temp = value as? NSArray {
temp.forEach({ element in
let keyObj = key + "[]"
if let string = element as? String {
multipartFormData.append(string.data(using: .utf8)!, withName: keyObj)
} else
if let num = element as? Int {
let value = "\(num)"
multipartFormData.append(value.data(using: .utf8)!, withName: keyObj)
}
})
}
if let data = value as? Data {
if key == "image" {
multipartFormData.append(data, withName: "image", fileName: "\(Date.init().timeIntervalSince1970).png", mimeType: "image/png")
}
}
}
},
usingThreshold: UInt64.init(),
to: url,
method: .put,
headers: headers
) { (result) in
switch result {
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print("Upload Progress: \(progress.fractionCompleted)")
})
upload.responseJSON { response in
print(response)
if let data = response.data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] ?? [:]
print(json)
} catch {
print("Something went wrong")
}
completion(true)
}
}
case .failure(let encodingError):
print(encodingError)
completion(false)
}
}
}
}

Resources