Browse Source

Use Gifu for GIF playback

swiftui-preferences
Shadowfacts 1 year ago
parent
commit
f684591888
Signed by: Shadowfacts <me@shadowfacts.net> GPG Key ID: 94A5AB95422746E5

+ 3
- 0
.gitmodules View File

@@ -4,3 +4,6 @@
4 4
 [submodule "Cache"]
5 5
 	path = Cache
6 6
 	url = git@github.com:hyperoslo/Cache.git
7
+[submodule "Gifu"]
8
+	path = Gifu
9
+	url = git://github.com/kaishin/Gifu.git

+ 1
- 0
Gifu

@@ -0,0 +1 @@
1
+Subproject commit ed572f53ce58b8e23499abeb3a926033cbe480f7

+ 6
- 0
Tusker.xcodeproj/project.pbxproj View File

@@ -115,6 +115,8 @@
115 115
 		D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
116 116
 		D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; };
117 117
 		D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
118
+		D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; };
119
+		D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
118 120
 		D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
119 121
 		D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
120 122
 		D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */; };
@@ -194,6 +196,7 @@
194 196
 				04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */,
195 197
 				D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
196 198
 				D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
199
+				D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */,
197 200
 				0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */,
198 201
 			);
199 202
 			name = "Embed Frameworks";
@@ -309,6 +312,7 @@
309 312
 		D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
310 313
 		D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = "<group>"; };
311 314
 		D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
315
+		D6BC874421961F73006163F1 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; };
312 316
 		D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
313 317
 		D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
314 318
 		D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SilentActionPermissionsTableViewController.swift; sourceTree = "<group>"; };
@@ -369,6 +373,7 @@
369 373
 				04496BCF216252E5001F1B23 /* TTTAttributedLabel.framework in Frameworks */,
370 374
 				D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
371 375
 				D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
376
+				D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
372 377
 				0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
373 378
 			);
374 379
 			runOnlyForDeploymentPostprocessing = 0;
