Jack Jack - 17 days ago 5
MySQL Question

MYSQL and Swift - Upload image and FILE || Would it be better to use Alamofire?

I am trying to upload an image, and a text file(uploading it as Data).

So far I can upload the image alone correctly, and also upload the text file data uploading it as a .txt successfully alone.

Now I need to upload both image and .txt file together...

I am not sure how to set the Paramaters up in my IOS app for this....

So far this is how I upload the .txt file (basically the same way I upload the image but I change the "filename" and "mimetype")

func createBodyWithParameters(parameters: [String : Any]?, filePathKey: String?,filePathKey1: String?, imageDataKey: NSData,imageDataKey1: NSData, 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")
}
}

let filename = "post-\(uuid).txt"
let mimetype = "image/txt"

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.append(imageDataKey as Data)

body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")

return body
}


Now I am not sure how to save both image and .txt file with that paramater.

This however is the rest of my swift code for uploading it:

let param = [
"id" : id,
"uuid" : uuid,
"Text" : Text,
"Title" : Title
] as [String : Any]

let boundary = "Boundary-\(NSUUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

let data: Data = NSKeyedArchiver.archivedData(withRootObject: blogattributedText)


var imageData = NSData()
let image = CoverImage
let width = CGSize(width: self.view.frame.width, height: image.size.height * (self.view.frame.width / image.size.width))
imageData = UIImageJPEGRepresentation(imageWithImage(image, scaledToSize: width), 0.5)! as NSData

// ... body
request.httpBody = createBodyWithParameters(parameters: param, filePathKey: "file",filePathKey1: "file1", imageDataKey: data as NSData,imageDataKey1: imageData as NSData, boundary: boundary) as Data


If anyone needs to see anymore of my code or doesn't understand my question please let me know!

Thanks in advance to anyone that can help!!

Rob Rob
Answer

If you don't want to get lost in the weeds of creating complex requests, a third party library like Alamofire would be smart. It's by the same author as AFNetworking, but it's a native Swift library.

So, an Alamofire implementation might look like:

func performRequest(urlString: String, id: String, uuid: String, text: String, title: String, blogAttributedText: NSAttributedString, image: UIImage) {

    let parameters = [
        "id" : id,
        "uuid" : uuid,
        "Text" : text,
        "Title" : title
    ]

    let imageData = UIImageJPEGRepresentation(image, 0.5)!

    let blogData = NSKeyedArchiver.archivedData(withRootObject: blogAttributedText)

    Alamofire.upload(
        multipartFormData: { multipartFormData in
            for (key, value) in parameters {
                if let data = value.data(using: .utf8) {
                    multipartFormData.append(data, withName: key)
                }
            }
            multipartFormData.append(imageData, withName: "image", fileName: "image.jpg", mimeType: "image/jpeg")
            multipartFormData.append(blogData, withName: "blog", fileName: "blog.archive", mimeType: "application/octet-stream")
    },
    to: urlString,
    encodingCompletion: { encodingResult in
        switch encodingResult {
        case .success(let upload, _, _):
            upload
                .validate()
                .responseJSON { response in
                    switch response.result {
                    case .success(let value):
                        print("responseObject: \(value)")
                    case .failure(let responseError):
                        print("responseError: \(responseError)")
                    }
            }
        case .failure(let encodingError):
            print("encodingError: \(encodingError)")
        }
    })
}

If you're going to build this request yourself, I'd suggest a few things. First, since you're sending files of different types, you might want some nice type to encapsulate this:

struct FilePayload {
    let fieldname: String
    let filename: String
    let mimetype: String
    let payload: Data
}

I'm also not sure what to make of the image/txt mime type. I'd probably use application/octet-stream for the archive.

Anyway, the building of the request could be as follows:

/// Create request.
///
/// - Parameters:
///   - url:                The URL to where the post will be sent.
///   - id:                 The identifier of the entry
///   - uuid:               The UUID of the entry
///   - text:               The text.
///   - title:              The title.
///   - blogAttributedText: The attributed text of the blog entry.
///   - image:              The `UIImage` of the image to be included.
///
/// - Returns: The `URLRequest` that was created

func createRequest(url: URL, id: String, uuid: String, text: String, title: String, blogAttributedText: NSAttributedString, image: UIImage) -> URLRequest {
    let parameters = [
        "id" : id,
        "uuid" : uuid,
        "Text" : text,     // I find it curious to see uppercase field names (I'd use lowercase for consistency's sake, but use whatever your PHP is looking for)
        "Title" : title
    ]

    let boundary = "Boundary-\(NSUUID().uuidString)"

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    request.setValue("application/json", forHTTPHeaderField: "Accept")  // adjust if your response is not JSON

    // use whatever field name your PHP is looking for the image; I used `image`

    let imageData = UIImageJPEGRepresentation(image, 0.5)!
    let imagePayload = FilePayload(fieldname: "image", filename: "image.jpg", mimetype: "image/jpeg", payload: imageData)

    // again, use whatever field name your PHP is looking for the image; I used `blog`

    let blogData = NSKeyedArchiver.archivedData(withRootObject: blogAttributedText)
    let blogPayload = FilePayload(fieldname: "blog", filename: "blog.archive", mimetype: "application/octet-stream", payload: blogData)

    request.httpBody = createBody(with: parameters, files: [imagePayload, blogPayload], boundary: boundary)

    return request
}

/// Create body of the multipart/form-data request.
///
/// - Parameters:
///   - parameters: The optional dictionary containing keys and values to be passed to web service.
///   - files:      The list of files to be included in the request.
///   - boundary:   The `multipart/form-data` boundary
///
/// - Returns: The `Data` of the body of the request.

private func createBody(with parameters: [String: String]?, files: [FilePayload], boundary: String) -> 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 file in files {
        body.append("--\(boundary)\r\n")
        body.append("Content-Disposition: form-data; name=\"\(file.fieldname)\"; filename=\"\(file.filename)\"\r\n")
        body.append("Content-Type: \(file.mimetype)\r\n\r\n")
        body.append(file.payload)
        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-\(NSUUID().uuidString)"
}

Where

extension Data {

    /// Append string to Data
    ///
    /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to `Data`, and then add that data to the `Data`, this wraps it in a nice convenient little `Data` extension. This converts using UTF-8.
    ///
    /// - parameter string:       The string to be added to the mutable `Data`.

    mutating func append(_ string: String) {
        if let data = string.data(using: .utf8) {
            append(data)
        }
    }
}

Clearly this was Swift 3 code, so I excised the NSMutableData reference.