I have an UIImageView
extension with a method to download an set image, the extension also contains an UIActivityIndicatorView
which i set as a view before the image is loaded once the image is loaded i remove or hide the UIActivityIndicatorView
from the UIImageView
extension UIImageView {
private static let imageProcessingQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".imageprocessing", attributes: .concurrent)
var activityIndicator: UIActivityIndicatorView! {
get {
return objc_getAssociatedObject(self, &activityIndicatorAssociationKey) as? UIActivityIndicatorView
}
set(newValue) {
objc_setAssociatedObject(self, &activityIndicatorAssociationKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func showActivityIndicatory() {
// activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
activityIndicator = UIActivityIndicatorView(frame: CGRect.init(x: 0, y: 0, width: 50, height: 50))
activityIndicator.center = self.center
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
self.isUserInteractionEnabled = false
UIImageView.imageProcessingQueue.async {
DispatchQueue.main.async {
self.activityIndicator.startAnimating()
self.addSubview(self.activityIndicator)
}
}
}
func hideIndicator() {
UIImageView.imageProcessingQueue.async {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.activityIndicator.removeFromSuperview()
}
}
}
func setImage(url : String) {
self.showActivityIndicatory()
operatorManager.loadImage(url: url) { (image) in
self.image = image
self.hideIndicator()
}
}
}
And then i can use this extension function like this,
guard let cell = tableView.dequeueReusableCell(withIdentifier:"Tableview", for: indexPath) as? TableViewCell else {
fatalError()
}
let imagees = list[indexPath.row]
cell.labelView.text = imagees.urlString
cell.iconView.setImage(url: imagees.urlString)
This is the Operation
for loading image from Cache if available or from a server.
class LoadImageOperation: ConcurrentOps<UIImage> {
private let session: URLSession
private let url: String
private var task: URLSessionTask?
private var defaultImage: UIImage?
// MARK: - Init
init(session: URLSession = URLSession.shared, url: String) {
self.session = session
self.url = url
self.defaultImage = UIImage(named: "icons")
}
override func main() {
guard let imageURL = URL(string: url) else {
cancel()
return
}
if let cachedImage = DataCache.shared.object(forKey: url) {
DispatchQueue.main.async {
self.complete(result: cachedImage as! UIImage)
}
cancel()
}
task = session.downloadTask(with: imageURL){ (url,response,error) in
guard let url = url ,
let data = try? Data(contentsOf: url),
let image = UIImage(data: data) else {
DispatchQueue.main.async {
if error != nil {
self.complete(result: self.defaultImage!)
} else {
self.complete(result: self.defaultImage!)
}
}
return
}
DispatchQueue.main.async {
DataCache.shared.saveObject(object: image, forKey: self.url)
self.complete(result: image)
}
}
task?.resume()
}
}
The operation is managed here ,
func loadImage(url: String, completionHandler: @escaping (_ result: UIImage) ->Void) {
let operation = LoadImageOperation(url: url)
operation.completionHandler = completionHandler
operation.name = "loadImageOperation"
queueManager.enqueue(operation)
}
When my tableView
first starts loading the indicator
shows and hides perfectly, but when i scroll the tableview
, it starts animating the indicator
again on the UIImageView
.
How do i prevent this ?
The solution was just simple for me to believe until i tried it out.Its not the best out there but it fixed my problem.
func setImage(url : String) {
if DataCache.shared.object(forKey: url) != nil {
self.hideIndicator()
}else{
self.showActivityIndicatory()
}
operatorManager.loadImage(url: url) { (image) in
self.image = image
self.hideIndicator()
}
}
I checked if the image its cached before i even run my Operation
, if its cached i hide the UIActivityIndicatorView
else i show it . why i think this is not 100% is that, the operation is also checking for cached images which i think can increase CPU.