@@ -742,6 +747,7 @@
742 747
 		D6D4DDC3212518A000E1C4BB = {
743 748
 			isa = PBXGroup;
744 749
 			children = (
750
+				D6BC874421961F73006163F1 /* Gifu.framework */,
745 751
 				0461A38F2163CBAE00C0A807 /* Cache.framework */,
746 752
 				D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
747 753
 				D61099AC2144B0CC00432DC2 /* Pachyderm */,

+ 3
- 0
Tusker.xcworkspace/contents.xcworkspacedata View File

@@ -10,4 +10,7 @@
10 10
    <FileRef
11 11
       location = "group:SwiftSoup/SwiftSoup.xcodeproj">
12 12
    </FileRef>
13
+   <FileRef
14
+      location = "group:Gifu/Gifu.xcodeproj">
15
+   </FileRef>
13 16
 </Workspace>

+ 7
- 0
Tusker/AppRouter.swift View File

@@ -91,6 +91,13 @@ class AppRouter {
91 91
         return vc
92 92
     }
93 93
     
94
+    func largeImage(gifData: Data, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, transitioningDelegate: UIViewControllerTransitioningDelegate?) -> LargeImageViewController {
95
+        let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, router: self)
96
+        vc.gifData = gifData
97
+        vc.transitioningDelegate = transitioningDelegate
98
+        return vc
99
+    }
100
+    
94 101
     func moreOptions(forStatus statusID: String) -> UIAlertController {
95 102
         guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
96 103
         

+ 19
- 19
Tusker/Caching/ImageCache.swift View File

@@ -15,45 +15,45 @@ class ImageCache {
15 15
     static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
16 16
     static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
17 17
 
18
-    let cache: Cache<UIImage>
18
+    let cache: Cache<Data>
19 19
     
20 20
     var requests = [URL: Request]()
21 21
     
22 22
     init(name: String, memoryExpiry expiry: Expiry) {
23
-        let storage = MemoryStorage<UIImage>(config: MemoryConfig(expiry: expiry))
23
+        let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
24 24
         self.cache = .memory(storage)
25 25
     }
26 26
     
27 27
     init(name: String, diskExpiry expiry: Expiry) {
28
-        let storage = try! DiskStorage<UIImage>(config: DiskConfig(name: name, expiry: expiry), transformer: TransformerFactory.forImage())
28
+        let storage = try! DiskStorage<Data>(config: DiskConfig(name: name, expiry: expiry), transformer: TransformerFactory.forData())
29 29
         self.cache = .disk(storage)
30 30
     }
31 31
     
32 32
     init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry) {
33
-        let memory = MemoryStorage<UIImage>(config: MemoryConfig(expiry: memoryExpiry))
34
-        let disk = try! DiskStorage<UIImage>(config: DiskConfig(name: name, expiry: diskExpiry), transformer: TransformerFactory.forImage())
33
+        let memory = MemoryStorage<Data>(config: MemoryConfig(expiry: memoryExpiry))
34
+        let disk = try! DiskStorage<Data>(config: DiskConfig(name: name, expiry: diskExpiry), transformer: TransformerFactory.forData())
35 35
         self.cache = .hybrid(HybridStorage(memoryStorage: memory, diskStorage: disk))
36 36
     }
37 37
     
38
-    func get(_ url: URL, completion: ((UIImage?) -> Void)?) {
38
+    func get(_ url: URL, completion: ((Data?) -> Void)?) {
39 39
         let key = url.absoluteString
40 40
         if (try? cache.existsObject(forKey: key)) ?? false,
41
-            let image = try? cache.object(forKey: key) {
42
-            completion?(image)
41
+            let data = try? cache.object(forKey: key) {
42
+            completion?(data)
43 43
         } else {
44 44
             if let completion = completion, let request = requests[url] {
45 45
                 request.callbacks.append(completion)
46 46
             } else {
47 47
                 let request = Request(url: url, completion: completion)
48 48
                 requests[url] = request
49
-                request.run { (image) in
50
-                    try? self.cache.setObject(image, forKey: key)
49
+                request.run { (data) in
50
+                    try? self.cache.setObject(data, forKey: key)
51 51
                 }
52 52
             }
53 53
         }
54 54
     }
55 55
     
56
-    func get(_ url: URL) -> UIImage? {
56
+    func get(_ url: URL) -> Data? {
57 57
         return try? cache.object(forKey: url.absoluteString)
58 58
     }
59 59
     
@@ -64,9 +64,9 @@ class ImageCache {
64 64
     class Request {
65 65
         let url: URL
66 66
         var task: URLSessionDataTask?
67
-        var callbacks: [(UIImage?) -> Void]
67
+        var callbacks: [(Data?) -> Void]
68 68
         
69
-        init(url: URL, completion: ((UIImage?) -> Void)?) {
69
+        init(url: URL, completion: ((Data?) -> Void)?) {
70 70
             if let completion = completion {
71 71
                 self.callbacks = [completion]
72 72
             } else {
@@ -75,14 +75,14 @@ class ImageCache {
75 75
             self.url = url
76 76
         }
77 77
         
78
-        func run(cache: @escaping (UIImage) -> Void) {
78
+        func run(cache: @escaping (Data) -> Void) {
79 79
             task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
80
-                guard error == nil, let data = data, let image = UIImage(data: data) else {
80
+                guard error == nil, let data = data else {
81 81
                     self.complete(with: nil)
82 82
                     return
83 83
                 }
84
-                cache(image)
85
-                self.complete(with: image)
84
+                cache(data)
85
+                self.complete(with: data)
86 86
             })
87 87
             task!.resume()
88 88
         }
@@ -92,8 +92,8 @@ class ImageCache {
92 92
             complete(with: nil)
93 93
         }
94 94
         
95
-        func complete(with image: UIImage?) {
96
-            callbacks.forEach { $0(image) }
95
+        func complete(with data: Data?) {
96
+            callbacks.forEach { $0(data) }
97 97
         }
98 98
     }
99 99
     

+ 3
- 2
Tusker/Screens/Compose/ComposeViewController.swift View File

@@ -98,9 +98,10 @@ class ComposeViewController: UIViewController {
98 98
             inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
99 99
             inReplyToAvatarImageView.layer.masksToBounds = true
100 100
             inReplyToAvatarImageView.image = nil
101
-            ImageCache.avatars.get(inReplyTo.account.avatar) { (image) in
101
+            ImageCache.avatars.get(inReplyTo.account.avatar) { (data) in
102
+                guard let data = data else { return }
102 103
                 DispatchQueue.main.async {
103
-                    self.inReplyToAvatarImageView.image = image
104
+                    self.inReplyToAvatarImageView.image = UIImage(data: data)
104 105
                 }
105 106
             }
106 107
             inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"

+ 18
- 2
Tusker/Screens/Large Image/LargeImageViewController.swift View File

@@ -8,6 +8,7 @@
8 8
 
9 9
 import UIKit
10 10
 import Photos
11
+import Gifu
11 12
 
12 13
 class LargeImageViewController: UIViewController, UIScrollViewDelegate {
13 14
 
@@ -18,7 +19,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
18 19
     var dismissInteractionController: LargeImageInteractionController?
19 20
     
20 21
     @IBOutlet weak var scrollView: UIScrollView!
21
-    @IBOutlet weak var imageView: UIImageView!
22
+    @IBOutlet weak var imageView: GIFImageView!
22 23
     @IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
23 24
     @IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!
24 25
     @IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
@@ -39,6 +40,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
39 40
     var initializedTopControlsConstrains = false
40 41
     
41 42
     var image: UIImage?
43
+    var gifData: Data?
42 44
     var imageDescription: String?
43 45
     
44 46
     var controlsVisible = true {
@@ -70,6 +72,16 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
70 72
         super.init(nibName: "LargeImageViewController", bundle: nil)
71 73
     }
72 74
     
75
+//    init(gifData: Data?, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, router: AppRouter) {
76
+//        self.router = router
77
+//        self.gifData = gifData
78
+//        self.imageDescription = description
79
+//        self.originFrame = sourceFrame
80
+//        self.originCornerRadius = sourceCornerRadius
81
+//        
82
+//        super.init(nibName: "LargeImageViewController", bundle: nil)
83
+//    }
84
+    
73 85
     required init?(coder aDecoder: NSCoder) {
74 86
         fatalError("init(coder:) has not been implemented")
75 87
     }
@@ -78,8 +90,12 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
78 90
         super.viewDidLoad()
79 91
 
80 92
         imageView.image = image
93
+        if let gifData = gifData {
94
+            imageView.animate(withGIFData: gifData)
95
+        }
96
+        
81 97
         scrollView.delegate = self
82
-        imageView.bounds = CGRect(origin: .zero, size: image!.size)
98
+        imageView.bounds = CGRect(origin: .zero, size: imageView.image!.size)
83 99
         
84 100
         if let imageDescription = imageDescription {
85 101
             descriptionLabel.text = imageDescription

+ 3
- 3
Tusker/Screens/Large Image/LargeImageViewController.xib View File

@@ -1,10 +1,10 @@
1 1
 <?xml version="1.0" encoding="UTF-8"?>
2
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3 3
     <device id="retina4_7" orientation="portrait">
4 4
         <adaptation id="fullscreen"/>
5 5
     </device>
6 6
     <dependencies>
7
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
7
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
8 8
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
9 9
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10 10
     </dependencies>
@@ -38,7 +38,7 @@
38 38
                 <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" minimumZoomScale="0.25" maximumZoomScale="2" translatesAutoresizingMaskIntoConstraints="NO" id="Skj-xq-AgQ">
39 39
                     <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
40 40
                     <subviews>
41
-                        <imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qcn-1t-3sS">
41
+                        <imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qcn-1t-3sS" customClass="GIFImageView" customModule="Gifu">
42 42
                             <rect key="frame" x="0.0" y="-10" width="375" height="647"/>
43 43
                             <gestureRecognizers/>
44 44
                         </imageView>

+ 7
- 3
Tusker/Screens/Large Image/Transitions/LargeImageExpandAnimationController.swift View File

@@ -7,6 +7,7 @@
7 7
 //
8 8
 
9 9
 import UIKit
10
+import Gifu
10 11
 
11 12
 class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
12 13
     
@@ -23,14 +24,17 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
23 24
         
24 25
         let containerView = transitionContext.containerView
25 26
         let finalVCFrame = transitionContext.finalFrame(for: toVC)
26
-        let image = toVC.image!
27
+        let image = toVC.imageView.image!
27 28
         let ratio = image.size.width / image.size.height
28 29
         let width = finalVCFrame.width
29 30
         let height = width / ratio
30 31
         let finalFrame = CGRect(x: finalVCFrame.midX - width / 2, y: finalVCFrame.midY - height / 2, width: width, height: height)
31 32
         
32
-        let imageView = UIImageView(frame: originFrame)
33
-        imageView.image = toVC.image!
33
+        let imageView = GIFImageView(frame: originFrame)
34
+        imageView.image = toVC.imageView.image!
35
+        if let gifData = toVC.gifData {
36
+            imageView.animate(withGIFData: gifData)
37
+        }
34 38
         imageView.contentMode = .scaleAspectFill
35 39
         imageView.layer.cornerRadius = toVC.originCornerRadius!
36 40
         imageView.layer.masksToBounds = true

+ 5
- 1
Tusker/Screens/Large Image/Transitions/LargeImageShrinkAnimationController.swift View File

@@ -7,6 +7,7 @@
7 7
 //
8 8
 
9 9
 import UIKit
10
+import Gifu
10 11
 
11 12
 class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
12 13
     
@@ -36,8 +37,11 @@ class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTra
36 37
         let height = width / ratio
37 38
         let originalFrame = CGRect(x: originalVCFrame.midX - width / 2, y: originalVCFrame.midY - height / 2, width: width, height: height)
38 39
         
39
-        let imageView = UIImageView(frame: originalFrame)
40
+        let imageView = GIFImageView(frame: originalFrame)
40 41
         imageView.image = fromVC.image!
42
+        if let gifData = fromVC.gifData {
43
+            imageView.animate(withGIFData: gifData)
44
+        }
41 45
         imageView.contentMode = .scaleAspectFill
42 46
         imageView.layer.cornerRadius = 0
43 47
         imageView.layer.masksToBounds = true

+ 22
- 0
Tusker/TuskerNavigationDelegate.swift View File

@@ -30,8 +30,12 @@ protocol TuskerNavigationDelegate {
30 30
     
31 31
     func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController
32 32
     
33
+    func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController
34
+    
33 35
     func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView)
34 36
     
37
+    func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView)
38
+    
35 39
     func showMoreOptions(forStatus statusID: String)
36 40
     
37 41
     func showMoreOptions(forURL url: URL)
@@ -95,10 +99,28 @@ extension TuskerNavigationDelegate where Self: UIViewController {
95 99
         return router.largeImage(image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, transitioningDelegate: self)
96 100
     }
97 101
     
102
+    func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController {
103
+        var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
104
+        if let scrollView = view as? UIScrollView {
105
+            let scale = scrollView.zoomScale
106
+            let width = sourceFrame.width * scale
107
+            let height = sourceFrame.height * scale
108
+            let x = sourceFrame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX
109
+            let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
110
+            sourceFrame = CGRect(x: x, y: y, width: width, height: height)
111
+        }
112
+        let sourceCornerRadius = sourceView.layer.cornerRadius
113
+        return router.largeImage(gifData: gifData, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, transitioningDelegate: self)
114
+    }
115
+    
98 116
     func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView) {
99 117
         router.present(largeImage(image, description: description, sourceView: sourceView), animated: true)
100 118
     }
101 119
     
120
+    func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView) {
121
+        router.present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
122
+    }
123
+    
102 124
     func showMoreOptions(forStatus statusID: String) {
103 125
         router.present(router.moreOptions(forStatus: statusID), animated: true)
104 126
     }

+ 17
- 3
Tusker/Views/AttachmentView.swift View File

@@ -8,16 +8,20 @@
8 8
 
9 9
 import UIKit
10 10
 import Pachyderm
11
+import Gifu
11 12
 
12 13
 protocol AttachmentViewDelegate {
13 14
     func showLargeAttachment(for attachmentView: AttachmentView)
14 15
 }
15 16
 
16
-class AttachmentView: UIImageView {
17
+class AttachmentView: UIImageView, GIFAnimatable {
17 18
     
18 19
     var delegate: AttachmentViewDelegate?
19 20
     
20 21
     var attachment: Attachment!
22
+    var gifData: Data?
23
+    
24
+    public lazy var animator: Animator? = Animator(withDelegate: self)
21 25
     
22 26
     required init?(coder aDecoder: NSCoder) {
23 27
         super.init(coder: aDecoder)
@@ -43,13 +47,23 @@ class AttachmentView: UIImageView {
43 47
     }
44 48
     
45 49
     func loadImage() {
46
-        ImageCache.attachments.get(attachment.url) { (image) in
50
+        ImageCache.attachments.get(attachment.url) { (data) in
51
+            guard let data = data else { return }
47 52
             DispatchQueue.main.async {
48
-                self.image = image
53
+                if self.attachment.url.pathExtension == "gif" {
54
+                    self.animate(withGIFData: data)
55
+                    self.gifData = data
56
+                } else {
57
+                    self.image = UIImage(data: data)
58
+                }
49 59
             }
50 60
         }
51 61
     }
52 62
     
63
+    override func display(_ layer: CALayer) {
64
+        updateImageIfNeeded()
65
+    }
66
+    
53 67
     @objc func imagePressed() {
54 68
         if image != nil {
55 69
             delegate?.showLargeAttachment(for: self)

+ 6
- 4
Tusker/Views/Notifications/ActionNotificationTableViewCell.swift View File

@@ -79,17 +79,19 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
79 79
         usernameLabel.text = "@\(status.account.acct)"
80 80
         opAvatarImageView.image = nil
81 81
         opAvatarURL = status.account.avatar
82
-        ImageCache.avatars.get(status.account.avatar) { (image) in
82
+        ImageCache.avatars.get(status.account.avatar) { (data) in
83
+            guard let data = data else { return }
83 84
             DispatchQueue.main.async {
84
-                self.opAvatarImageView.image = image
85
+                self.opAvatarImageView.image = UIImage(data: data)
85 86
                 self.opAvatarURL = nil
86 87
             }
87 88
         }
88 89
         actionAvatarImageView.image = nil
89 90
         actionAvatarURL = notification.account.avatar
90
-        ImageCache.avatars.get(notification.account.avatar) { (image) in
91
+        ImageCache.avatars.get(notification.account.avatar) { (data) in
92
+            guard let data = data else { return }
91 93
             DispatchQueue.main.async {
92
-                self.actionAvatarImageView.image = image
94
+                self.actionAvatarImageView.image = UIImage(data: data)
93 95
                 self.actionAvatarURL = nil
94 96
             }
95 97
         }

+ 3
- 2
Tusker/Views/Notifications/FollowNotificationTableViewCell.swift View File

@@ -51,9 +51,10 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
51 51
         usernameLabel.text = "@\(account.acct)"
52 52
         avatarImageView.image = nil
53 53
         avatarURL = account.avatar
54
-        ImageCache.avatars.get(account.avatar) { (image) in
54
+        ImageCache.avatars.get(account.avatar) { (data) in
55
+            guard let data = data else { return }
55 56
             DispatchQueue.main.async {
56
-                self.avatarImageView.image = image
57
+                self.avatarImageView.image = UIImage(data: data)
57 58
                 self.avatarURL = nil
58 59
             }
59 60
         }

+ 6
- 4
Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift View File

@@ -61,15 +61,17 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
61 61
         
62 62
         avatarImageView.image = nil
63 63
         avatarURL = account.avatar
64
-        ImageCache.avatars.get(account.avatar) { (image) in
64
+        ImageCache.avatars.get(account.avatar) { (data) in
65
+            guard let data = data else { return }
65 66
             DispatchQueue.main.async {
66
-                self.avatarImageView.image = image
67
+                self.avatarImageView.image = UIImage(data: data)
67 68
                 self.avatarURL = nil
68 69
             }
69 70
         }
70
-        ImageCache.headers.get(account.header) { (image) in
71
+        ImageCache.headers.get(account.header) { (data) in
72
+            guard let data = data else { return }
71 73
             DispatchQueue.main.async {
72
-                self.headerImageView.image = image
74
+                self.headerImageView.image = UIImage(data: data)
73 75
                 self.headerURL = nil
74 76
             }
75 77
         }

+ 8
- 3
Tusker/Views/Status/ConversationMainStatusTableViewCell.swift View File

@@ -80,9 +80,10 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
80 80
         usernameLabel.text = "@\(account.acct)"
81 81
         avatarImageView.image = nil
82 82
         avatarURL = account.avatar
83
-        ImageCache.avatars.get(account.avatar) { (image) in
83
+        ImageCache.avatars.get(account.avatar) { (data) in
84
+            guard let data = data else { return }
84 85
             DispatchQueue.main.async {
85
-                self.avatarImageView.image = image
86
+                self.avatarImageView.image = UIImage(data: data)
86 87
                 self.avatarURL = nil
87 88
             }
88 89
         }
@@ -221,7 +222,11 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
221 222
 
222 223
 extension ConversationMainStatusTableViewCell: AttachmentViewDelegate {
223 224
     func showLargeAttachment(for attachmentView: AttachmentView) {
224
-        delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
225
+        if let gifData = attachmentView.gifData {
226
+            delegate?.showLargeImage(gifData: gifData, description: attachmentView.attachment.description, animatingFrom: attachmentView)
227
+        } else {
228
+            delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
229
+        }
225 230
     }
226 231
 }
227 232
 

+ 8
- 3
Tusker/Views/Status/StatusTableViewCell.swift View File

@@ -97,9 +97,10 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
97 97
         usernameLabel.text = "@\(account.acct)"
98 98
         avatarImageView.image = nil
99 99
         avatarURL = account.avatar
100
-        ImageCache.avatars.get(account.avatar) { (image) in
100
+        ImageCache.avatars.get(account.avatar) { (data) in
101
+            guard let data = data else { return }
101 102
             DispatchQueue.main.async {
102
-                self.avatarImageView.image = image
103
+                self.avatarImageView.image = UIImage(data: data)
103 104
                 self.avatarURL = nil
104 105
             }
105 106
         }
@@ -352,7 +353,11 @@ extension StatusTableViewCell: TableViewSwipeActionProvider {
352 353
 
353 354
 extension StatusTableViewCell: AttachmentViewDelegate {
354 355
     func showLargeAttachment(for attachmentView: AttachmentView) {
355
-        delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
356
+        if let gifData = attachmentView.gifData {
357
+            delegate?.showLargeImage(gifData: gifData, description: attachmentView.attachment.description, animatingFrom: attachmentView)
358
+        } else {
359
+            delegate?.showLargeImage(attachmentView.image!, description: attachmentView.attachment.description, animatingFrom: attachmentView)
360
+        }
356 361
     }
357 362
 }
358 363
 

Loading…
Cancel
Save