在 Swift 中上传带参数的图像

Posted

技术标签:

【中文标题】在 Swift 中上传带参数的图像【英文标题】:Upload image with parameters in Swift 【发布时间】:2014-10-02 14:05:42 【问题描述】:

我正在尝试在 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

【问题讨论】:

你对filePathKey有什么问题我也面临同样的问题。? 上传图片简单...Using Alamofire 【参考方案1】:

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

    为您的multipart/form-data 请求指定边界。

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

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

有关详细信息,请参阅RFC 7578。无论如何,在 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 fileURL = Bundle.main.url(forResource: "image1", withExtension: "png")!
    request.httpBody = try createBody(with: parameters, filePathKey: "file", urls: [fileURL], 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 urls:         The optional array of file URLs 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]? = 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)"

与:

extension URL 
    /// Mime type for the URL
    ///
    /// Requires `import UniformTypeIdentifiers` for ios 14 solution.
    /// Requires `import MobileCoreServices` for pre-iOS 14 solution

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

完成所有这些后,您现在需要提交此请求。我建议这是异步完成的。例如,使用URLSession,您可以执行以下操作:

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 let data = data, error == nil else 
        // handle error here
        print(error ?? "Unknown error")
        return
    
    
    // parse `data` here, then parse it
    
    // 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
    // 

task.resume()

如果您要上传大型资产(例如视频等),您可能希望使用上述基于文件的排列。见https://***.com/a/70552269/1271826。


对于 Swift 2 版本,请参阅 previous revision of this answer。

【讨论】:

谢谢!很有用! 我尝试了很多东西保存在本地图像...让documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String 我可以保存它,但php告诉我比我尝试上传它时已经存在的,我想我发送的图像是空白的,否则会出错......我想我在路径和 filePathKey 之间混淆了我编辑我的答案! @NicolasMiari - 这是 Swift 2。正如 mimeTypeForPath 的评论所说,你需要 MobileCoreServices。所以,在文件的顶部,做import MobileCoreServices @luke - 这是一个见仁见智的问题,但感觉客户端应用程序有点纠结于后端架构的实现细节。您还可能要处理两个身份验证系统和两个故障点。我可能会倾向于应用程序的单一端点,并让网络服务管理图像存储。 @KrutikaSonawala - 以上,我假设您有一个编写 JSON 响应的 Web 服务。 (这是让 Web 服务向客户端应用程序返回可解析响应的最可靠方法之一。)也许您的 Web 服务并非旨在返回 JSON 响应。或者,您的 Web 服务中可能存在错误,导致它无法创建正确的 JSON 响应。根据您的评论,我无法诊断。如果上述内容不清楚,我建议您发布您自己的问题,向我们展示您做了什么、您期望的回复以及您实际收到的回复。【参考方案2】:

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  
)

【讨论】:

【参考方案3】:

谢谢@Rob,您的代码运行良好,但就我而言,我正在从图库中检索图像并使用代码获取图像名称:

let filename = url.lastPathComponent

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

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

现在我的代码运行良好。

谢谢你:)

【讨论】:

以上是关于在 Swift 中上传带参数的图像的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 alamofire 4 上传带参数的图像?

如何在上传带参数的图像时使用 AFNetworking 2.0 设置 cookie?

alamofire上传带json参数的图片

Swift Vision Framework - VNRecognizeTextRequest:传递给不带参数的调用的参数

NSURLSessionUploadTask 不上传带参数的图片

AFNetworking 上传带参数的图片