如何使用Swift保存远程图片?

16

我正在尝试使用Swift显示和保存图片。在第一次访问时,它会在ImageView上显示远程图片,而在第二次访问时,它会显示空的ImageView,而不是应该显示在第一次访问中保存的本地图片。

    var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
    var imagePath = paths.stringByAppendingPathComponent("images/\(id)/logo.jpg" )
    var checkImage = NSFileManager.defaultManager()

    if (checkImage.fileExistsAtPath(imagePath)) {
        let getImage = UIImage(contentsOfFile: imagePath)
        self.image?.image = getImage
    } else {
        dispatch_async(dispatch_get_main_queue()) {
            let getImage =  UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)))
            UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)
            self.image?.image = getImage
        }
    }

编辑:这个对我有效。

var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var dirPath = paths.stringByAppendingPathComponent("images/\(id)" )
var imagePath = paths.stringByAppendingPathComponent("images/\(id)/logo.jpg" )
var checkImage = NSFileManager.defaultManager()

if (checkImage.fileExistsAtPath(imagePath)) {
    let getImage = UIImage(contentsOfFile: imagePath)
    self.image?.image = getImage
} else {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
        checkImage.createDirectoryAtPath(dirPath, withIntermediateDirectories: true, attributes: nil, error: nil)
        let getImage =  UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)))
        UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)

        dispatch_async(dispatch_get_main_queue()) {
            self.image?.image = getImage
            return
        }
    }
}
2个回答

21
回答你的主要问题,你正在调用错误的UIImage初始化程序。在 Swift 2 中,你应该调用 UIImage(contentsOfFile: imagePath),而在 Swift 3 中,应该调用UIImage(contentsOf: imagePath)
此外,看起来你正在尝试在后台使用dispatch_async(或在 Swift 3 中使用DispatchQueue)进行远程获取,但是你正在传递到主队列,因此实际上正在阻塞主线程/用户界面线程。相反,你应该将其分派到其中一个后台队列,然后在实际设置 UI 图像时再返回到主队列:
Swift 3:
DispatchQueue.global(qos: DispatchQoS.background.qosClass).async {
    do {
        let data = try Data(contentsOf: URL(string: self.remoteImage)!)
        let getImage = UIImage(data: data)
        try UIImageJPEGRepresentation(getImage!, 100)?.write(to: imagePath)
        DispatchQueue.main.async {
            self.image?.image = getImage
            return
        }
    }
    catch {
            return
    }
}

Swift 2:


Swift 2:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
    let getImage =  UIImage(data: NSData(contentsOfURL: NSURL(string: self.remoteImage)))
    UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)

    dispatch_async(dispatch_get_main_queue()) {
        self.image?.image = getImage
        return
    }
}

@Rob的答案关于获取远程图片并保存它真的是最好的方法。


谢谢,我已将其更改为UIImage(contentsOfFile: imagePath)。但是当我将dispatch更改为dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0))时,甚至远程图像也不显示。 - bilmiyore
谢谢,我认为调度现在更好了。但是我仍然无法在本地加载图像。我认为“var imagePath = paths.stringByAppendingPathComponent("images/(id)/logo.jpg" )”是问题所在,因为“if block”根本没有显示出来。 - bilmiyore
你设置了断点并查看了 imagePath 变量以确保它是正确的吗?(你也可以使用 println(imagePath) 来输出变量值) - Mike S
当我将路径更改为"(id)-logo.jpg"时,它可以工作。问题应该是子目录。 - bilmiyore
1
@bilmiyore 只需调用 NSFileManager 方法 createDirectoryAtPath - Rob
显示剩余3条评论

10
你的代码使用了 NSData(contentsOfURL:) 方法(现在称为 Data(contentsOf:))来分发。如果你要使用该同步方法请求远程图像,应该在后台队列上执行。
此外,你正在将 NSData 转换为 UIImage,然后再使用 UIImageJPEGRepresentation 将其转换回 NSData。不要通过 UIImageJPEGRepresentation 来回传数据,因为这会改变原始负载的大小并且影响资产的大小。只需确认数据包含图像,然后写入原始的 NSData
因此,在 Swift 3 中,你可能需要执行以下操作:
DispatchQueue.global().async {
    do {
        let data = try Data(contentsOf: URL(string: urlString)!)
        if let image = UIImage(data: data) {
            try data.write(to: fileURL)
            DispatchQueue.main.async {
                self.imageView?.image = image
            }
        }
    } catch {
        print(error)
    }
}

更好的做法是使用NSURLSession,因为你可以更好地诊断问题,它是可取消的等等。(不要使用已弃用的NSURLConnection。)我还会检查响应的statusCode。例如:
func requestImage(_ url: URL, fileURL: URL) {
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        // check for fundamental network issues (e.g. no internet, etc.)

        guard let data = data, error == nil else {
            print("dataTask error: \(error?.localizedDescription ?? "Unknown error")")
            return
        }

        // make sure web server returned 200 status code (and not 404 for bad URL or whatever)

        guard let httpResponse = response as? HTTPURLResponse, 200 ..< 300 ~= httpResponse.statusCode else {
            print("Error; Text of response = \(String(data: data, encoding: .utf8) ?? "(Cannot display)")")
            return
        }

        // save image and update UI

        if let image = UIImage(data: data) {
            do {
                // add directory if it doesn't exist

                let directory = fileURL.deletingLastPathComponent()
                try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)

                // save file

                try data.write(to: fileURL, options: .atomic)
            } catch let fileError {
                print(fileError)
            }

            DispatchQueue.main.async {
                print("image = \(image)")
                self.imageView?.image = image
            }
        }
    }
    task.resume()

}

请注意,只有在您尚未创建该文件夹时才需要及时创建文件夹。个人建议在构建原始路径时创建该文件夹,而不是在完成处理程序中创建该文件夹,但您可以按任何方式执行此操作。确保在写入文件之前该文件夹存在即可。
无论如何,希望这可以说明主要观点,即您应该保存原始资产,并且应该在后台执行此操作。
对于Swift 2版本,请参见本答案的上一个修订版本

我刚刚尝试了这个,但无法加载任何远程或本地图像。 - bilmiyore
1
@bilmiyore FYI,我修改了我的答案以改进错误处理,以诊断是否出现任何问题。至于本地图像加载,我没有触及它。 - Rob
谢谢@Rob,那个也行。只有一个问题,我无法让它在子目录中工作。"(id)-logo.jpg"路径可以工作,但"(id)\logo.jpg"不行。 - bilmiyore
@bilmiyore 你不能只是保存到不存在的子目录。如果它不存在,你必须使用 NSFileManager 创建该子目录。另外,看看我的最后一个例子,我会向你展示如何以一种方式使用 writeToFile,使其提供有意义的错误消息。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接