Upload image with multipart form-data only in Swift 4.2
Please check my solution which is taken parameters and images.I have tested with PHP, .net, java.
class func request(withImages path:APIMethods, method:URLMethod, token : String?, headers:[String:String]?, parameters: [String:Any]?,imageNames : [String], images:[Data], completion: @escaping(Any?, Error?, Bool)->Void) {
if !Reachability.isConnectedToNetwork() {
DispatchQueue.main.async {
showAlert(message: AppMessages.connectionFailed.rawValue)
}
dissmissHud()
return
}
var stringUrl = "\(APIManager.url)\(APIMethods.preFix.rawValue)\(path.rawValue)"
if method == .get, let lastPath = parameters?.values.first as? String {
stringUrl += lastPath
}else{
stringUrl += token ?? ""
}
// generate boundary string using a unique per-app string
let boundary = UUID().uuidString
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
print("\n\ncomplete Url :-------------- ",stringUrl," \n\n-------------: complete Url")
guard let url = URL(string: stringUrl) else { return }
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
if headers != nil{
print("\n\nHeaders :-------------- ",headers as Any,"\n\n --------------: Headers")
for (key, value) in headers! {
request.setValue(value, forHTTPHeaderField: key)
}
}
// 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
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var data = Data()
if parameters != nil{
for(key, value) in parameters!{
// Add the reqtype field and its value to the raw http request data
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
data.append("\(value)".data(using: .utf8)!)
}
}
for (index,imageData) in images.enumerated() {
// 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=\"\(imageNames[index])\"; filename=\"\(imageNames[index])\"\r\n".data(using: .utf8)!)
data.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
data.append(imageData)
}
// End the raw http request data, note that there is 2 extra dash ("-") at the end, this is to indicate the end of the data
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: request, from: data, completionHandler: { data, response, error in
if let checkResponse = response as? HTTPURLResponse{
if checkResponse.statusCode == 200{
guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.allowFragments]) else {
completion(nil, error, false)
return
}
let jsonString = String(data: data, encoding: .utf8)!
print("\n\n---------------------------\n\n"+jsonString+"\n\n---------------------------\n\n")
print(json)
completion(json, nil, true)
}else{
guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
completion(nil, error, false)
return
}
let jsonString = String(data: data, encoding: .utf8)!
print("\n\n---------------------------\n\n"+jsonString+"\n\n---------------------------\n\n")
print(json)
completion(json, nil, false)
}
}else{
guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
completion(nil, error, false)
return
}
completion(json, nil, false)
}
}).resume()
}
extension Data {
/// Append string to Data
///
/// Rather than littering my code with calls to `data(using: .utf8)` to convert `String` values to `Data`, this wraps it in a nice convenient little extension to Data. This defaults to converting using UTF-8.
///
/// - parameter string: The string to be added to the `Data`.
mutating func append(_ string: String, using encoding: String.Encoding = .utf8) {
if let data = string.data(using: encoding) {
append(data)
}
}
}
How to upload image file using Codable and URLSession.shared.uploadTask (multipart/form-data) in Swift?
Finally I was able to find the solution.
The source is: URLSession: Multipart Form-Data Requests | Swift 3, Xcode 8.
In my specific case I need to provide orderExtId as a parameter for the backend server to accept my image. Your case may vary, depending on the requirements of the backend.
func requestNativeImageUpload(image: UIImage, orderExtId: String) {
guard let url = imageUploadEndpoint else { return }
let boundary = generateBoundary()
var request = URLRequest(url: url)
let parameters = ["order_ext_id": orderExtId]
guard let mediaImage = Media(withImage: image, forKey: "file") else { return }
request.httpMethod = "POST"
request.allHTTPHeaderFields = [
"X-User-Agent": "ios",
"Accept-Language": "en",
"Accept": "application/json",
"Content-Type": "multipart/form-data; boundary=\(boundary)",
"ApiKey": KeychainService.getString(by: KeychainKey.apiKey) ?? ""
]
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: [String: String]?, 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/jpg"
self.fileName = "\(arc4random()).jpeg"
guard let data = image.jpegData(compressionQuality: 0.5) else { return nil }
self.data = data
}
}
Related Topics
Converting iPhone Xib to iPad Xib
Instruments Allocations Track Alloc and Dealloc of Objects of User Defined Classes
How to Create Layout Constraints Programmatically
How to Implement the Uitapgesturerecognizer into My Application
Objective C: Downloading File with Progress Bar
Restrict to Certain iOS Target Devices for App Store Submission
Scrollview Gesture Recognizer Eating All Touch Events
++ Is Deprecated It Will Be Removed in Swift 3
Uidatepicker Select Month and Year
Programmatically Creating an Expanding Uitableviewcell
What Is "Self" Used for in Swift
How to Delete Wkwebview Cookies
Ios9 Does Not Load Insecure Resources from a Secure Page (Ssl/Https)
How to Use Uiscrollview in Storyboard
How to Scroll to the Bottom of a Uitableview on the iPhone Before the View Appears