1

I am working on a project wherein I'd like to select an image and then upload it to the server. I am able to click the upload image button, select an image, and change the image in the iOS simulator but the upload fails. When I include a print(request) after the request.httpBody = creatBodyWithParams() statement, it shows that the original url has not changed. I am using XAMPP to set up a virtual server and php to communicate with it.

My code:

// upload image button clicked
@IBAction func edit_click(_ sender: Any) {

    // select image
    let picker = UIImagePickerController()
    picker.delegate = self

    //picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
    picker.allowsEditing = true
    self.present(picker, animated: true, completion: nil)
}

// selected image
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    avaImg.image = info[UIImagePickerControllerEditedImage] as? UIImage
    self.dismiss(animated: true, completion: nil)

    // call function of uploading image file to server
    uploadImage()
}

// create body of HTTP request to upload image file
func createBodyWithParams(parameters: [String : String]?, filePathKey: String?, imageDataKey: NSData, boundary: String) -> NSData {

    let body = NSMutableData();

    if parameters != nil {
        for (key, value) in parameters! {
            body.appendString(string: "--\(boundary)\r\n")
            body.appendString(string: "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
            body.appendString(string: "\(value)\r\n")
        }
    }

    let filename = "image.jpg"

    let mimetype = "image/jpg"

    body.appendString(string: "--\(boundary)\r\n")
    body.appendString(string: "Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
    body.appendString(string: "Content-Type: \(mimetype)\r\n\r\n")
    body.append(imageDataKey as Data)
    body.appendString(string: "\r\n")

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

    return body
}

// upload image to server
func uploadImage() {

    let id = user!["id"] as! String

    let address = URL(string: "http://localhost/project/uploadImage.php")!
    var request = URLRequest(url: address)
    request.httpMethod = "POST"
    let param = ["id" : id]

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

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

    let imageData = UIImageJPEGRepresentation(avaImg.image!, 0.5)

    if imageData == nil {
        return
    }

    request.httpBody = createBodyWithParams(parameters: param, filePathKey: "file", imageDataKey: imageData! as NSData, boundary: boundary) as Data

    // launch session
    let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in

        // get main queue to communicate back to user
        DispatchQueue.main.async {

            if error == nil {

                do {

                    let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary

                    guard let parseJSON = json else {

                        print("error while parsing")
                        return

                    }

                    print(parseJSON)

                } catch {

                    print("Caught an error: \(error)")

                }

            } else {

                print(error)

            }
        }
    })

    task.resume()

}

With the following extension:

// creating protocol of appending string to var of type data
extension NSMutableData {

    appendString(string : String) {

        let data = string.data(using: String.Encoding.utf8, allowLossyConversion: true)
        append(data!)

    }
}

I have consulted the following pages for help and have tried changing my code to reflect the differences in their approaches but I am left banging my head against the wall.

Uploading image with other parameters in SWIFT

Upload image with parameters in Swift

http://www.kaleidosblog.com/how-to-upload-images-using-swift-2-send-multipart-post-request

EDIT 1:

Forgot error message (dur): Caught an error: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}

Error is occurring somewhere in the do statement. I think something is going on with the request because testing http://localhost/project/uploadImage.php in my browser returns the array {"message":"Missing required information"}

EDIT 2: PHP Code

uploadImage.php

<?php

// report all errors except E_NOTICE
error_reporting(E_ALL & ~E_NOTICE);

// Part 1: Upload File
// Step 1: check data passed to this php file
if (empty($_REQUEST["id"])) {

    $returnArray["message"] = "Missing required information";
    print json_encode($returnArray);
    return;
}

// request user id
$id = htmlentities($_REQUEST["id"]);

// step 2: create a folder for user with id as folder name
$folder = "/Applications/XAMPP/xamppfiles/htdocs/project/image/" . $id;

// if folder doesn't exist
if (!file_exists($folder)) {

    mkdir($folder, 0777, true);

}

// step 3: move uploaded file
$folder = $folder . "/" . basename($_FILES["file"]["name"]);

if (move_uploaded_file($_FILES["file"]["tempname"], $folder)) {
    $returnArray["status"] = "200";
    $returnArray["message"] = "The file has been uploaded successfully.";
} else {
    $returnArray["status"] = "300";
    $returnArray["message"] = "Error while uploading.";
}


// Part 2: Updating image path
// build secure connection

$file = parse_ini_file("../../../project.ini");

