如何将图像从 AWS S3 加载到 UICollectionView 中?

how do I load images from AWS S3 into UICollectionView?

我的 AWS S3 后端有一个桶,里面装满了我想加载到我的 UICollectionViewCell 上的图像。我应该采用什么方法来做到这一点?我也在寻找最有效的方法。

我可能会注意到,目前在我的项目中,我拥有的框架是 Alamofire、swiftyJSON 和 Haneke(用于缓存目的),尽管我不知道如何正确使用它们来实现我的目标。我可能还要补充一点,我正在使用 Parse.com 作为我的 BaaS,所以如果有一种方法可以在其中集成解析,那也将受到欢迎。

所以有什么建议 在 Swift?

我建议的解决方案是使用 Parse.com 作为书籍的元数据源,并使用 s3 作为存储。我认为您不需要任何其他组件(而且我特别怀疑基本 iOS 东西的网络代码便利包装器)。

所以我会(并且确实这样做了,以证明它有效)设置一个像这样的解析模型......

这里是 swift 中的香草 ViewController,它有一个集合视图和一组自定义 swift "Book" 对象...

// ViewController.swift
import UIKit
import Parse

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    @IBOutlet weak private var collectionView : UICollectionView!
    var books : Array<Book>!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.books = []
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        self.loadBooks()
    }

    func loadBooks() {
        var query = PFQuery(className: "Book")
        query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
            if error == nil {
                let bookObjects = objects as! [PFObject]
                for (index, object) in enumerate(bookObjects) {
                    self.books.append(Book(pfBook: object))
                }
            }
            self.collectionView.reloadData()
        }
    }

基本的东西。当视图出现时,查询解析书籍对象,当它们返回时,在它们周围创建 swift "Book" 包装器并重新加载集合视图。这是 swift 本书 class ...

// Book.swift
import Parse

class Book: NSObject {

    var pfBook : PFObject
    var coverImage : UIImage!

    init(pfBook: PFObject) {
        self.pfBook = pfBook
    }

    func fetchCoverImage(completion: (image: UIImage?, error: NSError?) -> Void) {
        let urlString = self.pfBook["coverUrl"] as! String
        let url = NSURL(string: urlString)
        let request = NSURLRequest(URL: url!)
        let queue = dispatch_get_main_queue()

        NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response: NSURLResponse?, data: NSData?, error: NSError?) in
            if error == nil {
                self.coverImage = UIImage(data: data!)
                completion(image: self.coverImage, error: nil)
            } else {
                completion(image: nil, error: error)
            }
        }
    }
}

有趣的方法是从保存在 PFObject 中的 url 中获取封面图像。 (查看 parse.com 数据浏览器中的 url 列。我在这里使用了一个快捷方式,并在网络上使用了一个很好的虚拟图像生成器。您需要实现一个具有相同签名的方法,但是它从 s3 获取图像)。

唯一剩下的就是集合视图数据源。再次回到 ViewController.swift...

// ViewController.swift
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return self.books.count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",
        forIndexPath:indexPath) as! UICollectionViewCell
    if cell.backgroundView == nil { // brand new cell
        let v = UIImageView(frame:cell.bounds)
        v.tag = 32
        v.contentMode = .ScaleToFill
        cell.backgroundView = v
    }

    let book = self.books[indexPath.row]
    let coverImage = book.coverImage
    if coverImage == nil {
        book.fetchCoverImage({ (image, error) -> Void in
            collectionView.reloadItemsAtIndexPaths([indexPath])
        })
    } else {
        let imageView = cell.viewWithTag(32) as! UIImageView
        imageView.image = book.coverImage
    }
    return cell
}

注意我们在 cellForRow... 方法中做了什么:如果书对象有一个缓存的 coverImage,我们将它放在图像视图中。否则,我们告诉这本书获取它的封面艺术,完成后,在当前索引路径处重新加载单元格(我们不再依赖于当前的单元格,因为在获取图像时时间会流逝)。

这是它的样子运行...

我遇到了这个问题并且有一个很好的工作解决方案,如何将图像从 AWS S3 加载到单元格或其他视图中的 UIImageView。为此,我额外使用了 SDWebImage SDK SDWebImage

所以让我们为UIImageView创建一个扩展并导入SDWebImage,然后将图像缓存添加到仅缓存图像链接

import UIKit
import SDWebImage

let imageCache = NSCache<NSString, NSString>()

extension UIImageView {

    func loadAsyncWithCacheFromAWSS3(_ link: String?, placeholder: UIImage? = UIImage(named: "placeholder")) {
        guard let unwrappedlink = link else { return self.image = placeholder }
        // Use your own image appearing style
        self.sd_imageTransition = .fade
        if !loadFromCache(unwrappedlink as NSString, placeholder: placeholder) {
            let file = File(bucket: 'YOUR BUCKET', key: unwrappedlink, region: 'YOUR REGION')
            AWSManager.shared.getFileURL(file: file) { [weak self] (string, url, error) in
            guard let validURL = url else { self?.image = placeholder; return }
            imageCache.setObject(NSString(string: validURL.absoluteString), forKey: NSString(string: unwrappedlink))
            guard unwrappedlink == link else {
                guard let key = link else { return }
                self?.loadFromCashe(key as NSString, placeholder: placeholder)
                return
            }
                self?.sd_setImage(with: validURL, placeholderImage: placeholder, options: [.scaleDownLargeImages])
            }
        }
    }
    // quick load from existing cashed link
    @discardableResult
    private func loadFromCache(_ key: NSString, placeholder: UIImage?) -> Bool {
        guard let string = imageCache.object(forKey: key) else { return false }
        let link = URL(string: String(string))
        self.sd_setImage(with: link, placeholderImage: placeholder, options: [.scaleDownLargeImages])
    
        return true
    }

}

这是一个单例 class 和 getFileURL 方法

func getFileURL(file: File, completionHandler: @escaping (String?, URL?, Error?) -> Void) {
    let getPreSignedURLRequest = AWSS3GetPreSignedURLRequest()
    getPreSignedURLRequest.bucket = file.bucket
    getPreSignedURLRequest.key = file.key
    getPreSignedURLRequest.httpMethod = .GET
    getPreSignedURLRequest.expires = Date().adjust(hour: 24, minute: 0, second: 0)
    getPreSignedURLRequest.minimumCredentialsExpirationInterval = 10
    
    AWSS3PreSignedURLBuilder.s3PreSignedURLBuilder(forKey: 'YOUR BUILDER URL').getPreSignedURL(getPreSignedURLRequest).continueWith { (task: AWSTask<NSURL>) -> Any? in
        if let error = task.error as NSError? {
            completionHandler(nil, nil, error)
            return nil
        }
        let presignedURL = task.result
        completionHandler(file.key, presignedURL as URL?, nil)
        return nil
    }
}

in Appdelegate in func applicationDidReceiveMemoryWarning(_ application: UIApplication) 简单调用 imageCache.removeAllObjects()