Skip to content

Commit f946269

Browse files
authored
gif support (#39)
1 parent 8a87bc5 commit f946269

File tree

4 files changed

+179
-4
lines changed

4 files changed

+179
-4
lines changed

Django Files.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
4CE57E8B2D7C9F440073CFC1 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE57E8A2D7C9F440073CFC1 /* SnapshotHelper.swift */; };
1616
A21CC8852DEB967300EF776C /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = A21CC8842DEB967300EF776C /* FirebaseAnalytics */; };
1717
A21CC88B2DEB991100EF776C /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = A21CC88A2DEB991100EF776C /* FirebaseCrashlytics */; };
18+
A22380612DFD33F000A544A0 /* FLAnimatedImage in Frameworks */ = {isa = PBXBuildFile; productRef = A22380602DFD33F000A544A0 /* FLAnimatedImage */; };
1819
A2DF11D52DDA13FE0096E7C4 /* HighlightSwift in Frameworks */ = {isa = PBXBuildFile; productRef = A2DF11D42DDA13FE0096E7C4 /* HighlightSwift */; };
1920
/* End PBXBuildFile section */
2021

@@ -135,6 +136,7 @@
135136
A2DF11D52DDA13FE0096E7C4 /* HighlightSwift in Frameworks */,
136137
4C82CB512D62372200C0893B /* HTTPTypes in Frameworks */,
137138
A21CC88B2DEB991100EF776C /* FirebaseCrashlytics in Frameworks */,
139+
A22380612DFD33F000A544A0 /* FLAnimatedImage in Frameworks */,
138140
A21CC8852DEB967300EF776C /* FirebaseAnalytics in Frameworks */,
139141
4C82CB532D62372200C0893B /* HTTPTypesFoundation in Frameworks */,
140142
);
@@ -224,6 +226,7 @@
224226
A2DF11D42DDA13FE0096E7C4 /* HighlightSwift */,
225227
A21CC8842DEB967300EF776C /* FirebaseAnalytics */,
226228
A21CC88A2DEB991100EF776C /* FirebaseCrashlytics */,
229+
A22380602DFD33F000A544A0 /* FLAnimatedImage */,
227230
);
228231
productName = "Django Files";
229232
productReference = 4C5E20EC2D603C3B009EE83A /* Django Files.app */;
@@ -339,6 +342,7 @@
339342
4CE57E892D7C9EB70073CFC1 /* XCRemoteSwiftPackageReference "fastlane" */,
340343
A2DF11D32DDA12CA0096E7C4 /* XCRemoteSwiftPackageReference "highlightswift" */,
341344
A21CC8832DEB950C00EF776C /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
345+
A223805D2DFD335A00A544A0 /* XCRemoteSwiftPackageReference "FLAnimatedImage" */,
342346
);
343347
preferredProjectObjectVersion = 77;
344348
productRefGroup = 4C5E20ED2D603C3B009EE83A /* Products */;
@@ -871,6 +875,14 @@
871875
minimumVersion = 11.13.0;
872876
};
873877
};
878+
A223805D2DFD335A00A544A0 /* XCRemoteSwiftPackageReference "FLAnimatedImage" */ = {
879+
isa = XCRemoteSwiftPackageReference;
880+
repositoryURL = "https://github.com/Flipboard/FLAnimatedImage.git";
881+
requirement = {
882+
kind = upToNextMajorVersion;
883+
minimumVersion = 1.0.17;
884+
};
885+
};
874886
A2DF11D32DDA12CA0096E7C4 /* XCRemoteSwiftPackageReference "highlightswift" */ = {
875887
isa = XCRemoteSwiftPackageReference;
876888
repositoryURL = "https://github.com/appstefan/highlightswift";
@@ -912,6 +924,11 @@
912924
package = A21CC8832DEB950C00EF776C /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
913925
productName = FirebaseCrashlytics;
914926
};
927+
A22380602DFD33F000A544A0 /* FLAnimatedImage */ = {
928+
isa = XCSwiftPackageProductDependency;
929+
package = A223805D2DFD335A00A544A0 /* XCRemoteSwiftPackageReference "FLAnimatedImage" */;
930+
productName = FLAnimatedImage;
931+
};
915932
A2DF11D42DDA13FE0096E7C4 /* HighlightSwift */ = {
916933
isa = XCSwiftPackageProductDependency;
917934
package = A2DF11D32DDA12CA0096E7C4 /* XCRemoteSwiftPackageReference "highlightswift" */;

Django Files.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import SwiftUI
2+
import FLAnimatedImage
3+
4+
struct AnimatedImageView: UIViewRepresentable {
5+
let data: Data
6+
7+
func makeUIView(context: Context) -> FLAnimatedImageView {
8+
let imageView = FLAnimatedImageView()
9+
imageView.contentMode = .scaleAspectFit
10+
imageView.clipsToBounds = true
11+
return imageView
12+
}
13+
14+
func updateUIView(_ imageView: FLAnimatedImageView, context: Context) {
15+
if let animatedImage = FLAnimatedImage(animatedGIFData: data) {
16+
imageView.animatedImage = animatedImage
17+
}
18+
}
19+
}
20+
21+
struct AnimatedImageScrollView: UIViewRepresentable {
22+
let data: Data
23+
24+
func makeCoordinator() -> Coordinator {
25+
Coordinator(self)
26+
}
27+
28+
func makeUIView(context: Context) -> UIScrollView {
29+
let scrollView = CustomScrollView()
30+
scrollView.delegate = context.coordinator
31+
32+
let imageView = FLAnimatedImageView()
33+
if let animatedImage = FLAnimatedImage(animatedGIFData: data) {
34+
imageView.animatedImage = animatedImage
35+
}
36+
imageView.contentMode = .scaleAspectFit
37+
imageView.frame = CGRect(origin: .zero, size: imageView.animatedImage?.size ?? .zero)
38+
scrollView.addSubview(imageView)
39+
40+
context.coordinator.imageView = imageView
41+
context.coordinator.scrollView = scrollView
42+
43+
let doubleTapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleDoubleTap(_:)))
44+
doubleTapGesture.numberOfTapsRequired = 2
45+
scrollView.addGestureRecognizer(doubleTapGesture)
46+
47+
// Calculate initial zoom scale
48+
if let size = imageView.animatedImage?.size {
49+
let widthScale = UIScreen.main.bounds.width / size.width
50+
let heightScale = UIScreen.main.bounds.height / size.height
51+
let minScale = min(widthScale, heightScale)
52+
53+
scrollView.minimumZoomScale = minScale
54+
scrollView.maximumZoomScale = 5.0
55+
56+
// Set content size to image size
57+
scrollView.contentSize = size
58+
59+
// Set initial zoom scale
60+
scrollView.zoomScale = minScale
61+
}
62+
63+
return scrollView
64+
}
65+
66+
func updateUIView(_ scrollView: UIScrollView, context: Context) {
67+
if let imageView = context.coordinator.imageView as? FLAnimatedImageView,
68+
let animatedImage = FLAnimatedImage(animatedGIFData: data) {
69+
imageView.animatedImage = animatedImage
70+
context.coordinator.updateZoomScaleForSize(scrollView.bounds.size)
71+
}
72+
}
73+
74+
class Coordinator: NSObject, UIScrollViewDelegate {
75+
let parent: AnimatedImageScrollView
76+
weak var imageView: FLAnimatedImageView?
77+
weak var scrollView: UIScrollView?
78+
79+
init(_ parent: AnimatedImageScrollView) {
80+
self.parent = parent
81+
}
82+
83+
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
84+
return imageView
85+
}
86+
87+
func updateZoomScaleForSize(_ size: CGSize) {
88+
guard let imageView = imageView,
89+
let animatedImage = imageView.animatedImage,
90+
let scrollView = scrollView,
91+
size.width > 0,
92+
size.height > 0,
93+
animatedImage.size.width > 0,
94+
animatedImage.size.height > 0 else { return }
95+
96+
let widthScale = size.width / animatedImage.size.width
97+
let heightScale = size.height / animatedImage.size.height
98+
let minScale = min(widthScale, heightScale)
99+
100+
scrollView.minimumZoomScale = minScale
101+
scrollView.maximumZoomScale = max(minScale * 5, 5.0)
102+
}
103+
104+
@objc func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
105+
guard let scrollView = gesture.view as? UIScrollView else { return }
106+
107+
if scrollView.zoomScale > scrollView.minimumZoomScale {
108+
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
109+
} else {
110+
let point = gesture.location(in: imageView)
111+
let size = scrollView.bounds.size
112+
let w = size.width / (scrollView.maximumZoomScale / 5)
113+
let h = size.height / (scrollView.maximumZoomScale / 5)
114+
let x = point.x - (w / 2.0)
115+
let y = point.y - (h / 2.0)
116+
let rect = CGRect(x: x, y: y, width: w, height: h)
117+
scrollView.zoom(to: rect, animated: true)
118+
}
119+
}
120+
121+
func scrollViewDidZoom(_ scrollView: UIScrollView) {
122+
guard let imageView = imageView else { return }
123+
124+
let boundsSize = scrollView.bounds.size
125+
var frameToCenter = imageView.frame
126+
127+
if frameToCenter.size.width < boundsSize.width {
128+
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2
129+
} else {
130+
frameToCenter.origin.x = 0
131+
}
132+
133+
if frameToCenter.size.height < boundsSize.height {
134+
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2
135+
} else {
136+
frameToCenter.origin.y = 0
137+
}
138+
139+
imageView.frame = frameToCenter
140+
}
141+
}
142+
}

Django Files/Views/Preview/Preview.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,16 @@ struct ContentPreview: View {
7979
// Image Preview
8080
private var imagePreview: some View {
8181
GeometryReader { geometry in
82-
if let content = content, let uiImage = UIImage(data: content) {
83-
ImageScrollView(image: uiImage)
84-
.frame(width: geometry.size.width, height: geometry.size.height)
82+
if let content = content {
83+
if mimeType == "image/gif" {
84+
AnimatedImageScrollView(data: content)
85+
.frame(width: geometry.size.width, height: geometry.size.height)
86+
} else if let uiImage = UIImage(data: content) {
87+
ImageScrollView(image: uiImage)
88+
.frame(width: geometry.size.width, height: geometry.size.height)
89+
} else {
90+
Text("Unable to load image")
91+
}
8592
} else {
8693
Text("Unable to load image")
8794
}

0 commit comments

Comments
 (0)