Subir imagen con parámetros en Swift

Estoy intentando subir una imagen con parámetros en Swift. Cuando pruebo este código, puedo obtener los parámetros pero no la imagen

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=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") } } 

editar 2:

Creo que tengo algunos problemas con la ruta del UIImage guardado, porque php me dice que el archivo ya existe, y creo que es porque lo envío en blanco

 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 } 

En su comentario a continuación, nos informa que está utilizando la syntax $_FILES para recuperar los archivos. Eso significa que desea crear una solicitud multipart/form-data . El proceso es básicamente:

  1. Especifique un límite para su solicitud multipart/form-data .

  2. Especifique un Content-Type de Content-Type de la solicitud que especifique que es multipart/form-data y cuál es el límite.

  3. Cree un cuerpo de solicitud, separando los componentes individuales (cada uno de los valores publicados, así como también entre cada carga).

Para más detalles, vea RFC 2388 . De todos modos, en Swift 3, esto podría verse así:

 /// Create request /// /// - parameter userid: The userid to be passed to web service /// - parameter password: The password to be passed to web service /// - parameter email: The email address to be passed to web service /// /// - returns: The `URLRequest` that was created func createRequest(userid: String, password: String, email: String) throws -> URLRequest { let parameters = [ "user_id" : userid, "email" : email, "password" : password] // build your dictionary however appropriate 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") let path1 = Bundle.main.path(forResource: "image1", ofType: "png")! request.httpBody = try createBody(with: parameters, filePathKey: "file", paths: [path1], boundary: boundary) return request } /// Create body of the `multipart/form-data` request /// /// - parameter parameters: The optional dictionary containing keys and values to be passed to web service /// - parameter filePathKey: The optional field name to be used when uploading files. If you supply paths, you must supply filePathKey, too. /// - parameter paths: The optional array of file paths of the files to be uploaded /// - parameter boundary: The `multipart/form-data` boundary /// /// - returns: The `Data` of the body of the request private func createBody(with parameters: [String: String]?, filePathKey: String, paths: [String], boundary: String) throws -> Data { var body = Data() if parameters != nil { for (key, value) in parameters! { body.append("--\(boundary)\r\n") body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n") body.append("\(value)\r\n") } } for path in paths { let url = URL(fileURLWithPath: path) let filename = url.lastPathComponent let data = try Data(contentsOf: url) let mimetype = mimeType(for: path) body.append("--\(boundary)\r\n") body.append("Content-Disposition: form-data; name=\"\(filePathKey)\"; filename=\"\(filename)\"\r\n") body.append("Content-Type: \(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)" } /// Determine mime type on the basis of extension of a file. /// /// This requires `import MobileCoreServices`. /// /// - parameter path: The path of the file for which we are going to determine the mime type. /// /// - returns: Returns the mime type if successful. Returns `application/octet-stream` if unable to determine mime type. private func mimeType(for path: String) -> String { let url = URL(fileURLWithPath: path) let pathExtension = url.pathExtension if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as NSString, nil)?.takeRetainedValue() { if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() { return mimetype as String } } return "application/octet-stream" } 

Con:

 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) } } } 

O, en Swift 2:

 /// Create request /// /// - parameter userid: The userid to be passed to web service /// - parameter password: The password to be passed to web service /// - parameter email: The email address to be passed to web service /// /// - returns: The NSURLRequest that was created func createRequest (userid userid: String, password: String, email: String) -> NSURLRequest { let param = [ "user_id" : userid, "email" : email, "password" : password] // build your dictionary however appropriate let boundary = generateBoundaryString() let url = NSURL(string: "https://example.com/imageupload.php")! let request = NSMutableURLRequest(URL: url) request.HTTPMethod = "POST" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") let path1 = NSBundle.mainBundle().pathForResource("image1", ofType: "png") as String! request.HTTPBody = createBodyWithParameters(param, filePathKey: "file", paths: [path1], boundary: boundary) return request } /// Create body of the multipart/form-data request /// /// - parameter parameters: The optional dictionary containing keys and values to be passed to web service /// - parameter filePathKey: The optional field name to be used when uploading files. If you supply paths, you must supply filePathKey, too. /// - parameter paths: The optional array of file paths of the files to be uploaded /// - parameter boundary: The multipart/form-data boundary /// /// - returns: The NSData of the body of the request func createBodyWithParameters(parameters: [String: String]?, filePathKey: String?, paths: [String]?, boundary: String) -> NSData { let body = NSMutableData() if parameters != nil { for (key, value) in parameters! { body.appendString("--\(boundary)\r\n") body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n") body.appendString("\(value)\r\n") } } if paths != nil { for path in paths! { let url = NSURL(fileURLWithPath: path) let filename = url.lastPathComponent let data = NSData(contentsOfURL: url)! let mimetype = mimeTypeForPath(path) body.appendString("--\(boundary)\r\n") body.appendString("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename!)\"\r\n") body.appendString("Content-Type: \(mimetype)\r\n\r\n") body.appendData(data) body.appendString("\r\n") } } body.appendString("--\(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. func generateBoundaryString() -> String { return "Boundary-\(NSUUID().UUIDString)" } /// Determine mime type on the basis of extension of a file. /// /// This requires MobileCoreServices framework. /// /// - parameter path: The path of the file for which we are going to determine the mime type. /// /// - returns: Returns the mime type if successful. Returns application/octet-stream if unable to determine mime type. func mimeTypeForPath(path: String) -> String { let url = NSURL(fileURLWithPath: path) let pathExtension = url.pathExtension if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension! as NSString, nil)?.takeRetainedValue() { if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() { return mimetype as String } } return "application/octet-stream"; } 

