@@ -4,15 +4,29 @@ import Shared
4
4
import UIKit
5
5
import UserNotifications
6
6
import UserNotificationsUI
7
+ import WebKit
7
8
8
9
class ImageAttachmentViewController : UIViewController , NotificationCategory {
9
10
let attachmentURL : URL
10
11
let needsEndSecurityScoped : Bool
11
12
let image : UIImage
12
- let imageView = with ( UIImageView ( ) ) {
13
- $0. contentMode = . scaleAspectFit
13
+ let imageData : Data
14
+ let imageUTI : CFString
15
+
16
+ enum ImageViewType {
17
+ case imageView( UIImageView )
18
+ case webView( WKWebView )
19
+
20
+ var view : UIView {
21
+ switch self {
22
+ case let . imageView( imageView) : return imageView
23
+ case let . webView( webView) : return webView
24
+ }
25
+ }
14
26
}
15
27
28
+ let visibleView : ImageViewType
29
+
16
30
required init ( notification: UNNotification , attachmentURL: URL ? ) throws {
17
31
guard let attachmentURL = attachmentURL else {
18
32
throw ImageAttachmentError . noAttachment
@@ -25,12 +39,55 @@ class ImageAttachmentViewController: UIViewController, NotificationCategory {
25
39
// has the full list of what is advertised - at time of writing (iOS 14.5) it's jpeg, gif and png
26
40
// but iOS 14 also supports webp, so who knows if it'll be added silently or not
27
41
28
- guard let image = UIImage ( contentsOfFile: attachmentURL. path) else {
42
+ do {
43
+ let data = try Data ( contentsOf: attachmentURL, options: . alwaysMapped)
44
+ guard let image = UIImage ( data: data) else {
45
+ throw ImageAttachmentError . imageDecodeFailure
46
+ }
47
+ self . image = image
48
+ self . imageData = data
49
+
50
+ if let imageSource = CGImageSourceCreateWithData ( data as CFData , nil ) ,
51
+ let uti = CGImageSourceGetType ( imageSource) {
52
+ self . imageUTI = uti
53
+ } else {
54
+ // can't figure out, just assume JPEG
55
+ self . imageUTI = kUTTypeJPEG
56
+ }
57
+
58
+ if UTTypeConformsTo ( imageUTI, kUTTypeGIF) {
59
+ // use a WebView for gif so we can animate without pulling in a third party library
60
+ let config = with ( WKWebViewConfiguration ( ) ) {
61
+ $0. userContentController = with ( WKUserContentController ( ) ) {
62
+ // we can't use `loadHTMLString` with `<img>` inside to do styling because the webview can't get
63
+ // the security scoped file if loaded by the service extension so we need to load data directly
64
+ $0. addUserScript ( WKUserScript ( source: """
65
+ var style = document.createElement('style');
66
+ style.innerHTML = `
67
+ img { width: 100%; height: 100%; }
68
+ `;
69
+ document.head.appendChild(style);
70
+ """ , injectionTime: . atDocumentEnd, forMainFrameOnly: true ) )
71
+ }
72
+ }
73
+
74
+ visibleView = . webView( with ( WKWebView ( frame: . zero, configuration: config) ) {
75
+ $0. scrollView. isScrollEnabled = false
76
+ $0. isOpaque = false
77
+ $0. backgroundColor = . clear
78
+ $0. scrollView. backgroundColor = . clear
79
+ } )
80
+ } else {
81
+ self . visibleView = . imageView( with ( UIImageView ( ) ) {
82
+ $0. contentMode = . scaleAspectFit
83
+ } )
84
+ }
85
+
86
+ } catch {
29
87
attachmentURL. stopAccessingSecurityScopedResource ( )
30
- throw ImageAttachmentError . imageDecodeFailure
88
+ throw error
31
89
}
32
90
33
- self . image = image
34
91
self . attachmentURL = attachmentURL
35
92
super. init ( nibName: nil , bundle: nil )
36
93
}
@@ -68,9 +125,22 @@ class ImageAttachmentViewController: UIViewController, NotificationCategory {
68
125
}
69
126
70
127
func start( ) -> Promise < Void > {
71
- imageView. image = image
72
128
lastAttachmentURL = attachmentURL
73
- aspectRatioConstraint = NSLayoutConstraint . aspectRatioConstraint ( on: imageView, size: image. size)
129
+
130
+ switch visibleView {
131
+ case let . webView( webView) :
132
+ let mime = UTTypeCopyPreferredTagWithClass ( imageUTI, kUTTagClassMIMEType) ? . takeRetainedValue ( ) as String ?
133
+ webView. load (
134
+ imageData,
135
+ mimeType: mime ?? " image/gif " ,
136
+ characterEncodingName: " UTF-8 " ,
137
+ baseURL: attachmentURL
138
+ )
139
+ case let . imageView( imageView) :
140
+ imageView. image = image
141
+ }
142
+
143
+ aspectRatioConstraint = NSLayoutConstraint . aspectRatioConstraint ( on: visibleView. view, size: image. size)
74
144
75
145
return . value( ( ) )
76
146
}
@@ -92,13 +162,14 @@ class ImageAttachmentViewController: UIViewController, NotificationCategory {
92
162
override func viewDidLoad( ) {
93
163
super. viewDidLoad ( )
94
164
95
- view. addSubview ( imageView)
96
- imageView. translatesAutoresizingMaskIntoConstraints = false
165
+ let subview = visibleView. view
166
+ view. addSubview ( subview)
167
+ subview. translatesAutoresizingMaskIntoConstraints = false
97
168
NSLayoutConstraint . activate ( [
98
- imageView . topAnchor. constraint ( equalTo: view. topAnchor) ,
99
- imageView . leadingAnchor. constraint ( equalTo: view. leadingAnchor) ,
100
- imageView . trailingAnchor. constraint ( equalTo: view. trailingAnchor) ,
101
- imageView . bottomAnchor. constraint ( equalTo: view. bottomAnchor) ,
169
+ subview . topAnchor. constraint ( equalTo: view. topAnchor) ,
170
+ subview . leadingAnchor. constraint ( equalTo: view. leadingAnchor) ,
171
+ subview . trailingAnchor. constraint ( equalTo: view. trailingAnchor) ,
172
+ subview . bottomAnchor. constraint ( equalTo: view. bottomAnchor) ,
102
173
] )
103
174
}
104
175
0 commit comments