// store variable information from ini file in php as variables
$host = trim($file["dbhost"]);
$user = trim($file["dbuser"]);
$pass = trim($file["dbpass"]);
$name = trim($file["dbname"]);

// include access.php to call connect function from access.php file
require ("secure/access.php");
$access = new access($host, $user, $pass, $name);
$access->connect();

// STEP 5: save path to uploaded file in database
$path = "http://localhost/project/image/" . $id . "/ava.jpg";
$access->updateImagePath($path, $id);

// STEP 6: get new user information after updating
$user = $access->selectUserViaID($id);

$returnArray["id"] = $user["id"];
$returnArray["username"] = $user["username"];
$returnArray["fullname"] = $user["fullname"];
$returnArray["email"] = $user["email"];
$returnArray["image"] = $user["image"];

// Step 7. Close Connection
$access->disconnect();

// Step 8. feedback array to app
echo json_encode($returnArray);

?>
Community
  • 1
  • 1
nsmedira
  • 78
  • 2
  • 10
  • What does the error show when it prints to the console window? – Pierce Feb 17 '17 at 23:02
  • What do you get if you put `print(String(data: data!, encoding: .utf8))` before `DispatchQueue.main.async` ? – OOPer Feb 18 '17 at 00:26
  • @OOPer When I add `print(String(data: data!, encoding: .utf8))` before `DispatchQueue.main.async` I get the following before the error message I included in the main post: `
    Notice: Undefined index: tempname in /Applications/XAMPP/xamppfiles/htdocs/project/uploadImage.php on line 31
    {"status":"300","message":"Error while uploading.","id":"98","username":"username","fullname":"full name","email":"*****@me.com","image":"http:\/\/localhost\/project\/image\/98\/ava,jpg"}`
    – nsmedira Feb 18 '17 at 00:43
  • @Pierce I added the error to the original post. I think it has to do with the `createBodyWithParams()` function and ultimately the `request` – nsmedira Feb 18 '17 at 00:49
  • @OOPer I see that some weird stuff is going on with the URL. I'm guessing something went wrong when I was appending the body. Any insights? – nsmedira Feb 18 '17 at 00:50
  • Seems your XAMPP php is configured to output Notices. Update your php.ini, or update the uploadImage‌​.php as not to generate any Notices. – OOPer Feb 18 '17 at 01:13
  • @OOPer looks like I am no longer catching an error in the `do` statement and it is printing the parseJSON variable, so that's progress I suppose. But still not getting the file moved... – nsmedira Feb 18 '17 at 14:48
  • @OOPer new error message: {"status":"300","message":"Error while uploading.","id":"98","username":"username","fullname":"fullname","email":"********@me.com","image":"http:\/\/localhost\/project\/image\/98\/ava.jpg"} { ava = "http://localhost/project/image/98/ava.jpg"; email = "********@me.com"; fullname = "fullname"; id = 98; message = "Error while uploading."; status = 300; username = username; } – nsmedira Feb 18 '17 at 14:54
  • @OOPer ^^ this after disabling notices in the uploadImage.php file. would it be helpful to see that code? – nsmedira Feb 18 '17 at 14:54
  • I have confirmed that your code can send a valid POST request. So, the problem may reside in your php side, including php settings. Some sort of inconsistency between Swift side and php side may exist. Anyway showing your php code can be some help to solve your issue. – OOPer Feb 18 '17 at 15:24
  • @OOPer php code added above. What technique did you use to confirm that code can send valid POST request? – nsmedira Feb 18 '17 at 16:50
  • Seems `move_uploaded_file` has failed. Have you checked if your php code really works with sending POST request from browser or any other tools ? At least, you need to change `"tempname"` to `"tmp_name"`. – OOPer Feb 18 '17 at 21:21
  • _What technique_ nothing special. Just used your code and sent request to our POST testing URL, which dumps all `$_POST` and `$_FILES`, that's all. – OOPer Feb 18 '17 at 21:24
  • @OOPer I changed a syntax error I found in a related php file and also tmp_name and now it is working. Can you please explain the reason why it must be tmp_name? – nsmedira Feb 19 '17 at 00:15
  • `'tmp_name'` is a system defined key string, please check the [documentation page](http://php.net/manual/en/features.file-upload.post-method.php) of php. – OOPer Feb 19 '17 at 00:26

1 Answers1

0

With help from @OOper in comments I was able to trace error to syntax error in a function called by my main uploadImage.php file (see main post) and also to the use of "tempname" when I should have used "tmp_name."

nsmedira
  • 78
  • 2
  • 10