Upload image with parameters in Swift

2019-08-23 07:51发布

问题:

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
}

回答1:

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:

  1. Specify a boundary for your multipart/form-data request.

  2. Specify a Content-Type of the request that specifies that it multipart/form-data and what the boundary is.

  3. 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 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"
}

With:

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

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:

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 let data = data, error == nil else {
        // handle error here
        print(error ?? "Unknown error")
        return
    }

    // parse `data` here, then parse it

    // note, if you want to update the UI, make sure to dispatch that to the main queue, e.g.:
    //
    // DispatchQueue.main.async {
    //     // update your UI and model objects here
    // }
}
task.resume()

For Swift 2 renditions, see previous revision of this answer.



回答2:

AlamoFire now supports Multipart:

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

Here's a blog post with sample project that touches on using Multipart with AlamoFire.

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

The relevant code might look something like this (assuming you're using AlamoFire and 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 { }
})


回答3:

Thank you @Rob, your code is working fine, but in my case, I am retriving image from gallary and taking name of the image by using code:

let filename = url.lastPathComponent

But this code, displaying image extension as .JPG (in capital letter), but server not accepting extensions in captital letter, so i changed my code as:

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

and now my code is working fine.

Thank you :)