Cache UIImage objects to avoid re-decoding images unnecessarily
This commit is contained in:
parent
27b39b79e6
commit
c12d2db258
|
@ -112,6 +112,7 @@
|
|||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
|
||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2425217ABF63005076CC /* UserActivityType.swift */; };
|
||||
D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */; };
|
||||
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; };
|
||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; };
|
||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
|
||||
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
|
||||
|
@ -468,6 +469,7 @@
|
|||
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D62D2425217ABF63005076CC /* UserActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityType.swift; sourceTree = "<group>"; };
|
||||
D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringHelperTests.swift; sourceTree = "<group>"; };
|
||||
D6311C4F25B3765B00B27539 /* ImageDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataCache.swift; sourceTree = "<group>"; };
|
||||
D6333B362137838300CE884A /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = "<group>"; };
|
||||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
|
||||
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesNavigationController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1493,6 +1495,7 @@
|
|||
children = (
|
||||
D6F1F84C2193B56E00F5FE67 /* Cache.swift */,
|
||||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */,
|
||||
D6311C4F25B3765B00B27539 /* ImageDataCache.swift */,
|
||||
);
|
||||
path = Caching;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1889,6 +1892,7 @@
|
|||
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */,
|
||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */,
|
||||
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */,
|
||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
||||
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
||||
|
|
|
@ -29,7 +29,7 @@ class AccountActivityItemSource: NSObject, UIActivityItemSource {
|
|||
metadata.originalURL = account.url
|
||||
metadata.url = account.url
|
||||
metadata.title = "\(account.displayName) (@\(account.username)@\(account.url.host!)"
|
||||
if let data = ImageCache.avatars.get(account.avatar),
|
||||
if let data = ImageCache.avatars.getData(account.avatar),
|
||||
let image = UIImage(data: data) {
|
||||
metadata.iconProvider = NSItemProvider(object: image)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class StatusActivityItemSource: NSObject, UIActivityItemSource {
|
|||
let doc = try! SwiftSoup.parse(status.content)
|
||||
let content = try! doc.text()
|
||||
metadata.title = "\(status.account.displayName): \"\(content)\""
|
||||
if let data = ImageCache.avatars.get(status.account.avatar),
|
||||
if let data = ImageCache.avatars.getData(status.account.avatar),
|
||||
let image = UIImage(data: data) {
|
||||
metadata.iconProvider = NSItemProvider(object: image)
|
||||
}
|
||||
|
|
|
@ -22,47 +22,36 @@ class ImageCache {
|
|||
private static let disableCaching = false
|
||||
#endif
|
||||
|
||||
private let cache: Cache<Data>
|
||||
private let cache: ImageDataCache
|
||||
|
||||
private var groups = MultiThreadDictionary<URL, RequestGroup>(name: "ImageCache request groups")
|
||||
|
||||
private var backgroundQueue = DispatchQueue(label: "ImageCache completion queue", qos: .default)
|
||||
|
||||
init(name: String, memoryExpiry expiry: Expiry) {
|
||||
let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
|
||||
self.cache = .memory(storage)
|
||||
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry? = nil) {
|
||||
self.cache = ImageDataCache(name: name, memoryExpiry: memoryExpiry, diskExpiry: diskExpiry)
|
||||
}
|
||||
|
||||
init(name: String, diskExpiry expiry: Expiry) {
|
||||
let storage = try! DiskStorage<Data>(config: DiskConfig(name: name, expiry: expiry), transformer: TransformerFactory.forData())
|
||||
self.cache = .disk(storage)
|
||||
}
|
||||
|
||||
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry) {
|
||||
let memory = MemoryStorage<Data>(config: MemoryConfig(expiry: memoryExpiry))
|
||||
let disk = try! DiskStorage<Data>(config: DiskConfig(name: name, expiry: diskExpiry), transformer: TransformerFactory.forData())
|
||||
self.cache = .hybrid(HybridStorage(memoryStorage: memory, diskStorage: disk))
|
||||
}
|
||||
|
||||
func get(_ url: URL, completion: ((Data?) -> Void)?) -> Request? {
|
||||
func get(_ url: URL, completion: ((Data?, UIImage?) -> Void)?) -> Request? {
|
||||
let key = url.absoluteString
|
||||
if !ImageCache.disableCaching,
|
||||
// todo: calling object(forKey: key) does disk I/O and this method is often called from the main thread
|
||||
// in performance sensitive paths. a nice optimization to DiskStorage would be adding an internal cache
|
||||
// of the state (unknown/exists/does not exist) of whether or not objects exist on disk so that the slow, disk I/O
|
||||
// path can be avoided most of the time
|
||||
let data = try? cache.object(forKey: key) {
|
||||
let (data, image) = try? cache.get(key) {
|
||||
backgroundQueue.async {
|
||||
completion?(data)
|
||||
completion?(data, image)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
if let completion = completion, let group = groups[url] {
|
||||
return group.addCallback(completion)
|
||||
} else {
|
||||
let group = RequestGroup(url: url) { (data) in
|
||||
if let data = data {
|
||||
try? self.cache.setObject(data, forKey: key)
|
||||
let group = RequestGroup(url: url) { (data, image) in
|
||||
if let data = data,
|
||||
let image = UIImage(data: data) {
|
||||
try? self.cache.set(key, data: data, image: image)
|
||||
}
|
||||
self.groups.removeValueWithoutReturning(forKey: url)
|
||||
}
|
||||
|
@ -74,8 +63,12 @@ class ImageCache {
|
|||
}
|
||||
}
|
||||
|
||||
func get(_ url: URL) -> Data? {
|
||||
return try? cache.object(forKey: url.absoluteString)
|
||||
func getData(_ url: URL) -> Data? {
|
||||
return try? cache.getData(url.absoluteString)
|
||||
}
|
||||
|
||||
func get(_ url: URL) -> (Data, UIImage)? {
|
||||
return try? cache.get(url.absoluteString)
|
||||
}
|
||||
|
||||
func cancelWithoutCallback(_ url: URL) {
|
||||
|
@ -88,11 +81,11 @@ class ImageCache {
|
|||
|
||||
private class RequestGroup {
|
||||
let url: URL
|
||||
private let onFinished: (Data?) -> Void
|
||||
private let onFinished: (Data?, UIImage?) -> Void
|
||||
private var task: URLSessionDataTask?
|
||||
private var requests = [Request]()
|
||||
|
||||
init(url: URL, onFinished: @escaping (Data?) -> Void) {
|
||||
init(url: URL, onFinished: @escaping (Data?, UIImage?) -> Void) {
|
||||
self.url = url
|
||||
self.onFinished = onFinished
|
||||
}
|
||||
|
@ -116,7 +109,7 @@ class ImageCache {
|
|||
task?.priority = max(1.0, URLSessionTask.defaultPriority + 0.1 * Float(requests.filter { !$0.cancelled }.count))
|
||||
}
|
||||
|
||||
func addCallback(_ completion: ((Data?) -> Void)?) -> Request {
|
||||
func addCallback(_ completion: ((Data?, UIImage?) -> Void)?) -> Request {
|
||||
let request = Request(callback: completion)
|
||||
requests.append(request)
|
||||
updatePriority()
|
||||
|
@ -141,21 +134,24 @@ class ImageCache {
|
|||
}
|
||||
|
||||
func complete(with data: Data?) {
|
||||
let image = data != nil ? UIImage(data: data!) : nil
|
||||
|
||||
requests.filter { !$0.cancelled }.forEach {
|
||||
if let callback = $0.callback {
|
||||
callback(data)
|
||||
callback(data, image)
|
||||
}
|
||||
}
|
||||
self.onFinished(data)
|
||||
|
||||
self.onFinished(data, image)
|
||||
}
|
||||
}
|
||||
|
||||
class Request {
|
||||
private weak var group: RequestGroup?
|
||||
private(set) var callback: ((Data?) -> Void)?
|
||||
private(set) var callback: ((Data?, UIImage?) -> Void)?
|
||||
private(set) var cancelled: Bool = false
|
||||
|
||||
init(callback: ((Data?) -> Void)?) {
|
||||
init(callback: ((Data?, UIImage?) -> Void)?) {
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// ImageDataCache.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/16/21.
|
||||
// Copyright © 2021 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Cache
|
||||
|
||||
class ImageDataCache {
|
||||
|
||||
private let memory: MemoryStorage<(Data, UIImage)>
|
||||
private let disk: DiskStorage<Data>?
|
||||
|
||||
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry?) {
|
||||
let memoryConfig = MemoryConfig(expiry: memoryExpiry)
|
||||
self.memory = MemoryStorage(config: memoryConfig)
|
||||
|
||||
if let diskExpiry = diskExpiry {
|
||||
let diskConfig = DiskConfig(name: name, expiry: diskExpiry)
|
||||
self.disk = try! DiskStorage(config: diskConfig, transformer: TransformerFactory.forData())
|
||||
} else {
|
||||
self.disk = nil
|
||||
}
|
||||
}
|
||||
|
||||
func has(_ key: String) throws -> Bool {
|
||||
if try memory.existsObject(forKey: key) {
|
||||
return true
|
||||
} else if let disk = self.disk,
|
||||
try disk.existsObject(forKey: key) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func get(_ key: String) throws -> (Data, UIImage)? {
|
||||
if try memory.existsObject(forKey: key) {
|
||||
return try! memory.object(forKey: key)
|
||||
} else if let disk = self.disk,
|
||||
try disk.existsObject(forKey: key),
|
||||
let data = try? disk.object(forKey: key),
|
||||
let image = UIImage(data: data) {
|
||||
return (data, image)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getImage(_ key: String) throws -> UIImage? {
|
||||
return try get(key)?.1
|
||||
}
|
||||
|
||||
func getData(_ key: String) throws -> Data? {
|
||||
return try get(key)?.0
|
||||
}
|
||||
|
||||
func set(_ key: String, data: Data, image: UIImage) throws {
|
||||
memory.setObject((data, image), forKey: key)
|
||||
|
||||
if let disk = self.disk {
|
||||
try disk.setObject(data, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
func removeAll() throws {
|
||||
memory.removeAll()
|
||||
try? disk?.removeAll()
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,15 @@ struct ImageGrayscalifier {
|
|||
private static let context = CIContext()
|
||||
private static let cache = NSCache<NSURL, UIImage>()
|
||||
|
||||
static func convertIfNecessary(url: URL?, image: UIImage) -> UIImage? {
|
||||
if Preferences.shared.grayscaleImages,
|
||||
let source = image.cgImage {
|
||||
return convert(url: url, cgImage: source)
|
||||
} else {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
static func convert(url: URL?, data: Data) -> UIImage? {
|
||||
if let url = url,
|
||||
let cached = cache.object(forKey: url as NSURL) {
|
||||
|
|
|
@ -25,7 +25,7 @@ class AttachmentPreviewViewController: UIViewController {
|
|||
}
|
||||
|
||||
override func loadView() {
|
||||
if let data = ImageCache.attachments.get(attachment.url),
|
||||
if let data = ImageCache.attachments.getData(attachment.url),
|
||||
let image = UIImage(data: data) {
|
||||
let imageView: UIImageView
|
||||
if attachment.url.pathExtension == "gif" {
|
||||
|
|
|
@ -44,7 +44,7 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
var animationGifData: Data? {
|
||||
let attachment = attachments[currentIndex]
|
||||
if attachment.url.pathExtension == "gif" {
|
||||
return ImageCache.attachments.get(attachment.url)
|
||||
return ImageCache.attachments.getData(attachment.url)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -44,17 +44,11 @@ struct ComposeAvatarImageView: View {
|
|||
|
||||
private func loadImage() {
|
||||
guard let url = url else { return }
|
||||
request = ImageCache.avatars.get(url) { (data) in
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
request = ImageCache.avatars.get(url) { (_, image) in
|
||||
DispatchQueue.main.async {
|
||||
self.request = nil
|
||||
self.avatarImage = image
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.request = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,15 +45,14 @@ class EmojiCollectionViewCell: UICollectionViewCell {
|
|||
func updateUI(emoji: Emoji) {
|
||||
currentEmojiShortcode = emoji.shortcode
|
||||
|
||||
imageRequest = ImageCache.emojis.get(emoji.url) { [weak self] (data) in
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
imageRequest = ImageCache.emojis.get(emoji.url) { [weak self] (_, image) in
|
||||
guard let image = image else { return }
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self, self.currentEmojiShortcode == emoji.shortcode else { return }
|
||||
self.emojiImageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
|
|
@ -87,8 +87,8 @@ class FastSwitchingAccountView: UIView {
|
|||
let controller = MastodonController.getForAccount(account)
|
||||
controller.getOwnAccount { [weak self] (result) in
|
||||
guard let self = self, case let .success(account) = result else { return }
|
||||
self.avatarRequest = ImageCache.avatars.get(account.avatar) { [weak avatarImageView] (data) in
|
||||
guard let avatarImageView = avatarImageView, let data = data, let image = UIImage(data: data) else { return }
|
||||
self.avatarRequest = ImageCache.avatars.get(account.avatar) { [weak avatarImageView] (_, image) in
|
||||
guard let avatarImageView = avatarImageView, let image = image else { return }
|
||||
DispatchQueue.main.async {
|
||||
avatarImageView.image = image
|
||||
}
|
||||
|
|
|
@ -85,19 +85,19 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
|||
overrideUserInterfaceStyle = .dark
|
||||
view.backgroundColor = .black
|
||||
|
||||
if let data = cache.get(url) {
|
||||
createLargeImage(data: data, url: url)
|
||||
if let (data, image) = cache.get(url) {
|
||||
createLargeImage(data: data, image: image, url: url)
|
||||
} else {
|
||||
createPreview()
|
||||
|
||||
loadingVC = LoadingViewController()
|
||||
embedChild(loadingVC!)
|
||||
imageRequest = cache.get(url) { [weak self] (data) in
|
||||
imageRequest = cache.get(url) { [weak self] (data, image) in
|
||||
guard let self = self else { return }
|
||||
self.imageRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.loadingVC?.removeViewAndController()
|
||||
self.createLargeImage(data: data!, url: self.url)
|
||||
self.createLargeImage(data: data!, image: image!, url: self.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,20 +115,13 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
|||
}
|
||||
}
|
||||
|
||||
private func createLargeImage(data: Data, url: URL) {
|
||||
private func createLargeImage(data: Data, image: UIImage, url: URL) {
|
||||
guard !loaded else { return }
|
||||
loaded = true
|
||||
|
||||
let image: UIImage?
|
||||
if Preferences.shared.grayscaleImages {
|
||||
image = ImageGrayscalifier.convert(url: url, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
if let transformedImage = ImageGrayscalifier.convertIfNecessary(url: url, image: image) {
|
||||
let gifData = url.pathExtension == "gif" ? data : nil
|
||||
createLargeImage(image: image, gifData: gifData)
|
||||
createLargeImage(image: transformedImage, gifData: gifData)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,15 +38,13 @@ struct LocalAccountAvatarView: View {
|
|||
let controller = MastodonController.getForAccount(localAccountInfo)
|
||||
controller.getOwnAccount { (result) in
|
||||
guard case let .success(account) = result else { return }
|
||||
_ = ImageCache.avatars.get(account.avatar) { (data) in
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
_ = ImageCache.avatars.get(account.avatar) { (_, image) in
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//struct LocalAccountAvatarView_Previews: PreviewProvider {
|
||||
|
|
|
@ -43,17 +43,10 @@ class MyProfileViewController: ProfileViewController {
|
|||
|
||||
private func setAvatarTabBarImage<Account: AccountProtocol>(account: Account) {
|
||||
let avatarURL = account.avatar
|
||||
_ = ImageCache.avatars.get(avatarURL, completion: { [weak self] (data) in
|
||||
guard let self = self, let data = data else { return }
|
||||
|
||||
let maybeGrayscale: UIImage?
|
||||
if Preferences.shared.grayscaleImages {
|
||||
maybeGrayscale = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
maybeGrayscale = UIImage(data: data)
|
||||
}
|
||||
|
||||
guard let image = maybeGrayscale else {
|
||||
_ = ImageCache.avatars.get(avatarURL, completion: { [weak self] (_, image) in
|
||||
guard let self = self,
|
||||
let image = image,
|
||||
let maybeGrayscale = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -63,7 +56,7 @@ class MyProfileViewController: ProfileViewController {
|
|||
let tabBarImage = UIGraphicsImageRenderer(size: size).image { (_) in
|
||||
let radius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
UIBezierPath(roundedRect: rect, cornerRadius: radius).addClip()
|
||||
image.draw(in: rect)
|
||||
maybeGrayscale.draw(in: rect)
|
||||
}
|
||||
let alwaysOriginalImage = tabBarImage.withRenderingMode(.alwaysOriginal)
|
||||
self.tabBarItem.image = alwaysOriginalImage
|
||||
|
|
|
@ -63,19 +63,16 @@ class AccountTableViewCell: UITableViewCell {
|
|||
let accountID = self.accountID
|
||||
|
||||
let avatarURL = account.avatar
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == accountID else { return }
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self else { return }
|
||||
self.avatarRequest = nil
|
||||
|
||||
let image: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
guard let image = image,
|
||||
self.accountID == accountID,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
self.avatarImageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,11 +69,11 @@ class LargeAccountDetailView: UIView {
|
|||
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||
guard let self = self, let data = data else { return }
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in
|
||||
guard let self = self, let image = image else { return }
|
||||
self.avatarRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
self.avatarImageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,9 +54,9 @@ struct AccountDisplayNameLabel<Account: AccountProtocol>: View {
|
|||
}
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.get(emoji.url) { (data) in
|
||||
let request = ImageCache.emojis.get(emoji.url) { (_, image) in
|
||||
defer { group.leave() }
|
||||
guard let data = data, let image = UIImage(data: data) else { return }
|
||||
guard let image = image else { return }
|
||||
|
||||
let size = CGSize(width: fontSize, height: fontSize)
|
||||
let renderer = UIGraphicsImageRenderer(size: size)
|
||||
|
|
|
@ -159,7 +159,7 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
|
||||
func loadImage() {
|
||||
let attachmentURL = attachment.url
|
||||
attachmentRequest = ImageCache.attachments.get(attachmentURL) { [weak self] (data) in
|
||||
attachmentRequest = ImageCache.attachments.get(attachmentURL) { [weak self] (data, _) in
|
||||
guard let self = self, let data = data else { return }
|
||||
self.attachmentRequest = nil
|
||||
if self.attachment.url.pathExtension == "gif" {
|
||||
|
|
|
@ -43,20 +43,13 @@ extension BaseEmojiLabel {
|
|||
foundEmojis = true
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.get(emoji.url) { (data) in
|
||||
let request = ImageCache.emojis.get(emoji.url) { (_, image) in
|
||||
defer { group.leave() }
|
||||
guard let data = data else {
|
||||
guard let image = image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: image) else {
|
||||
return
|
||||
}
|
||||
let image: UIImage?
|
||||
if Preferences.shared.grayscaleImages {
|
||||
image = ImageGrayscalifier.convert(url: emoji.url, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
if let image = image {
|
||||
emojiImages[emoji.shortcode] = image
|
||||
}
|
||||
emojiImages[emoji.shortcode] = transformedImage
|
||||
}
|
||||
if let request = request {
|
||||
emojiRequests.append(request)
|
||||
|
|
|
@ -63,20 +63,13 @@ class ContentTextView: LinkTextView {
|
|||
|
||||
for emoji in emojis {
|
||||
group.enter()
|
||||
_ = ImageCache.emojis.get(emoji.url) { (data) in
|
||||
_ = ImageCache.emojis.get(emoji.url) { (_, image) in
|
||||
defer { group.leave() }
|
||||
guard let data = data else {
|
||||
guard let image = image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: image) else {
|
||||
return
|
||||
}
|
||||
let image: UIImage?
|
||||
if Preferences.shared.grayscaleImages {
|
||||
image = ImageGrayscalifier.convert(url: emoji.url, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
if let image = image {
|
||||
emojiImages[emoji.shortcode] = image
|
||||
}
|
||||
emojiImages[emoji.shortcode] = transformedImage
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,16 +33,12 @@ struct CustomEmojiImageView: View {
|
|||
}
|
||||
|
||||
private func loadImage() {
|
||||
request = ImageCache.emojis.get(emoji.url) { (data) in
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
request = ImageCache.emojis.get(emoji.url) { (_, image) in
|
||||
DispatchQueue.main.async {
|
||||
self.request = nil
|
||||
if let image = image {
|
||||
self.image = image
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.request = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,8 +60,8 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
private func updateThumbnail(url: URL) {
|
||||
thumbnailImageView.image = nil
|
||||
thumbnailURL = url
|
||||
thumbnailRequest = ImageCache.attachments.get(url) { [weak self] (data) in
|
||||
guard let self = self, self.thumbnailURL == url, let data = data, let image = UIImage(data: data) else { return }
|
||||
thumbnailRequest = ImageCache.attachments.get(url) { [weak self] (_, image) in
|
||||
guard let self = self, self.thumbnailURL == url, let image = image else { return }
|
||||
self.thumbnailRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.thumbnailImageView.image = image
|
||||
|
|
|
@ -84,21 +84,20 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
let avatarURL = account.avatar
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.group.id == group.id else { return }
|
||||
|
||||
let image: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self else { return }
|
||||
guard let image = image,
|
||||
self.group.id == group.id,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else {
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = image
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
actionAvatarStackView.addArrangedSubview(imageView)
|
||||
|
@ -133,21 +132,20 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
let avatarURL = account.avatar
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.group.id == groupID else { return }
|
||||
|
||||
let image: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self else { return }
|
||||
guard let image = image,
|
||||
self.group.id == groupID,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else {
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = image
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,21 +65,17 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
let avatarURL = account.avatar
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.group.id == group.id else { return }
|
||||
|
||||
let image: UIImage?
|
||||
if Preferences.shared.grayscaleImages {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self,
|
||||
let image = image,
|
||||
self.group.id == group.id,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = image
|
||||
}
|
||||
imageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
avatarStackView.addArrangedSubview(imageView)
|
||||
|
@ -103,21 +99,20 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
let avatarURL = account.avatar
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.group.id == groupID else { return }
|
||||
|
||||
let image: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self else { return }
|
||||
guard let image = image,
|
||||
self.group.id == groupID,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else {
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = image
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarRequests.removeValue(forKey: account.id)
|
||||
imageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,21 +68,18 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
|||
actionLabel.setEmojis(account.emojis, identifier: account.id)
|
||||
}
|
||||
let avatarURL = account.avatar
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, self.account == account, let data = data else { return }
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self else { return }
|
||||
self.avatarRequest = nil
|
||||
|
||||
let image: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
guard self.account == account,
|
||||
let image = image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
}
|
||||
self.avatarImageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,35 +191,35 @@ class ProfileHeaderView: UIView {
|
|||
|
||||
let accountID = account.id
|
||||
let avatarURL = account.avatar
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == accountID else { return }
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self, let image = image, self.accountID == accountID else { return }
|
||||
self.avatarRequest = nil
|
||||
|
||||
let image: UIImage?
|
||||
let transformedImage: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
transformedImage = ImageGrayscalifier.convert(url: avatarURL, cgImage: image.cgImage!)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
transformedImage = image
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
self.avatarImageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
if let header = account.header {
|
||||
headerRequest = ImageCache.headers.get(header) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == accountID else { return }
|
||||
headerRequest = ImageCache.headers.get(header) { [weak self] (_, image) in
|
||||
guard let self = self, let image = image, self.accountID == accountID else { return }
|
||||
self.headerRequest = nil
|
||||
|
||||
let image: UIImage?
|
||||
let transformedImage: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: header, data: data)
|
||||
transformedImage = ImageGrayscalifier.convert(url: header, cgImage: image.cgImage!)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
transformedImage = image
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.headerImageView.image = image
|
||||
self.headerImageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -260,18 +260,14 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
|
||||
let avatarURL = account.avatar
|
||||
let accountID = account.id
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (data) in
|
||||
guard let self = self, let data = data, self.accountID == accountID else { return }
|
||||
|
||||
let image: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: avatarURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
}
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self,
|
||||
let image = image,
|
||||
self.accountID == accountID,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
self.avatarImageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -141,18 +141,13 @@ class StatusCardView: UIView {
|
|||
if let imageURL = card.image {
|
||||
placeholderImageView.isHidden = true
|
||||
|
||||
imageRequest = ImageCache.attachments.get(imageURL, completion: { (data) in
|
||||
guard let data = data else { return }
|
||||
let image: UIImage?
|
||||
if self.isGrayscale {
|
||||
image = ImageGrayscalifier.convert(url: imageURL, data: data)
|
||||
} else {
|
||||
image = UIImage(data: data)
|
||||
imageRequest = ImageCache.attachments.get(imageURL, completion: { (_, image) in
|
||||
guard let image = image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: imageURL, image: image) else {
|
||||
return
|
||||
}
|
||||
if let image = image {
|
||||
DispatchQueue.main.async {
|
||||
self.imageView.image = image
|
||||
}
|
||||
self.imageView.image = transformedImage
|
||||
}
|
||||
})
|
||||
if imageRequest != nil {
|
||||
|
|
Loading…
Reference in New Issue