4

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!!

  • Please provide `SHOW CREATE TABLE`. – Rick James Nov 17 '16 at 01:11
  • 1
    This has nothing to do with PHP. Please fix your tags. – ChristianF Nov 17 '16 at 08:56
  • @ChristianF done, sorry. –  Nov 17 '16 at 09:15
  • @RickJames I can do but there's no problems with the tables, just with the parameters in swift for uploading the 2 files. I know how to handle the php and mysql side, just need to figure out the correct parameters for upload a **text document++ and ++image++ –  Nov 17 '16 at 09:58

2 Answers2

1

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Wow this deffienetly looks promising I shall give it a good ASAP..! Thank you. –  Nov 21 '16 at 07:03
  • Fantastic, can't thank you enough!! I have decided to do it normaly without Alamofire as that's how I've been doing it and I would have to change alot of code. But if it does have several adavantages I think I might, what do you think? –  Nov 21 '16 at 08:28
  • Best one Rob. Thanks. – Hasya Nov 21 '16 at 09:53
  • @Jack - If you'll have to refactor a lot of code to integrate Alamofire, then perhaps you don't worry about it for now. But next time, consider Alamofire, as it's a pretty robust library and could save you time in the future. – Rob Nov 21 '16 at 11:29
0

Alternatively, you can make JSON rest API's.

  • Convert your notepad file content in Base64 encoded string.
  • Convert your image file in Base64 encoded string.
  • Pass both Base64 encoded string in request parameters.
  • Once you get Base64 encoded string on API file in function parameter.
  • Again decode Base64 string and store it in content folder.

https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking

upload image using AFNetworking in swift ios

Uploading image with AFNetworking 2.0

Note - Show image on iOS app, you can make absolute URL and using image caching class you can display image.

Community
  • 1
  • 1
Hasya
  • 9,792
  • 4
  • 31
  • 46
  • Hi again! so upload both files as Base64 string and then in the php before uploading to the server convert them to normal files and store them with a link to the content folder? and then getting them back to the IOS app I just do the normal procedure downloading them from the link? upvoting for showing interest in the question though :) –  Nov 18 '16 at 10:38
  • I guess you question code is like HTTP post on server location, right ? – Hasya Nov 18 '16 at 10:50
  • yeh, if it makes it easier I can add all the post code from my app and php code? –  Nov 18 '16 at 10:52
  • Better to swift your code on AFNetworking in swift and change way to uploading media material as Base64. – Hasya Nov 18 '16 at 13:41
  • Hmm I'll probably change all my code to work with AFnetworking if I can't get anything done by tonight...But there must be a way with swift using normal NSurlsession –  Nov 18 '16 at 15:32
  • First, using base64-encoded JSON rather than multipart makes the request 33% larger. I'm not sure I'd recommend that, but you certainly can. Second, if you're going to use a third-party utility (which is a good idea when doing something complicated), I'd suggest Alamofire rather than AFNetworking. They are both from the same author, and I'd suggest staying within Swift, rather than introducing an Objective-C library into the mix. – Rob Nov 21 '16 at 01:11
  • @Rob - Good suggestion Alamofire. – Hasya Nov 21 '16 at 09:51