在Swift中使用参数上传图像

时间:2014-10-02 14:05:43

标签: ios image file-upload swift

我试图在Swift中上传带有参数的图片。当我尝试这段代码时,我可以得到参数而不是图像

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

编辑2:

我认为我在保存UIImage的路径上遇到了一些问题,因为php告诉我文件已经存在,我认为是因为我发送空白

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
}

3 个答案:

答案 0 :(得分:131)

在下面的评论中,您通知我们您使用$_FILES语法来检索文件。这意味着您要创建multipart/form-data请求。这个过程基本上是:

  1. 指定multipart/form-data请求的边界。

  2. 指定请求的Content-Type,指定它multipart/form-data以及边界是什么。

  3. 创建请求主体,分隔各个组件(每个发布的值以及每次上传之间)。

  4. 有关详细信息,请参阅RFC 2388。无论如何,在Swift 3中,这可能看起来像:

    /// 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"
    }
    

    使用:

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

    或者,在Swift 2中:

    /// 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 NSURLRequest that was created
    
    func createRequest (userid userid: String, password: String, email: String) -> NSURLRequest {
        let param = [
            "user_id"  : userid,
            "email"    : email,
            "password" : password]  // build your dictionary however appropriate
    
        let boundary = generateBoundaryString()
    
        let url = NSURL(string: "https://example.com/imageupload.php")!
        let request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = "POST"
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    
        let path1 = NSBundle.mainBundle().pathForResource("image1", ofType: "png") as String!
        request.HTTPBody = createBodyWithParameters(param, 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 NSData of the body of the request
    
    func createBodyWithParameters(parameters: [String: String]?, filePathKey: String?, paths: [String]?, 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")
            }
        }
    
        if paths != nil {
            for path in paths! {
                let url = NSURL(fileURLWithPath: path)
                let filename = url.lastPathComponent
                let data = NSData(contentsOfURL: url)!
                let mimetype = mimeTypeForPath(path)
    
                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.appendData(data)
                body.appendString("\r\n")
            }
        }
    
        body.appendString("--\(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.
    
    func generateBoundaryString() -> String {
        return "Boundary-\(NSUUID().UUIDString)"
    }
    
    /// Determine mime type on the basis of extension of a file.
    ///
    /// This requires MobileCoreServices framework.
    ///
    /// - 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.
    
    func mimeTypeForPath(path: String) -> String {
        let url = NSURL(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";
    }
    

    extension NSMutableData {
    
        /// Append string to NSMutableData
        ///
        /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8.
        ///
        /// - parameter string:       The string to be added to the `NSMutableData`.
    
        func appendString(string: String) {
            let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
            appendData(data!)
        }
    }
    

    完成所有这些后,您现在需要提交此请求。我建议不要在你的问题中使用同步技术。你应该异步这样做。例如,在URLSession中,在Swift 3中,您可以执行以下操作:

    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 error == nil else {
            // handle error here
            print(error!)
            return
        }
    
        // if response was JSON, then parse it
    
        do {
            let responseDictionary = try JSONSerialization.jsonObject(with: data!)
            print("success == \(responseDictionary)")
    
            // 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
            // }
        } catch {
            print(error)
    
            let responseString = String(data: data!, encoding: .utf8)
            print("responseString = \(responseString)")
        }
    }
    task.resume()
    

    或者,对于Swift 2的演绎:

    let request = createRequest(userid: userid, password: password, email: email)
    
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
        if error != nil {
            // handle error here
            print(error)
            return
        }
    
        // if response was JSON, then parse it
    
        do {
            if let responseDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
                print("success == \(responseDictionary)")
    
                // note, if you want to update the UI, make sure to dispatch that to the main queue, e.g.:
                //
                // dispatch_async(dispatch_get_main_queue()) {
                //     // update your UI and model objects here
                // }
            }
        } catch {
            print(error)
    
            let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
            print("responseString = \(responseString)")
        }
    }
    task.resume()
    

    我的原始答案在下面是出于历史目的:


    有几点意见:

    1. 您将HTTPBody设置为标准POST格式(就好像它是application/x-www-form-urlencoded请求一样,即使您从未指定过这种格式)。然后,您继续丢弃它并将其替换为图像的PNG表示的二进制数据。你可能想要发送两者。

    2. 我们无法建议您准确说明服务器的期望,但经常是multipart/form-data,而不是application/x-www-form-urlencoded(例如,如果它是PHP网络服务,它使用$_FILES变量)。如果您尝试multipart/form-data,请参阅此POST multipart/form-data with Objective-C,例如如何执行此操作。显然,这是Objective-C,但它说明了基本技术。

      请注意,其他网络服务使用的格式还有其他格式,所以我不敢假设这是期待multipart/form-data请求。您应该确切地确认服务器的期望。

    3. 毋庸置疑,还有其他问题(例如,你真的应该至少指定请求的Content-Type;你真的不应该发出同步请求(除非你&#39) ;已经在后台线程中执行此操作);我可能会建议NSURLSession;等等。

      但主要问题是你如何填充HTTPBody。但是,如果没有更清楚地了解服务器的要求,我们很难帮助您。

答案 1 :(得分:14)

AlamoFire现在支持Multipart:

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

这是一篇博客文章,内容涉及使用Multipart和AlamoFire进行示例项目。

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

相关代码可能看起来像这样(假设您正在使用AlamoFire和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 { }
})

答案 2 :(得分:1)

谢谢你@Rob,你的代码工作正常,但在我的情况下,我正在从gallary中重新获取图像并使用代码获取图像的名称:

let filename = url.lastPathComponent

但是这段代码,将图片扩展名显示为.JPG(大写字母),但服务器不接受captital letter中的扩展名,所以我将代码更改为:

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

现在我的代码工作正常。

谢谢:)