Y:

 extension NSMutableData { /// Append string to NSMutableData /// /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. /// /// - parameter string: The string to be added to the `NSMutableData`. func appendString(string: String) { let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true) appendData(data!) } } 

Teniendo todo esto, ahora necesita enviar esta solicitud. Aconsejaría no usar la técnica síncrona en tu pregunta. Deberías hacer esto asincrónicamente. Por ejemplo, en URLSession , en Swift 3 harías algo como:

 let request: URLRequest do { request = try createRequest(userid: userid, password: password, email: email) } catch { print(error) return } let task = URLSession.shared.dataTask(with: request) { data, response, error in guard error == nil else { // handle error here print(error!) return } // if response was JSON, then parse it do { let responseDictionary = try JSONSerialization.jsonObject(with: data!) print("success == \(responseDictionary)") // note, if you want to update the UI, make sure to dispatch that to the main queue, eg: // // DispatchQueue.main.async { // // update your UI and model objects here // } } catch { print(error) let responseString = String(data: data!, encoding: .utf8) print("responseString = \(responseString)") } } task.resume() 

O bien, para la versión de Swift 2:

 let request = createRequest(userid: userid, password: password, email: email) let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in if error != nil { // handle error here print(error) return } // if response was JSON, then parse it do { if let responseDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary { print("success == \(responseDictionary)") // note, if you want to update the UI, make sure to dispatch that to the main queue, eg: // // dispatch_async(dispatch_get_main_queue()) { // // update your UI and model objects here // } } } catch { print(error) let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding) print("responseString = \(responseString)") } } task.resume() 

Mi respuesta original está debajo para propósitos históricos:


Un par de observaciones:

  1. Está configurando HTTPBody para que sea el formato POST estándar (como si fuera una application/x-www-form-urlencoded request, aunque nunca haya especificado eso). A continuación, procede a descartarlo y reemplazarlo con los datos binarios de la representación PNG de la imagen. Es de suponer que quería enviar ambos.

  2. No podemos aconsejarle con una aclaración sobre lo que espera el servidor, pero con frecuencia es multipart/form-data , en lugar de application/x-www-form-urlencoded (por ejemplo, si se trata de un servicio web PHP, está usando la variable $_FILES ) . Si está tratando de hacer multipart/form-data , vea esto, POST multipart / form-data con Objective-C , por ejemplo, sobre cómo hacer eso. Claramente, ese es Objective-C, pero ilustra la técnica básica.

    Tenga en cuenta que hay otros formatos que utilizan otros servicios web, por lo que dudo en suponer que esto está esperando multipart/form-data solicitud multipart/form-data . Debe confirmar con precisión lo que espera el servidor.

Huelga decir que también hay otros problemas (por ejemplo, debería especificar el Content-Type de Content-Type de la solicitud, como mínimo, realmente no debería estar emitiendo una solicitud sincrónica (a menos que ya esté haciendo esto en un hilo de fondo) ), Probablemente recomendaría NSURLSession ; etc.).

Pero el problema principal es cómo estás HTTPBody . Sin embargo, es difícil para nosotros ayudarlo más sin una mayor claridad con respecto a lo que el servidor requiere.

AlamoFire ahora es compatible con Multipart:

https://github.com/Alamofire/Alamofire#uploading-multipartformdata

Aquí hay una publicación de blog con un proyecto de muestra que trata sobre el uso de Multipart con AlamoFire.

http://www.thorntech.com/2015/07/4-essential-swift-networking-tools-for-working-with-rest-apis/

El código relevante puede parecerse a esto (suponiendo que esté utilizando AlamoFire y SwiftyJSON):

 func createMultipart(image: UIImage, callback: Bool -> Void){ // use SwiftyJSON to convert a dictionary to JSON var parameterJSON = JSON([ "id_user": "test" ]) // JSON stringify let parameterString = parameterJSON.rawString(encoding: NSUTF8StringEncoding, options: nil) let jsonParameterData = parameterString!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true) // convert image to binary let imageData = UIImageJPEGRepresentation(image, 0.7) // upload is part of AlamoFire upload( .POST, URLString: "http://httpbin.org/post", multipartFormData: { multipartFormData in // fileData: puts it in "files" multipartFormData.appendBodyPart(fileData: jsonParameterData!, name: "goesIntoFile", fileName: "json.txt", mimeType: "application/json") multipartFormData.appendBodyPart(fileData: imageData, name: "file", fileName: "iosFile.jpg", mimeType: "image/jpg") // data: puts it in "form" multipartFormData.appendBodyPart(data: jsonParameterData!, name: "goesIntoForm") }, encodingCompletion: { encodingResult in switch encodingResult { case .Success(let upload, _, _): upload.responseJSON { request, response, data, error in let json = JSON(data!) println("json:: \(json)") callback(true) } case .Failure(let encodingError): callback(false) } } ) } let fotoImage = UIImage(named: "foto") createMultipart(fotoImage!, callback: { success in if success { } }) 

Gracias @Rob, tu código está funcionando bien, pero en mi caso, estoy recuperando la imagen de gallary y tomando el nombre de la imagen usando el código:

 let filename = url.lastPathComponent 

Pero este código, mostrando la extensión de la imagen como .JPG (en mayúscula), pero el servidor no acepta extensiones en la carta de captura, así que cambié mi código como:

  let filename = (path.lastPathComponent as NSString).lowercaseString 

y ahora mi código está funcionando bien.

Gracias 🙂