I'm trying to upload an image with parameters in Swift. When I try this code, I can get the parameters but not the image
uploadFileToUrl(fotiño:UIImage){
var foto = UIImage(data: UIImageJPEGRepresentation(fotiño, 0.2))
var request = NSMutableURLRequest(URL:NSURL(string: "URL"))
request.HTTPMethod = "POST"
var bodyData = "id_user="PARAMETERS&ETC""
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
request.HTTPBody = NSData.dataWithData(UIImagePNGRepresentation(foto))
println("miraqui \(request.debugDescription)")
var response: AutoreleasingUnsafeMutablePointer<NSURLResponse?>=nil
var HTTPError: NSError? = nil
var JSONError: NSError? = nil
var dataVal: NSData? = NSURLConnection.sendSynchronousRequest(request, returningResponse: response, error: &HTTPError)
if ((dataVal != nil) && (HTTPError == nil)) {
var jsonResult = NSJSONSerialization.JSONObjectWithData(dataVal!, options: NSJSONReadingOptions.MutableContainers, error: &JSONError)
if (JSONError != nil) {
println("Bad JSON")
} else {
println("Synchronous\(jsonResult)")
}
} else if (HTTPError != nil) {
println("Request failed")
} else {
println("No Data returned")
}
}
edit 2:
I think that I have some problems with the path of the saved UIImage, because php tells me that the file already exist, which I think is because I send it in blank
func createRequest (#userid: String, disco: String, id_disco: String, pub: String, foto: UIImage) -> NSURLRequest {
let param = [
"id_user" : userid,
"name_discoteca" : disco,
"id_discoteca" : id_disco,
"ispublic" : pub] // build your dictionary however appropriate
let boundary = generateBoundaryString()
let url = NSURL(string: "http....")
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.timeoutInterval = 60
request.HTTPShouldHandleCookies = false
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var imagesaver = ImageSaver()
var image = foto // However you create/get a UIImage
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let destinationPath = documentsPath.stringByAppendingPathComponent("VipKing.jpg")
UIImageJPEGRepresentation(image,1.0).writeToFile(destinationPath, atomically: true)
self.saveImage(foto, withFileName: "asdasd22.jpg")
var path = self.documentsPathForFileName("asdasd22.jpg")
self.ViewImage.image = self.loadImageWithFileName("asdasd22.jpg")
// let path1 = NSBundle.mainBundle().pathForResource("asdasd22", ofType: "jpg", inDirectory: path) as String!
**//path1 always crash**
println(param.debugDescription)
println(path.debugDescription)
println(boundary.debugDescription)
request.HTTPBody = createBodyWithParameters(param, filePathKey: "asdasd22.jpg", paths: [path], boundary: boundary)
println(request.debugDescription)
return request
}
In your comment below, you inform us that you are using the $_FILES
syntax to retrieve the files. That means that you want to create a multipart/form-data
request. The process is basically:
Specify a boundary for your multipart/form-data
request.
Specify a Content-Type
of the request that specifies that it multipart/form-data
and what the boundary is.
Create body of request, separating the individual components (each of the posted values as well as between each upload).
For more detail, see RFC 7578. Anyway, in Swift 3 and later, this might look like:
/// Create `multipart/form-data` request with specifics values.
///
/// - Parameters:
/// - userid: The userid to be passed to web service
/// - password: The password to be passed to web service
/// - email: The email address to be passed to web service
/// - imageUrl: The URL of file resource for image to be uploaded
///
/// - returns: The `URLRequest` and its associated `Data`
func requestAndData(userid: String, password: String, email: String, urls: [URL]) throws -> (URLRequest, Data) {
let parameters = [
"user_id" : userid,
"email" : email,
"password" : password] // build your dictionary however appropriate
return try requestAndData(parameters: parameters, fileKeyPath: "file", urls: urls)
}
/// General purpose routine to create `multipart/form-data` request
///
/// - Parameters:
/// - parameters: Dictionary of parameters to be added to body.
/// - headers: Dictionary of headers to be added to request.
/// - filePathKey: The optional field name to be used when uploading files. If you supply paths, you must supply filePathKey, too.
/// - urls: An array of fileURLs to be added to body.
///
/// - returns: The `URLRequest` and its associated `Data`
func requestAndData(
parameters: [String: String]? = nil,
headers: [String: String]? = nil,
fileKeyPath: String,
urls: [URL]
) throws -> (URLRequest, Data) {
let boundary = generateBoundaryString()
let url = URL(string: "https://example.com/imageupload.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
parameters?.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
let data = try createBody(with: parameters, filePathKey: fileKeyPath, urls: urls, boundary: boundary)
return (request, data)
}
/// Create body of the `multipart/form-data` request
///
/// - Parameters:
/// - parameters: The optional dictionary containing keys and values to be passed to web service.
/// - filePathKey: The optional field name to be used when uploading files. If you supply paths, you must supply filePathKey, too.
/// - fileKeyPath: The key that the server is expecting for files in body of request.
/// - urls: The optional array of file URLs of the files to be uploaded.
/// - boundary: The `multipart/form-data` boundary.
///
/// - returns: The `Data` of the body of the request.
private func createBody(
with parameters: [String: String]? = nil,
filePathKey: String,
urls: [URL],
boundary: String
) throws -> Data {
var body = Data()
parameters?.forEach { (key, value) in
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
}
for url in urls {
let filename = url.lastPathComponent
let data = try Data(contentsOf: url)
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(filePathKey)\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(url.mimeType)\r\n\r\n")
body.append(data)
body.append("\r\n")
}
body.append("--\(boundary)--\r\n")
return body
}
/// Create boundary string for multipart/form-data request
///
/// - returns: The boundary string that consists of "Boundary-" followed by a UUID string.
private func generateBoundaryString() -> String {
return "Boundary-\(UUID().uuidString)"
}
With:
import UniformTypeIdentifiers // for iOS 14+
import MobileCoreServices // for pre-iOS 14
extension URL {
/// Mime type for the URL
var mimeType: String {
if #available(iOS 14.0, *) {
return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream"
} else {
guard
let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let mimeType = UTTypeCopyPreferredTagWithClass(identifier, kUTTagClassMIMEType)?.takeRetainedValue() as String?
else {
return "application/octet-stream"
}
return mimeType
}
}
}
extension Data {
/// Append string to Data
///
/// Append string using `.utf8` format.
///
/// - parameter string: The string to be added to the `Data`.
mutating func append(_ string: String) {
append(Data(string.utf8))
}
}
Having all of this, you now need to submit this request. I would advise this is done asynchronously. For example, using URLSession
, you would do something like:
func upload(userId: String, password: String, email: String, imageUrl: URL) async throws {
let (request, payload) = try requestAndData(userid: userid, password: password, email: email, imageUrl: imageUrl)
let (data, response) = try await URLSession.shared.upload(for: request, from: payload)
guard let httpResponse = response as? HTTPURLResponse else {
// throw error for non-HTTP response
}
guard 200..<300 ~= httpResponse.statusCode else {
// throw error if server reported anything other than success
}
// ok, server returned 2xx response, so now parse `data` here
}
If you are uploading large assets (e.g. videos or the like), you might want to use a file-based permutation of the above. See https://stackoverflow.com/a/70552269/1271826.
For completion-handler-based rendition (i.e., for codebases that have not adopted Swift concurrency) see previous revision of this answer.