session-ios/Signal/src/ViewControllers/GifPicker/GifPickerCell.swift
Matthew Chen 3bfb91d0c2 Ignore obsolete GIF requests.
// FREEBIE
2017-10-02 13:37:32 -04:00

188 lines
8.3 KiB
Swift

//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
import Foundation
class GifPickerCell: UICollectionViewCell {
let TAG = "[GifPickerCell]"
// MARK: Properties
var imageInfo: GiphyImageInfo? {
didSet {
AssertIsOnMainThread()
ensureCellState()
}
}
// Loading and playing GIFs is quite expensive (network, memory, cpu).
// Here's a bit of logic to not preload offscreen cells that are prefetched.
var isCellVisible = false {
didSet {
AssertIsOnMainThread()
ensureCellState()
}
}
// We do "progressive" loading by loading stills (jpg or gif) and "full" gifs.
// This is critical on cellular connections.
var stillAssetRequest: GiphyAssetRequest?
var stillAsset: GiphyAsset?
var fullAssetRequest: GiphyAssetRequest?
var fullAsset: GiphyAsset?
var imageView: YYAnimatedImageView?
// MARK: Initializers
@available(*, unavailable, message:"use other constructor instead.")
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
owsFail("\(self.TAG) invalid constructor")
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func prepareForReuse() {
super.prepareForReuse()
imageInfo = nil
isCellVisible = false
stillAsset = nil
stillAssetRequest?.cancel()
stillAssetRequest = nil
fullAsset = nil
fullAssetRequest?.cancel()
fullAssetRequest = nil
imageView?.removeFromSuperview()
imageView = nil
}
private func clearStillAssetRequest() {
stillAssetRequest?.cancel()
stillAssetRequest = nil
}
private func clearFullAssetRequest() {
fullAssetRequest?.cancel()
fullAssetRequest = nil
}
private func clearAssetRequests() {
clearStillAssetRequest()
clearFullAssetRequest()
}
private func ensureCellState() {
guard isCellVisible else {
// Cancel any outstanding requests.
clearAssetRequests()
// Clear image view so we don't animate offscreen GIFs.
imageView?.removeFromSuperview()
imageView = nil
return
}
guard let imageInfo = imageInfo else {
clearAssetRequests()
return
}
guard self.fullAsset == nil else {
return
}
// The Giphy API returns a slew of "renditions" for a given image.
// It's critical that we carefully "pick" the best rendition to use.
guard let fullRendition = imageInfo.pickGifRendition() else {
Logger.warn("\(TAG) could not pick gif rendition: \(imageInfo.giphyId)")
clearAssetRequests()
return
}
guard let stillRendition = imageInfo.pickStillRendition() else {
Logger.warn("\(TAG) could not pick still rendition: \(imageInfo.giphyId)")
clearAssetRequests()
return
}
if stillAsset == nil && fullAsset == nil && stillAssetRequest == nil {
stillAssetRequest = GifDownloader.sharedInstance.requestAsset(rendition:stillRendition,
priority:.high,
success: { [weak self] assetRequest, asset in
guard let strongSelf = self else { return }
if assetRequest != nil && assetRequest != strongSelf.stillAssetRequest {
// Ignore obsolete requests.
return
}
strongSelf.clearStillAssetRequest()
strongSelf.stillAsset = asset
strongSelf.tryToDisplayAsset()
},
failure: { [weak self] assetRequest in
guard let strongSelf = self else { return }
if assetRequest != strongSelf.stillAssetRequest {
// Ignore obsolete requests.
return
}
strongSelf.clearStillAssetRequest()
})
}
if fullAsset == nil && fullAssetRequest == nil {
fullAssetRequest = GifDownloader.sharedInstance.requestAsset(rendition:fullRendition,
priority:.low,
success: { [weak self] assetRequest, asset in
guard let strongSelf = self else { return }
if assetRequest != nil && assetRequest != strongSelf.fullAssetRequest {
// Ignore obsolete requests.
return
}
// If we have the full asset, we don't need the still asset.
strongSelf.clearAssetRequests()
strongSelf.fullAsset = asset
strongSelf.tryToDisplayAsset()
},
failure: { [weak self] assetRequest in
guard let strongSelf = self else { return }
if assetRequest != strongSelf.fullAssetRequest {
// Ignore obsolete requests.
return
}
strongSelf.clearFullAssetRequest()
})
}
}
private func tryToDisplayAsset() {
guard let asset = pickBestAsset() else {
owsFail("\(TAG) missing asset.")
return
}
guard let image = YYImage(contentsOfFile:asset.filePath) else {
owsFail("\(TAG) could not load asset.")
return
}
if imageView == nil {
let imageView = YYAnimatedImageView()
self.imageView = imageView
self.contentView.addSubview(imageView)
imageView.autoPinWidthToSuperview()
imageView.autoPinHeightToSuperview()
}
guard let imageView = imageView else {
owsFail("\(TAG) missing imageview.")
return
}
imageView.image = image
}
private func pickBestAsset() -> GiphyAsset? {
if let fullAsset = fullAsset {
return fullAsset
}
if let stillAsset = stillAsset {
return stillAsset
}
return nil
}
}