77

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
}
2
  • What was your problem with filePathKey i am facing the same problem.? Commented Apr 13, 2015 at 20:47
  • Upload image simply...Using Alamofire Commented Apr 26, 2016 at 12:01

3 Answers 3

150

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 `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.

Sign up to request clarification or add additional context in comments.

21 Comments

I try a lot of things saving in local the image... let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String i could save it, but the php tell me than already exist when i try to upload it, i think than i send the image in blank or smth goes wrong... i think i'm confuse between paths and filePathKey i edit my answer!
@user3426109 By the way, the choice of NSSearchPathForDirectories or NSBundle is how you specify which local file you're uploading. But, if you're getting your "already exist" message from the server, then this has nothing to do with that. The "already exist" indicates that the server has a copy of a file of that name (and apparently the script is written such that it won't just overwrite it).
@NicolasMiari - This is Swift 2. As the comment for mimeTypeForPath says, you need MobileCoreServices. So, up at the top of the file, do import MobileCoreServices.
@luke - It’s a matter of opinion, but it feels like the client app is a bit entangled in implementation details of the back end architecture. You’re also likely dealing with two authentication systems and two points of failure. I’d probably lean towards a single end point for the app, and have the web service manage the image storage.
@KrutikaSonawala - Above, I assumed you had a web service that wrote JSON responses. (It's one of the most robust ways to have a web service return a parseable response to a client app.) Perhaps your web service isn't designed to return a JSON response. Or perhaps you had an error in your web service that prevented it from creating a proper JSON response. I can't diagnose that on the basis of your comment. If the aforementioned isn't clear, I'd suggest you post your own question showing us what you did, what you expected in response, and what you actually received in response.
|
14

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

Comments

2

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.