├── .github
└── FUNDING.yml
├── .gitignore
├── .swift-version
├── .travis.yml
├── Cartfile.private
├── Cartfile.resolved
├── LICENSE
├── Package.swift
├── README.md
├── README_KR.md
├── SwiftyGif.podspec
├── SwiftyGif.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── SwiftyGif.xcscheme
│ │ ├── SwiftyGifExample.xcscheme
│ │ └── SwiftyGifMacExample.xcscheme
└── xcuserdata
│ ├── alex.xcuserdatad
│ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── travasonig.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── SwiftyGif
├── Info.plist
├── NSImage+SwiftyGif.swift
├── NSImageView+SwiftyGif.swift
├── ObjcAssociatedWeakObject.swift
├── PrivacyInfo.xcprivacy
├── SwiftyGif.h
├── SwiftyGifError.swift
├── SwiftyGifManager.swift
├── UIImage+SwiftyGif.swift
└── UIImageView+SwiftyGif.swift
├── SwiftyGifExample
├── 2.gif
├── 3.gif
├── 4.gif
├── 5.gif
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Cell.swift
├── DetailController.swift
├── Info.plist
└── ViewController.swift
├── SwiftyGifMacExample
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── Info.plist
├── SwiftyGifMacExample.entitlements
└── ViewController.swift
├── SwiftyGifTests
├── Images
│ ├── 20000x20000.gif
│ ├── no_property_dictionary.gif
│ └── single_frame_Zt2012.gif
├── Info.plist
├── SwiftyGifTests.swift
└── __Snapshots__
│ └── SwiftyGifTests
│ ├── testThat15MBGIFCanBeLoaded.1.png
│ ├── testThatGIFWithoutkCGImagePropertyGIFDictionaryCanBeLoaded.1.png
│ ├── testThatImageViewCanBeRecycled.1.png
│ ├── testThatImageViewCanBeRecycledForGIF.2.png
│ ├── testThatImageViewCanBeRecycledForNormalImage.1.png
│ ├── testThatNonAnimatedGIFCanBeLoaded.1.png
│ └── testThatNonAnimatedGIFCanBeLoadedWithUIImage.1.png
├── example.gif
└── projec-file-explain.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [kirualex]
4 | custom: ['https://www.paypal.me/alexiscreuzot']
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/xcode,swift,objective-c
3 |
4 | .DS_Store
5 |
6 | ### Xcode ###
7 | # Xcode
8 | #
9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
10 |
11 | ## Build generated
12 | build/
13 | DerivedData/
14 |
15 | ## Various settings
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata/
25 |
26 | ## Other
27 | *.moved-aside
28 | *.xccheckout
29 | *.xcscmblueprint
30 |
31 |
32 | ### Swift ###
33 | # Xcode
34 | #
35 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
36 |
37 | ## Build generated
38 | build/
39 | DerivedData/
40 |
41 | ## Various settings
42 | *.pbxuser
43 | !default.pbxuser
44 | *.mode1v3
45 | !default.mode1v3
46 | *.mode2v3
47 | !default.mode2v3
48 | *.perspectivev3
49 | !default.perspectivev3
50 | xcuserdata/
51 |
52 | ## Other
53 | *.moved-aside
54 | *.xcuserstate
55 |
56 | ## Obj-C/Swift specific
57 | *.hmap
58 | *.ipa
59 | *.dSYM.zip
60 | *.dSYM
61 |
62 | ## Playgrounds
63 | timeline.xctimeline
64 | playground.xcworkspace
65 |
66 | # Swift Package Manager
67 | #
68 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
69 | # Packages/
70 | .build/
71 |
72 | # CocoaPods
73 | #
74 | # We recommend against adding the Pods directory to your .gitignore. However
75 | # you should judge for yourself, the pros and cons are mentioned at:
76 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
77 | #
78 | # Pods/
79 |
80 | # Carthage
81 | #
82 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
83 | # Carthage/Checkouts
84 |
85 | Carthage/Build
86 |
87 | # fastlane
88 | #
89 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
90 | # screenshots whenever they are needed.
91 | # For more information about the recommended setup visit:
92 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
93 |
94 | fastlane/report.xml
95 | fastlane/Preview.html
96 | fastlane/screenshots
97 | fastlane/test_output
98 |
99 |
100 | ### Objective-C ###
101 | # Xcode
102 | #
103 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
104 |
105 | ## Build generated
106 | build/
107 | DerivedData/
108 |
109 | ## Various settings
110 | *.pbxuser
111 | !default.pbxuser
112 | *.mode1v3
113 | !default.mode1v3
114 | *.mode2v3
115 | !default.mode2v3
116 | *.perspectivev3
117 | !default.perspectivev3
118 | xcuserdata/
119 |
120 | ## Other
121 | *.moved-aside
122 | *.xcuserstate
123 |
124 | ## Obj-C/Swift specific
125 | *.hmap
126 | *.ipa
127 | *.dSYM.zip
128 | *.dSYM
129 |
130 | # CocoaPods
131 | #
132 | # We recommend against adding the Pods directory to your .gitignore. However
133 | # you should judge for yourself, the pros and cons are mentioned at:
134 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
135 | #
136 | # Pods/
137 |
138 | # Carthage
139 | #
140 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
141 | # Carthage/Checkouts
142 |
143 | Carthage/Build
144 |
145 | # fastlane
146 | #
147 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
148 | # screenshots whenever they are needed.
149 | # For more information about the recommended setup visit:
150 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
151 |
152 | fastlane/report.xml
153 | fastlane/Preview.html
154 | fastlane/screenshots
155 | fastlane/test_output
156 |
157 | # Code Injection
158 | #
159 | # After new code Injection tools there's a generated folder /iOSInjectionProject
160 | # https://github.com/johnno1962/injectionforxcode
161 |
162 | iOSInjectionProject/
163 |
164 | ### Objective-C Patch ###
165 | *.xcscmblueprint
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | osx_image: xcode10
3 |
4 | branches:
5 | only:
6 | - master
7 |
8 | script:
9 | - xcodebuild build -project SwiftyGif.xcodeproj -scheme SwiftyGif -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone X,OS=11.3' | xcpretty
10 |
--------------------------------------------------------------------------------
/Cartfile.private:
--------------------------------------------------------------------------------
1 | github "pointfreeco/swift-snapshot-testing" ~> 1.5
2 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "pointfreeco/swift-snapshot-testing" "1.5.0"
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Alexis Creuzot
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "SwiftyGif",
7 | platforms: [
8 | .iOS("9.0"), .macOS(.v10_14),
9 | ],
10 | products: [
11 | .library(name: "SwiftyGif", targets: ["SwiftyGif"]),
12 | .library(name: "SwiftyGif-Dynamic", type: .dynamic, targets: ["SwiftyGif"]),
13 | ],
14 | dependencies: [],
15 | targets: [
16 | .target(
17 | name: "SwiftyGif",
18 | dependencies: [],
19 | path: "SwiftyGif",
20 | resources: [.copy("PrivacyInfo.xcprivacy")]
21 | ),
22 | ]
23 | )
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://swift.org)
2 | [](https://img.shields.io/cocoapods/v/SwiftyGif.svg)
3 | [](https://github.com/Carthage/Carthage)
4 | [](https://travis-ci.org/kirualex/SwiftyGif)
5 | [](https://raw.githubusercontent.com/kirualex/SwiftyGif/master/LICENSE)
6 |
7 | # SwiftyGif
8 | High performance & easy to use Gif engine
9 |
10 |
11 |
12 |
13 |
14 |
15 | > Language Switch: [한국어](README_KR.md)
16 |
17 | ## Features
18 | - [x] UIImage and UIImageView extension based
19 | - [x] Remote GIFs with customizable loader
20 | - [x] Great CPU/Memory performances
21 | - [x] Control playback
22 | - [x] Allow control of display quality by using 'levelOfIntegrity'
23 | - [x] Allow control CPU/memory tradeoff via 'memoryLimit'
24 |
25 | ## Installation
26 |
27 | ### With CocoaPods
28 | ```ruby
29 | source 'https://github.com/CocoaPods/Specs.git'
30 | use_frameworks!
31 | pod 'SwiftyGif'
32 | ```
33 |
34 | ### With Carthage
35 | Follow the usual Carthage instructions on how to [add a framework to an application](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). When adding SwiftyGif among the frameworks listed in `Cartfile`, apply its syntax for [GitHub repositories](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories):
36 |
37 | ```
38 | github "kirualex/SwiftyGif"
39 | ```
40 |
41 | ### With Swift Package Manager
42 | ```ruby
43 | https://github.com/kirualex/SwiftyGif.git
44 | ```
45 |
46 | ## How to Use
47 |
48 | ### Project files
49 | 
50 |
51 | As of now, Xcode `xcassets` folders do not recognize `.gif` as images. This means you need to put your `.gif` outside of the assets. I recommend creating a group `gif` for instance.
52 |
53 | ### Quick Start
54 |
55 | SwiftyGif uses familiar `UIImage` and `UIImageView` to display gifs.
56 |
57 | #### Programmaticaly
58 |
59 | ```swift
60 | import SwiftyGif
61 |
62 | do {
63 | let gif = try UIImage(gifName: "MyImage.gif")
64 | let imageview = UIImageView(gifImage: gif, loopCount: 3) // Will loop 3 times
65 | imageview.frame = view.bounds
66 | view.addSubview(imageview)
67 | } catch {
68 | print(error)
69 | }
70 | ```
71 |
72 | #### Directly from nib/storyboard
73 |
74 | ```swift
75 | @IBOutlet var myImageView : UIImageView!
76 | ...
77 |
78 | let gif = try UIImage(gifName: "MyImage.gif")
79 | self.myImageView.setGifImage(gif, loopCount: -1) // Will loop forever
80 | ```
81 |
82 | #### Remote GIFs
83 |
84 | ```swift
85 | // You can also set it with an URL pointing to your gif
86 | let url = URL(string: "...")
87 | let loader = UIActivityIndicatorView(style: .white)
88 | cell.gifImageView.setGifFromURL(url, customLoader: loader)
89 | ```
90 |
91 |
92 | #### SwiftUI
93 |
94 | Add this `UIViewRepresentable` to your code.
95 |
96 | ```swift
97 | struct AnimatedGifView: UIViewRepresentable {
98 | @Binding var url: URL
99 |
100 | func makeUIView(context: Context) -> UIImageView {
101 | let imageView = UIImageView(gifURL: self.url)
102 | imageView.contentMode = .scaleAspectFit
103 | return imageView
104 | }
105 |
106 | func updateUIView(_ uiView: UIImageView, context: Context) {
107 | uiView.setGifFromURL(self.url)
108 | }
109 | }
110 | ```
111 |
112 | Then to use it:
113 |
114 | ```swift
115 | AnimatedGifView(url: Binding(get: { myModel.gif.url }, set: { _ in }))
116 | ```
117 |
118 | ### Performances
119 | A `SwiftyGifManager` can hold one or several UIImageView using the same memory pool. This allows you to tune the memory limits to your convenience. If no manager is declared, SwiftyGif will just use the `SwiftyGifManager.defaultManager`.
120 |
121 | #### Level of integrity
122 | Setting a lower level of integrity will allow for frame skipping, lowering both CPU and memory usage. This can be a good option if you need to preview a lot of gifs at the same time.
123 |
124 | ```swift
125 | do {
126 | let gif = try UIImage(gifName: "MyImage.gif", levelOfIntegrity:0.5)
127 | } catch {
128 | print(error)
129 | }
130 | ```
131 |
132 | ### Controls
133 | SwiftyGif offers various controls on the current `UIImageView` playing your gif file.
134 |
135 | ```swift
136 | self.myImageView.startAnimatingGif()
137 | self.myImageView.stopAnimatingGif()
138 | self.myImageView.showFrameAtIndexDelta(delta: Int)
139 | self.myImageView.showFrameAtIndex(index: Int)
140 | ```
141 |
142 | To allow easy use of those controls, some utility methods are provided :
143 |
144 | ```swift
145 | self.myImageView.isAnimatingGif() // Returns whether the gif is currently playing
146 | self.myImageView.gifImage!.framesCount() // Returns number of frames for this gif
147 | ```
148 |
149 | ### Delegate
150 | You can declare a SwiftyGifDelegate to receive updates on the gif lifecycle.
151 | For instance, if you want your controller `MyController` to act as the delegate:
152 | ```swift
153 | override func viewDidLoad() {
154 | super.viewDidLoad()
155 | self.imageView.delegate = self
156 | }
157 | ```
158 |
159 | Then simply add an extension:
160 |
161 | ```swift
162 | extension MyController : SwiftyGifDelegate {
163 |
164 | func gifURLDidFinish(sender: UIImageView) {
165 | print("gifURLDidFinish")
166 | }
167 |
168 | func gifURLDidFail(sender: UIImageView) {
169 | print("gifURLDidFail")
170 | }
171 |
172 | func gifDidStart(sender: UIImageView) {
173 | print("gifDidStart")
174 | }
175 |
176 | func gifDidLoop(sender: UIImageView) {
177 | print("gifDidLoop")
178 | }
179 |
180 | func gifDidStop(sender: UIImageView) {
181 | print("gifDidStop")
182 | }
183 | }
184 | ```
185 |
186 | ## Benchmark
187 | ### Display 1 Image
188 | | |CPU Usage(average) |Memory Usage(average) |
189 | |:-------------:|:-----------------:|:-----------------------:|
190 | |FLAnimatedImage|35% |9,5Mb |
191 | |SwiftyGif |2% |18,4Mb |
192 | |SwiftyGif(memoryLimit:10)|34% |9,5Mb |
193 |
194 | ### Display 6 Images
195 | | |CPU Usage(average) |Memory Usage(average) |
196 | |:-------------:|:-----------------:|:-----------------------:|
197 | |FLAnimatedImage|65% |25,1Mb |
198 | |SwiftyGif |22% |105Mb |
199 | |SwiftyGif(memoryLimit:20)|45% |26Mb |
200 |
201 | Measured on an iPhone 6S, iOS 9.3.1 and Xcode 7.3.
202 |
--------------------------------------------------------------------------------
/README_KR.md:
--------------------------------------------------------------------------------
1 | [](http://swift.org)
2 | [](https://img.shields.io/cocoapods/v/SwiftyGif.svg)
3 | [](https://github.com/Carthage/Carthage)
4 | [](https://travis-ci.org/kirualex/SwiftyGif)
5 | [](https://raw.githubusercontent.com/kirualex/SwiftyGif/master/LICENSE)
6 |
7 | # SwiftyGif
8 | 쉽고 빠르게 Gif를 사용할 수 있습니다.
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## 기능
16 | - [x] UIImage와 UIImageView extension을 기반으로 구현했습니다.
17 | - [x] 원격 GIF를 불러올 수 있고, 로딩 바를 설정할 수 있습니다.
18 | - [x] 뛰어난 CPU/메모리 성능을 보입니다.
19 | - [x] 반복 재생 횟수를 조정할 수 있습니다.
20 | - [x] 'levelOfIntegrity' 파라미터를 통해 Integrity 레벨을 조절합니다.
21 | - [x] 'memoryLimit' 파라미터를 통해 CPU/메모리 사용량을 조절합니다. (CPU - 메모리는 tradeoff 관계)
22 |
23 | ## 설치
24 |
25 | ### CocoaPods 사용
26 | ```ruby
27 | source 'https://github.com/CocoaPods/Specs.git'
28 | use_frameworks!
29 | pod 'SwiftyGif'
30 | ```
31 |
32 | ### Carthage 사용
33 | [애플리케이션에 프레임워크 추가하기](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)에 나온 일반적인 카르타고 사용법을 사용합니다.
34 | `Cartfile`에 나열된 프레임워크 중에서 SwityGif를 추가할 때, [GitHub repositories](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#github-repositories)에 구문을 적용합니다.:
35 |
36 | ```
37 | github "kirualex/SwiftyGif"
38 | ```
39 |
40 | ### Swift Package Manager 사용
41 | ```ruby
42 | https://github.com/kirualex/SwiftyGif.git
43 | ```
44 |
45 | ## 사용법
46 |
47 | ### 프로젝트 파일
48 | 
49 |
50 | 현재 Xcode `xcassets` 폴더는 `.gif` 파일을 이미지로 인식하지 못합니다. 즉 assets밖에 '.gif' 파일을 위치시켜야 합니다. `gif` 폴더를 만들어 그 안에서 관리하는 것을 추천합니다.
51 |
52 | ### 빠른 시작
53 |
54 | SwiftyGif는 `UIImage`와 `UIImageView` 를 사용합니다.
55 |
56 | #### 코드
57 |
58 | ```swift
59 | import SwiftyGif
60 |
61 | do {
62 | let gif = try UIImage(gifName: "MyImage.gif")
63 | let imageview = UIImageView(gifImage: gif, loopCount: 3) // 3번 반복
64 | imageview.frame = view.bounds
65 | view.addSubview(imageview)
66 | } catch {
67 | print(error)
68 | }
69 | ```
70 |
71 | #### nib/storyboard 사용
72 |
73 | ```swift
74 | @IBOutlet var myImageView : UIImageView!
75 | ...
76 |
77 | let gif = try UIImage(gifName: "MyImage.gif")
78 | self.myImageView.setGifImage(gif, loopCount: -1) // 무한 반복
79 | ```
80 |
81 | #### 원격 GIFs
82 |
83 | ```swift
84 | // GIF를 나타내는 URL을 설정합니다
85 | let url = URL(string: "...")
86 | let loader = UIActivityIndicatorView(style: .white)
87 | cell.gifImageView.setGifFromURL(url, customLoader: loader)
88 | ```
89 |
90 | ### 성능
91 | `SwiftyGifManager`는 같은 메모리 풀은 사용해 하나 이상의 UIImageView를 관리합니다. 이를 사용해 쉽게 메모리 제한을 할 수 있습니다. 따로 매니저를 선언하지 않는다면 `SwiftyGifManager.defaultManager`를 사용합니다.
92 |
93 | #### Integrity 레벨
94 | Integrity 레벨을 낮게 설정하면 프레임을 건너뜁니다. CPU와 메모리 사용량도 낮춥니다. 많은 gif를 한 번에 미리보기 할 때 유용한 옵션입니다.
95 |
96 | ```swift
97 | do {
98 | let gif = try UIImage(gifName: "MyImage.gif", levelOfIntegrity:0.5)
99 | } catch {
100 | print(error)
101 | }
102 | ```
103 |
104 | ### 제어
105 | SwiftyGif는 `UIImageVIew`에 대해 다양한 제어 기능을 제공합니다.
106 |
107 | ```swift
108 | self.myImageView.startAnimatingGif()
109 | self.myImageView.stopAnimatingGif()
110 | self.myImageView.showFrameAtIndexDelta(delta: Int)
111 | self.myImageView.showFrameAtIndex(index: Int)
112 | ```
113 |
114 | 더 쉽게 사용하기 위한 메소드들이 제공됩니다. :
115 |
116 | ```swift
117 | self.myImageView.isAnimatingGif() // 현재 gif가 재생중인지 아닌지
118 | self.myImageView.gifImage!.framesCount() // 해당 gif 프레임 수 리턴
119 | ```
120 |
121 | ### Delegate
122 | SwiftyGifDelegate를 선언해 gif 생명 주기(=Lifecycle)를 받습니다. 예를 들어 `MyController`라는 ViewController를 delegate로 사용합니다.:
123 |
124 | ```swift
125 | override func viewDidLoad() {
126 | super.viewDidLoad()
127 | self.imageView.delegate = self
128 | }
129 | ```
130 |
131 | extension에 채택해 쉽게 사용할 수 있습니다.:
132 |
133 | ```swift
134 | extension MyController : SwiftyGifDelegate {
135 |
136 | func gifURLDidFinish(sender: UIImageView) {
137 | print("gifURLDidFinish")
138 | }
139 |
140 | func gifURLDidFail(sender: UIImageView) {
141 | print("gifURLDidFail")
142 | }
143 |
144 | func gifDidStart(sender: UIImageView) {
145 | print("gifDidStart")
146 | }
147 |
148 | func gifDidLoop(sender: UIImageView) {
149 | print("gifDidLoop")
150 | }
151 |
152 | func gifDidStop(sender: UIImageView) {
153 | print("gifDidStop")
154 | }
155 | }
156 | ```
157 |
158 | ## 벤치마크
159 | ### 이미지 1개
160 | | |CPU 사용량(평균) |메모리 사용량(평균) |
161 | |:-------------:|:-----------------:|:-----------------------:|
162 | |FLAnimatedImage|35% |9,5Mb |
163 | |SwiftyGif |2% |18,4Mb |
164 | |SwiftyGif(memoryLimit:10)|34% |9,5Mb |
165 |
166 | ### 이미지 6개
167 | | |CPU 사용량(평균) |메모리 사용량(평균) |
168 | |:-------------:|:-----------------:|:-----------------------:|
169 | |FLAnimatedImage|65% |25,1Mb |
170 | |SwiftyGif |22% |105Mb |
171 | |SwiftyGif(memoryLimit:20)|45% |26Mb |
172 |
173 | 테스트 환경 : iPhone 6S, iOS 9.3.1, Xcode 7.3.
174 | 메모리를 제한하면 CPU 사용량이 늘어나는 것을 확인할 수 있습니다. (trade-off)
--------------------------------------------------------------------------------
/SwiftyGif.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'SwiftyGif'
3 | s.version = '5.4.5'
4 | s.summary = 'High performance Gif engine in Swift. Add and control Gif images easily!'
5 | s.homepage = 'https://github.com/kirualex/SwiftyGif'
6 | s.license = { :type => "MIT", :file => "LICENSE" }
7 | s.author = { "Alexis Creuzot" => "alexis.creuzot@gmail.com" }
8 | s.source = { :git => "https://github.com/kirualex/SwiftyGif.git", :tag => s.version.to_s }
9 | s.platform = :ios, '9.0'
10 | s.requires_arc = true
11 | s.source_files = 'SwiftyGif/*{.h,.swift}'
12 | s.resource_bundles = {'SwiftyGif' => ['SwiftyGif/PrivacyInfo.xcprivacy']}
13 | s.swift_version = '5.0'
14 | end
15 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 230188A124D95CB800EFE1BC /* SwiftyGifManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAE121E11CB2A3DD00960D00 /* SwiftyGifManager.swift */; };
11 | 230188A324D9615700EFE1BC /* NSImageView+SwiftyGif.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230188A224D9614900EFE1BC /* NSImageView+SwiftyGif.swift */; };
12 | 230188A524D961D800EFE1BC /* NSImage+SwiftyGif.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230188A424D961CD00EFE1BC /* NSImage+SwiftyGif.swift */; };
13 | 230188C124D964FE00EFE1BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230188C024D964FE00EFE1BC /* AppDelegate.swift */; };
14 | 230188C324D964FE00EFE1BC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230188C224D964FE00EFE1BC /* ViewController.swift */; };
15 | 230188C524D964FF00EFE1BC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 230188C424D964FF00EFE1BC /* Assets.xcassets */; };
16 | 230188C824D964FF00EFE1BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 230188C624D964FF00EFE1BC /* Main.storyboard */; };
17 | 230188D024D965E800EFE1BC /* libSwiftyGifMac.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2301889824D95CA100EFE1BC /* libSwiftyGifMac.a */; };
18 | 230188D224D9684100EFE1BC /* 3.gif in Resources */ = {isa = PBXBuildFile; fileRef = FAF19DDC1CC90F9200454324 /* 3.gif */; };
19 | 230188D324D9684100EFE1BC /* 2.gif in Resources */ = {isa = PBXBuildFile; fileRef = FAC7163A1CCA26AE0018A0CF /* 2.gif */; };
20 | 230188D424D9684100EFE1BC /* 4.gif in Resources */ = {isa = PBXBuildFile; fileRef = FAC7163B1CCA26AE0018A0CF /* 4.gif */; };
21 | 230188D524D9684100EFE1BC /* 5.gif in Resources */ = {isa = PBXBuildFile; fileRef = FA57CC951CB3EDAA000F3476 /* 5.gif */; };
22 | 3B18BAF81E289899009C125A /* SwiftyGif.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B18BAF61E289899009C125A /* SwiftyGif.h */; settings = {ATTRIBUTES = (Public, ); }; };
23 | 3B18BAFB1E289899009C125A /* SwiftyGif.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B18BAF41E289899009C125A /* SwiftyGif.framework */; };
24 | 3B18BAFC1E289899009C125A /* SwiftyGif.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B18BAF41E289899009C125A /* SwiftyGif.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
25 | 3B18BB011E2898A1009C125A /* SwiftyGifManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAE121E11CB2A3DD00960D00 /* SwiftyGifManager.swift */; };
26 | 3B18BB021E2898A5009C125A /* UIImage+SwiftyGif.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAE121E21CB2A3DD00960D00 /* UIImage+SwiftyGif.swift */; };
27 | 3B18BB031E2898A9009C125A /* UIImageView+SwiftyGif.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAE121E31CB2A3DD00960D00 /* UIImageView+SwiftyGif.swift */; };
28 | 6F2FA2CF2C5417B600971497 /* SwiftyGifError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2FA2CE2C5417B600971497 /* SwiftyGifError.swift */; };
29 | 6F2FA2D02C5417BA00971497 /* SwiftyGifError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2FA2CE2C5417B600971497 /* SwiftyGifError.swift */; };
30 | 7A6E2EDC2B2278AE00A3ABF1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7A6E2EDB2B2278AE00A3ABF1 /* PrivacyInfo.xcprivacy */; };
31 | AD938875276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD938874276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift */; };
32 | AD938876276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD938874276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift */; };
33 | EF34CB4D22A51591002A6C92 /* SwiftyGifTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF34CB4C22A51591002A6C92 /* SwiftyGifTests.swift */; };
34 | EF34CB4F22A51591002A6C92 /* SwiftyGif.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B18BAF41E289899009C125A /* SwiftyGif.framework */; };
35 | EF34CB5522A524F6002A6C92 /* single_frame_Zt2012.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF26CB8A22A167B100E92383 /* single_frame_Zt2012.gif */; };
36 | EF34CB5622A524FA002A6C92 /* 20000x20000.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF26CB8C22A16DB900E92383 /* 20000x20000.gif */; };
37 | EF34CB5722A524FE002A6C92 /* no_property_dictionary.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF26CB8822A166E400E92383 /* no_property_dictionary.gif */; };
38 | EF34CB5F22A52909002A6C92 /* SnapshotTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF34CB5E22A52909002A6C92 /* SnapshotTesting.framework */; };
39 | EF34CB6122A52929002A6C92 /* SnapshotTesting.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EF34CB5E22A52909002A6C92 /* SnapshotTesting.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
40 | EF34CB6322A54271002A6C92 /* 15MB_Einstein_rings_zoom.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF34CB6222A54271002A6C92 /* 15MB_Einstein_rings_zoom.gif */; };
41 | EF34CB6422A54ABC002A6C92 /* no_property_dictionary.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF26CB8822A166E400E92383 /* no_property_dictionary.gif */; };
42 | EF34CB6522A54AC0002A6C92 /* single_frame_Zt2012.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF26CB8A22A167B100E92383 /* single_frame_Zt2012.gif */; };
43 | EF34CB6722A54ACE002A6C92 /* 20000x20000.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF26CB8C22A16DB900E92383 /* 20000x20000.gif */; };
44 | FA57CC981CB3EDAA000F3476 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA57CC871CB3EDAA000F3476 /* AppDelegate.swift */; };
45 | FA57CC991CB3EDAA000F3476 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA57CC881CB3EDAA000F3476 /* Assets.xcassets */; };
46 | FA57CC9A1CB3EDAA000F3476 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA57CC8A1CB3EDAA000F3476 /* LaunchScreen.storyboard */; };
47 | FA57CC9B1CB3EDAA000F3476 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA57CC8C1CB3EDAA000F3476 /* Main.storyboard */; };
48 | FA57CC9C1CB3EDAA000F3476 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA57CC8E1CB3EDAA000F3476 /* Cell.swift */; };
49 | FA57CC9D1CB3EDAA000F3476 /* DetailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA57CC8F1CB3EDAA000F3476 /* DetailController.swift */; };
50 | FA57CCA31CB3EDAA000F3476 /* 5.gif in Resources */ = {isa = PBXBuildFile; fileRef = FA57CC951CB3EDAA000F3476 /* 5.gif */; };
51 | FA57CCA51CB3EDAA000F3476 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA57CC971CB3EDAA000F3476 /* ViewController.swift */; };
52 | FAC7163C1CCA26AE0018A0CF /* 2.gif in Resources */ = {isa = PBXBuildFile; fileRef = FAC7163A1CCA26AE0018A0CF /* 2.gif */; };
53 | FAC7163D1CCA26AE0018A0CF /* 4.gif in Resources */ = {isa = PBXBuildFile; fileRef = FAC7163B1CCA26AE0018A0CF /* 4.gif */; };
54 | FAF19DE11CC90F9200454324 /* 3.gif in Resources */ = {isa = PBXBuildFile; fileRef = FAF19DDC1CC90F9200454324 /* 3.gif */; };
55 | /* End PBXBuildFile section */
56 |
57 | /* Begin PBXContainerItemProxy section */
58 | 230188CE24D965DE00EFE1BC /* PBXContainerItemProxy */ = {
59 | isa = PBXContainerItemProxy;
60 | containerPortal = FA29E92A1CA9340E00E579D5 /* Project object */;
61 | proxyType = 1;
62 | remoteGlobalIDString = 2301889724D95CA100EFE1BC;
63 | remoteInfo = SwiftyGifMac;
64 | };
65 | 3B18BAF91E289899009C125A /* PBXContainerItemProxy */ = {
66 | isa = PBXContainerItemProxy;
67 | containerPortal = FA29E92A1CA9340E00E579D5 /* Project object */;
68 | proxyType = 1;
69 | remoteGlobalIDString = 3B18BAF31E289899009C125A;
70 | remoteInfo = SwiftyGif;
71 | };
72 | EF34CB5022A51591002A6C92 /* PBXContainerItemProxy */ = {
73 | isa = PBXContainerItemProxy;
74 | containerPortal = FA29E92A1CA9340E00E579D5 /* Project object */;
75 | proxyType = 1;
76 | remoteGlobalIDString = 3B18BAF31E289899009C125A;
77 | remoteInfo = SwiftyGif;
78 | };
79 | /* End PBXContainerItemProxy section */
80 |
81 | /* Begin PBXCopyFilesBuildPhase section */
82 | 3B18BB001E289899009C125A /* Embed Frameworks */ = {
83 | isa = PBXCopyFilesBuildPhase;
84 | buildActionMask = 2147483647;
85 | dstPath = "";
86 | dstSubfolderSpec = 10;
87 | files = (
88 | 3B18BAFC1E289899009C125A /* SwiftyGif.framework in Embed Frameworks */,
89 | );
90 | name = "Embed Frameworks";
91 | runOnlyForDeploymentPostprocessing = 0;
92 | };
93 | EF34CB6022A52914002A6C92 /* CopyFiles */ = {
94 | isa = PBXCopyFilesBuildPhase;
95 | buildActionMask = 2147483647;
96 | dstPath = "";
97 | dstSubfolderSpec = 10;
98 | files = (
99 | EF34CB6122A52929002A6C92 /* SnapshotTesting.framework in CopyFiles */,
100 | );
101 | runOnlyForDeploymentPostprocessing = 0;
102 | };
103 | /* End PBXCopyFilesBuildPhase section */
104 |
105 | /* Begin PBXFileReference section */
106 | 2301889824D95CA100EFE1BC /* libSwiftyGifMac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSwiftyGifMac.a; sourceTree = BUILT_PRODUCTS_DIR; };
107 | 230188A224D9614900EFE1BC /* NSImageView+SwiftyGif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSImageView+SwiftyGif.swift"; sourceTree = ""; };
108 | 230188A424D961CD00EFE1BC /* NSImage+SwiftyGif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSImage+SwiftyGif.swift"; sourceTree = ""; };
109 | 230188BE24D964FE00EFE1BC /* SwiftyGifMacExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyGifMacExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
110 | 230188C024D964FE00EFE1BC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
111 | 230188C224D964FE00EFE1BC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
112 | 230188C424D964FF00EFE1BC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
113 | 230188C724D964FF00EFE1BC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
114 | 230188C924D964FF00EFE1BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
115 | 230188CA24D964FF00EFE1BC /* SwiftyGifMacExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftyGifMacExample.entitlements; sourceTree = ""; };
116 | 3B18BAF41E289899009C125A /* SwiftyGif.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyGif.framework; sourceTree = BUILT_PRODUCTS_DIR; };
117 | 3B18BAF61E289899009C125A /* SwiftyGif.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyGif.h; sourceTree = ""; };
118 | 3B18BAF71E289899009C125A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
119 | 6F2FA2CE2C5417B600971497 /* SwiftyGifError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyGifError.swift; sourceTree = ""; };
120 | 7A6E2EDB2B2278AE00A3ABF1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
121 | AD938874276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjcAssociatedWeakObject.swift; sourceTree = ""; };
122 | EF26CB8822A166E400E92383 /* no_property_dictionary.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = no_property_dictionary.gif; sourceTree = ""; };
123 | EF26CB8A22A167B100E92383 /* single_frame_Zt2012.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = single_frame_Zt2012.gif; sourceTree = ""; };
124 | EF26CB8C22A16DB900E92383 /* 20000x20000.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = 20000x20000.gif; sourceTree = ""; };
125 | EF34CB4A22A51591002A6C92 /* SwiftyGifTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyGifTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
126 | EF34CB4C22A51591002A6C92 /* SwiftyGifTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyGifTests.swift; sourceTree = ""; };
127 | EF34CB4E22A51591002A6C92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
128 | EF34CB5922A52876002A6C92 /* Cartfile.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile.resolved; sourceTree = SOURCE_ROOT; };
129 | EF34CB5A22A52876002A6C92 /* Cartfile.private */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile.private; sourceTree = SOURCE_ROOT; };
130 | EF34CB5E22A52909002A6C92 /* SnapshotTesting.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SnapshotTesting.framework; path = Carthage/Build/iOS/SnapshotTesting.framework; sourceTree = ""; };
131 | EF34CB6222A54271002A6C92 /* 15MB_Einstein_rings_zoom.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = 15MB_Einstein_rings_zoom.gif; path = ../../../../../Downloads/15MB_Einstein_rings_zoom.gif; sourceTree = ""; };
132 | FA29E9321CA9340E00E579D5 /* SwiftyGifExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyGifExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
133 | FA29E94A1CA9340F00E579D5 /* SwiftyGifTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyGifTests.swift; sourceTree = ""; };
134 | FA29E94C1CA9340F00E579D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
135 | FA57CC871CB3EDAA000F3476 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
136 | FA57CC881CB3EDAA000F3476 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
137 | FA57CC8B1CB3EDAA000F3476 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = LaunchScreen.storyboard; sourceTree = ""; };
138 | FA57CC8D1CB3EDAA000F3476 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = ""; };
139 | FA57CC8E1CB3EDAA000F3476 /* Cell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = ""; };
140 | FA57CC8F1CB3EDAA000F3476 /* DetailController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailController.swift; sourceTree = ""; };
141 | FA57CC951CB3EDAA000F3476 /* 5.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = 5.gif; sourceTree = ""; };
142 | FA57CC961CB3EDAA000F3476 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
143 | FA57CC971CB3EDAA000F3476 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
144 | FAC7163A1CCA26AE0018A0CF /* 2.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = 2.gif; sourceTree = ""; };
145 | FAC7163B1CCA26AE0018A0CF /* 4.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = 4.gif; sourceTree = ""; };
146 | FAE121E11CB2A3DD00960D00 /* SwiftyGifManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyGifManager.swift; sourceTree = ""; };
147 | FAE121E21CB2A3DD00960D00 /* UIImage+SwiftyGif.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+SwiftyGif.swift"; sourceTree = ""; };
148 | FAE121E31CB2A3DD00960D00 /* UIImageView+SwiftyGif.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+SwiftyGif.swift"; sourceTree = ""; };
149 | FAF19DDC1CC90F9200454324 /* 3.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = 3.gif; sourceTree = ""; };
150 | /* End PBXFileReference section */
151 |
152 | /* Begin PBXFrameworksBuildPhase section */
153 | 2301889624D95CA100EFE1BC /* Frameworks */ = {
154 | isa = PBXFrameworksBuildPhase;
155 | buildActionMask = 2147483647;
156 | files = (
157 | );
158 | runOnlyForDeploymentPostprocessing = 0;
159 | };
160 | 230188BB24D964FE00EFE1BC /* Frameworks */ = {
161 | isa = PBXFrameworksBuildPhase;
162 | buildActionMask = 2147483647;
163 | files = (
164 | 230188D024D965E800EFE1BC /* libSwiftyGifMac.a in Frameworks */,
165 | );
166 | runOnlyForDeploymentPostprocessing = 0;
167 | };
168 | 3B18BAF01E289899009C125A /* Frameworks */ = {
169 | isa = PBXFrameworksBuildPhase;
170 | buildActionMask = 2147483647;
171 | files = (
172 | );
173 | runOnlyForDeploymentPostprocessing = 0;
174 | };
175 | EF34CB4722A51591002A6C92 /* Frameworks */ = {
176 | isa = PBXFrameworksBuildPhase;
177 | buildActionMask = 2147483647;
178 | files = (
179 | EF34CB5F22A52909002A6C92 /* SnapshotTesting.framework in Frameworks */,
180 | EF34CB4F22A51591002A6C92 /* SwiftyGif.framework in Frameworks */,
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | };
184 | FA29E92F1CA9340E00E579D5 /* Frameworks */ = {
185 | isa = PBXFrameworksBuildPhase;
186 | buildActionMask = 2147483647;
187 | files = (
188 | 3B18BAFB1E289899009C125A /* SwiftyGif.framework in Frameworks */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXFrameworksBuildPhase section */
193 |
194 | /* Begin PBXGroup section */
195 | 230188BF24D964FE00EFE1BC /* SwiftyGifMacExample */ = {
196 | isa = PBXGroup;
197 | children = (
198 | 230188C024D964FE00EFE1BC /* AppDelegate.swift */,
199 | 230188C224D964FE00EFE1BC /* ViewController.swift */,
200 | 230188C424D964FF00EFE1BC /* Assets.xcassets */,
201 | 230188C624D964FF00EFE1BC /* Main.storyboard */,
202 | 230188C924D964FF00EFE1BC /* Info.plist */,
203 | 230188CA24D964FF00EFE1BC /* SwiftyGifMacExample.entitlements */,
204 | );
205 | path = SwiftyGifMacExample;
206 | sourceTree = "";
207 | };
208 | 3B18BAF51E289899009C125A /* SwiftyGif */ = {
209 | isa = PBXGroup;
210 | children = (
211 | 3B18BAF61E289899009C125A /* SwiftyGif.h */,
212 | 3B18BAF71E289899009C125A /* Info.plist */,
213 | );
214 | path = SwiftyGif;
215 | sourceTree = "";
216 | };
217 | EF34CB4422A514E7002A6C92 /* Images */ = {
218 | isa = PBXGroup;
219 | children = (
220 | EF34CB6222A54271002A6C92 /* 15MB_Einstein_rings_zoom.gif */,
221 | EF26CB8822A166E400E92383 /* no_property_dictionary.gif */,
222 | EF26CB8A22A167B100E92383 /* single_frame_Zt2012.gif */,
223 | EF26CB8C22A16DB900E92383 /* 20000x20000.gif */,
224 | );
225 | path = Images;
226 | sourceTree = "";
227 | };
228 | EF34CB4B22A51591002A6C92 /* SwiftyGifTests */ = {
229 | isa = PBXGroup;
230 | children = (
231 | EF34CB4C22A51591002A6C92 /* SwiftyGifTests.swift */,
232 | EF34CB4E22A51591002A6C92 /* Info.plist */,
233 | );
234 | path = SwiftyGifTests;
235 | sourceTree = "";
236 | };
237 | EF34CB5D22A52909002A6C92 /* Frameworks */ = {
238 | isa = PBXGroup;
239 | children = (
240 | EF34CB5E22A52909002A6C92 /* SnapshotTesting.framework */,
241 | );
242 | name = Frameworks;
243 | sourceTree = "";
244 | };
245 | FA29E9291CA9340E00E579D5 = {
246 | isa = PBXGroup;
247 | children = (
248 | FA29E9551CA9342F00E579D5 /* SwiftyGif */,
249 | FA29E9341CA9340E00E579D5 /* SwiftyGifExample */,
250 | 230188BF24D964FE00EFE1BC /* SwiftyGifMacExample */,
251 | FA29E9491CA9340F00E579D5 /* SwiftyGifTests */,
252 | 3B18BAF51E289899009C125A /* SwiftyGif */,
253 | EF34CB4B22A51591002A6C92 /* SwiftyGifTests */,
254 | FA29E9331CA9340E00E579D5 /* Products */,
255 | EF34CB5D22A52909002A6C92 /* Frameworks */,
256 | );
257 | sourceTree = "";
258 | };
259 | FA29E9331CA9340E00E579D5 /* Products */ = {
260 | isa = PBXGroup;
261 | children = (
262 | FA29E9321CA9340E00E579D5 /* SwiftyGifExample.app */,
263 | 3B18BAF41E289899009C125A /* SwiftyGif.framework */,
264 | EF34CB4A22A51591002A6C92 /* SwiftyGifTests.xctest */,
265 | 2301889824D95CA100EFE1BC /* libSwiftyGifMac.a */,
266 | 230188BE24D964FE00EFE1BC /* SwiftyGifMacExample.app */,
267 | );
268 | name = Products;
269 | sourceTree = "";
270 | };
271 | FA29E9341CA9340E00E579D5 /* SwiftyGifExample */ = {
272 | isa = PBXGroup;
273 | children = (
274 | FA57CC871CB3EDAA000F3476 /* AppDelegate.swift */,
275 | FA57CC881CB3EDAA000F3476 /* Assets.xcassets */,
276 | FA57CC891CB3EDAA000F3476 /* Base.lproj */,
277 | FA57CC8E1CB3EDAA000F3476 /* Cell.swift */,
278 | FA57CC8F1CB3EDAA000F3476 /* DetailController.swift */,
279 | FA57CC971CB3EDAA000F3476 /* ViewController.swift */,
280 | FAF19DDC1CC90F9200454324 /* 3.gif */,
281 | FAC7163A1CCA26AE0018A0CF /* 2.gif */,
282 | FAC7163B1CCA26AE0018A0CF /* 4.gif */,
283 | FA57CC951CB3EDAA000F3476 /* 5.gif */,
284 | FA57CC961CB3EDAA000F3476 /* Info.plist */,
285 | );
286 | path = SwiftyGifExample;
287 | sourceTree = "";
288 | };
289 | FA29E9491CA9340F00E579D5 /* SwiftyGifTests */ = {
290 | isa = PBXGroup;
291 | children = (
292 | EF34CB5A22A52876002A6C92 /* Cartfile.private */,
293 | EF34CB5922A52876002A6C92 /* Cartfile.resolved */,
294 | EF34CB4422A514E7002A6C92 /* Images */,
295 | FA29E94A1CA9340F00E579D5 /* SwiftyGifTests.swift */,
296 | FA29E94C1CA9340F00E579D5 /* Info.plist */,
297 | );
298 | path = SwiftyGifTests;
299 | sourceTree = "";
300 | };
301 | FA29E9551CA9342F00E579D5 /* SwiftyGif */ = {
302 | isa = PBXGroup;
303 | children = (
304 | FAE121E11CB2A3DD00960D00 /* SwiftyGifManager.swift */,
305 | FAE121E21CB2A3DD00960D00 /* UIImage+SwiftyGif.swift */,
306 | FAE121E31CB2A3DD00960D00 /* UIImageView+SwiftyGif.swift */,
307 | 230188A424D961CD00EFE1BC /* NSImage+SwiftyGif.swift */,
308 | 230188A224D9614900EFE1BC /* NSImageView+SwiftyGif.swift */,
309 | AD938874276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift */,
310 | 7A6E2EDB2B2278AE00A3ABF1 /* PrivacyInfo.xcprivacy */,
311 | 6F2FA2CE2C5417B600971497 /* SwiftyGifError.swift */,
312 | );
313 | path = SwiftyGif;
314 | sourceTree = "";
315 | };
316 | FA57CC891CB3EDAA000F3476 /* Base.lproj */ = {
317 | isa = PBXGroup;
318 | children = (
319 | FA57CC8A1CB3EDAA000F3476 /* LaunchScreen.storyboard */,
320 | FA57CC8C1CB3EDAA000F3476 /* Main.storyboard */,
321 | );
322 | path = Base.lproj;
323 | sourceTree = "";
324 | };
325 | /* End PBXGroup section */
326 |
327 | /* Begin PBXHeadersBuildPhase section */
328 | 2301889424D95CA100EFE1BC /* Headers */ = {
329 | isa = PBXHeadersBuildPhase;
330 | buildActionMask = 2147483647;
331 | files = (
332 | );
333 | runOnlyForDeploymentPostprocessing = 0;
334 | };
335 | 3B18BAF11E289899009C125A /* Headers */ = {
336 | isa = PBXHeadersBuildPhase;
337 | buildActionMask = 2147483647;
338 | files = (
339 | 3B18BAF81E289899009C125A /* SwiftyGif.h in Headers */,
340 | );
341 | runOnlyForDeploymentPostprocessing = 0;
342 | };
343 | /* End PBXHeadersBuildPhase section */
344 |
345 | /* Begin PBXNativeTarget section */
346 | 2301889724D95CA100EFE1BC /* SwiftyGifMac */ = {
347 | isa = PBXNativeTarget;
348 | buildConfigurationList = 230188A024D95CA100EFE1BC /* Build configuration list for PBXNativeTarget "SwiftyGifMac" */;
349 | buildPhases = (
350 | 2301889424D95CA100EFE1BC /* Headers */,
351 | 2301889524D95CA100EFE1BC /* Sources */,
352 | 2301889624D95CA100EFE1BC /* Frameworks */,
353 | );
354 | buildRules = (
355 | );
356 | dependencies = (
357 | );
358 | name = SwiftyGifMac;
359 | productName = SwiftyGifMac;
360 | productReference = 2301889824D95CA100EFE1BC /* libSwiftyGifMac.a */;
361 | productType = "com.apple.product-type.library.static";
362 | };
363 | 230188BD24D964FE00EFE1BC /* SwiftyGifMacExample */ = {
364 | isa = PBXNativeTarget;
365 | buildConfigurationList = 230188CB24D964FF00EFE1BC /* Build configuration list for PBXNativeTarget "SwiftyGifMacExample" */;
366 | buildPhases = (
367 | 230188BA24D964FE00EFE1BC /* Sources */,
368 | 230188BB24D964FE00EFE1BC /* Frameworks */,
369 | 230188BC24D964FE00EFE1BC /* Resources */,
370 | );
371 | buildRules = (
372 | );
373 | dependencies = (
374 | 230188CF24D965DE00EFE1BC /* PBXTargetDependency */,
375 | );
376 | name = SwiftyGifMacExample;
377 | productName = SwiftyGifMacExample;
378 | productReference = 230188BE24D964FE00EFE1BC /* SwiftyGifMacExample.app */;
379 | productType = "com.apple.product-type.application";
380 | };
381 | 3B18BAF31E289899009C125A /* SwiftyGif */ = {
382 | isa = PBXNativeTarget;
383 | buildConfigurationList = 3B18BAFD1E289899009C125A /* Build configuration list for PBXNativeTarget "SwiftyGif" */;
384 | buildPhases = (
385 | 3B18BAEF1E289899009C125A /* Sources */,
386 | 3B18BAF01E289899009C125A /* Frameworks */,
387 | 3B18BAF11E289899009C125A /* Headers */,
388 | 3B18BAF21E289899009C125A /* Resources */,
389 | );
390 | buildRules = (
391 | );
392 | dependencies = (
393 | );
394 | name = SwiftyGif;
395 | productName = SwiftyGif;
396 | productReference = 3B18BAF41E289899009C125A /* SwiftyGif.framework */;
397 | productType = "com.apple.product-type.framework";
398 | };
399 | EF34CB4922A51591002A6C92 /* SwiftyGifTests */ = {
400 | isa = PBXNativeTarget;
401 | buildConfigurationList = EF34CB5222A51591002A6C92 /* Build configuration list for PBXNativeTarget "SwiftyGifTests" */;
402 | buildPhases = (
403 | EF34CB4622A51591002A6C92 /* Sources */,
404 | EF34CB4722A51591002A6C92 /* Frameworks */,
405 | EF34CB4822A51591002A6C92 /* Resources */,
406 | EF34CB6022A52914002A6C92 /* CopyFiles */,
407 | );
408 | buildRules = (
409 | );
410 | dependencies = (
411 | EF34CB5122A51591002A6C92 /* PBXTargetDependency */,
412 | );
413 | name = SwiftyGifTests;
414 | productName = SwiftyGifTests;
415 | productReference = EF34CB4A22A51591002A6C92 /* SwiftyGifTests.xctest */;
416 | productType = "com.apple.product-type.bundle.unit-test";
417 | };
418 | FA29E9311CA9340E00E579D5 /* SwiftyGifExample */ = {
419 | isa = PBXNativeTarget;
420 | buildConfigurationList = FA29E94F1CA9340F00E579D5 /* Build configuration list for PBXNativeTarget "SwiftyGifExample" */;
421 | buildPhases = (
422 | FA29E92E1CA9340E00E579D5 /* Sources */,
423 | FA29E92F1CA9340E00E579D5 /* Frameworks */,
424 | FA29E9301CA9340E00E579D5 /* Resources */,
425 | 3B18BB001E289899009C125A /* Embed Frameworks */,
426 | );
427 | buildRules = (
428 | );
429 | dependencies = (
430 | 3B18BAFA1E289899009C125A /* PBXTargetDependency */,
431 | );
432 | name = SwiftyGifExample;
433 | productName = SwiftyGif;
434 | productReference = FA29E9321CA9340E00E579D5 /* SwiftyGifExample.app */;
435 | productType = "com.apple.product-type.application";
436 | };
437 | /* End PBXNativeTarget section */
438 |
439 | /* Begin PBXProject section */
440 | FA29E92A1CA9340E00E579D5 /* Project object */ = {
441 | isa = PBXProject;
442 | attributes = {
443 | LastSwiftUpdateCheck = 1150;
444 | LastUpgradeCheck = 1410;
445 | ORGANIZATIONNAME = alexiscreuzot;
446 | TargetAttributes = {
447 | 2301889724D95CA100EFE1BC = {
448 | CreatedOnToolsVersion = 11.5;
449 | DevelopmentTeam = 57W7KG4A63;
450 | ProvisioningStyle = Automatic;
451 | };
452 | 230188BD24D964FE00EFE1BC = {
453 | CreatedOnToolsVersion = 11.5;
454 | DevelopmentTeam = 57W7KG4A63;
455 | ProvisioningStyle = Automatic;
456 | };
457 | 3B18BAF31E289899009C125A = {
458 | CreatedOnToolsVersion = 8.2.1;
459 | LastSwiftMigration = 1020;
460 | ProvisioningStyle = Automatic;
461 | };
462 | EF34CB4922A51591002A6C92 = {
463 | CreatedOnToolsVersion = 10.2.1;
464 | ProvisioningStyle = Automatic;
465 | };
466 | FA29E9311CA9340E00E579D5 = {
467 | CreatedOnToolsVersion = 7.3;
468 | LastSwiftMigration = 1020;
469 | };
470 | };
471 | };
472 | buildConfigurationList = FA29E92D1CA9340E00E579D5 /* Build configuration list for PBXProject "SwiftyGif" */;
473 | compatibilityVersion = "Xcode 3.2";
474 | developmentRegion = en;
475 | hasScannedForEncodings = 0;
476 | knownRegions = (
477 | en,
478 | Base,
479 | );
480 | mainGroup = FA29E9291CA9340E00E579D5;
481 | productRefGroup = FA29E9331CA9340E00E579D5 /* Products */;
482 | projectDirPath = "";
483 | projectRoot = "";
484 | targets = (
485 | FA29E9311CA9340E00E579D5 /* SwiftyGifExample */,
486 | 3B18BAF31E289899009C125A /* SwiftyGif */,
487 | EF34CB4922A51591002A6C92 /* SwiftyGifTests */,
488 | 2301889724D95CA100EFE1BC /* SwiftyGifMac */,
489 | 230188BD24D964FE00EFE1BC /* SwiftyGifMacExample */,
490 | );
491 | };
492 | /* End PBXProject section */
493 |
494 | /* Begin PBXResourcesBuildPhase section */
495 | 230188BC24D964FE00EFE1BC /* Resources */ = {
496 | isa = PBXResourcesBuildPhase;
497 | buildActionMask = 2147483647;
498 | files = (
499 | 230188D224D9684100EFE1BC /* 3.gif in Resources */,
500 | 230188D324D9684100EFE1BC /* 2.gif in Resources */,
501 | 230188D524D9684100EFE1BC /* 5.gif in Resources */,
502 | 230188D424D9684100EFE1BC /* 4.gif in Resources */,
503 | 230188C524D964FF00EFE1BC /* Assets.xcassets in Resources */,
504 | 230188C824D964FF00EFE1BC /* Main.storyboard in Resources */,
505 | );
506 | runOnlyForDeploymentPostprocessing = 0;
507 | };
508 | 3B18BAF21E289899009C125A /* Resources */ = {
509 | isa = PBXResourcesBuildPhase;
510 | buildActionMask = 2147483647;
511 | files = (
512 | 7A6E2EDC2B2278AE00A3ABF1 /* PrivacyInfo.xcprivacy in Resources */,
513 | );
514 | runOnlyForDeploymentPostprocessing = 0;
515 | };
516 | EF34CB4822A51591002A6C92 /* Resources */ = {
517 | isa = PBXResourcesBuildPhase;
518 | buildActionMask = 2147483647;
519 | files = (
520 | EF34CB5522A524F6002A6C92 /* single_frame_Zt2012.gif in Resources */,
521 | EF34CB5622A524FA002A6C92 /* 20000x20000.gif in Resources */,
522 | EF34CB5722A524FE002A6C92 /* no_property_dictionary.gif in Resources */,
523 | EF34CB6322A54271002A6C92 /* 15MB_Einstein_rings_zoom.gif in Resources */,
524 | );
525 | runOnlyForDeploymentPostprocessing = 0;
526 | };
527 | FA29E9301CA9340E00E579D5 /* Resources */ = {
528 | isa = PBXResourcesBuildPhase;
529 | buildActionMask = 2147483647;
530 | files = (
531 | EF34CB6522A54AC0002A6C92 /* single_frame_Zt2012.gif in Resources */,
532 | FA57CC9B1CB3EDAA000F3476 /* Main.storyboard in Resources */,
533 | FA57CC991CB3EDAA000F3476 /* Assets.xcassets in Resources */,
534 | FAC7163C1CCA26AE0018A0CF /* 2.gif in Resources */,
535 | FAF19DE11CC90F9200454324 /* 3.gif in Resources */,
536 | EF34CB6722A54ACE002A6C92 /* 20000x20000.gif in Resources */,
537 | FA57CCA31CB3EDAA000F3476 /* 5.gif in Resources */,
538 | FAC7163D1CCA26AE0018A0CF /* 4.gif in Resources */,
539 | FA57CC9A1CB3EDAA000F3476 /* LaunchScreen.storyboard in Resources */,
540 | EF34CB6422A54ABC002A6C92 /* no_property_dictionary.gif in Resources */,
541 | );
542 | runOnlyForDeploymentPostprocessing = 0;
543 | };
544 | /* End PBXResourcesBuildPhase section */
545 |
546 | /* Begin PBXSourcesBuildPhase section */
547 | 2301889524D95CA100EFE1BC /* Sources */ = {
548 | isa = PBXSourcesBuildPhase;
549 | buildActionMask = 2147483647;
550 | files = (
551 | 230188A124D95CB800EFE1BC /* SwiftyGifManager.swift in Sources */,
552 | 230188A524D961D800EFE1BC /* NSImage+SwiftyGif.swift in Sources */,
553 | AD938876276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift in Sources */,
554 | 230188A324D9615700EFE1BC /* NSImageView+SwiftyGif.swift in Sources */,
555 | 6F2FA2D02C5417BA00971497 /* SwiftyGifError.swift in Sources */,
556 | );
557 | runOnlyForDeploymentPostprocessing = 0;
558 | };
559 | 230188BA24D964FE00EFE1BC /* Sources */ = {
560 | isa = PBXSourcesBuildPhase;
561 | buildActionMask = 2147483647;
562 | files = (
563 | 230188C324D964FE00EFE1BC /* ViewController.swift in Sources */,
564 | 230188C124D964FE00EFE1BC /* AppDelegate.swift in Sources */,
565 | );
566 | runOnlyForDeploymentPostprocessing = 0;
567 | };
568 | 3B18BAEF1E289899009C125A /* Sources */ = {
569 | isa = PBXSourcesBuildPhase;
570 | buildActionMask = 2147483647;
571 | files = (
572 | 3B18BB021E2898A5009C125A /* UIImage+SwiftyGif.swift in Sources */,
573 | 3B18BB031E2898A9009C125A /* UIImageView+SwiftyGif.swift in Sources */,
574 | AD938875276BBDBD00013AB1 /* ObjcAssociatedWeakObject.swift in Sources */,
575 | 3B18BB011E2898A1009C125A /* SwiftyGifManager.swift in Sources */,
576 | 6F2FA2CF2C5417B600971497 /* SwiftyGifError.swift in Sources */,
577 | );
578 | runOnlyForDeploymentPostprocessing = 0;
579 | };
580 | EF34CB4622A51591002A6C92 /* Sources */ = {
581 | isa = PBXSourcesBuildPhase;
582 | buildActionMask = 2147483647;
583 | files = (
584 | EF34CB4D22A51591002A6C92 /* SwiftyGifTests.swift in Sources */,
585 | );
586 | runOnlyForDeploymentPostprocessing = 0;
587 | };
588 | FA29E92E1CA9340E00E579D5 /* Sources */ = {
589 | isa = PBXSourcesBuildPhase;
590 | buildActionMask = 2147483647;
591 | files = (
592 | FA57CC981CB3EDAA000F3476 /* AppDelegate.swift in Sources */,
593 | FA57CC9C1CB3EDAA000F3476 /* Cell.swift in Sources */,
594 | FA57CCA51CB3EDAA000F3476 /* ViewController.swift in Sources */,
595 | FA57CC9D1CB3EDAA000F3476 /* DetailController.swift in Sources */,
596 | );
597 | runOnlyForDeploymentPostprocessing = 0;
598 | };
599 | /* End PBXSourcesBuildPhase section */
600 |
601 | /* Begin PBXTargetDependency section */
602 | 230188CF24D965DE00EFE1BC /* PBXTargetDependency */ = {
603 | isa = PBXTargetDependency;
604 | target = 2301889724D95CA100EFE1BC /* SwiftyGifMac */;
605 | targetProxy = 230188CE24D965DE00EFE1BC /* PBXContainerItemProxy */;
606 | };
607 | 3B18BAFA1E289899009C125A /* PBXTargetDependency */ = {
608 | isa = PBXTargetDependency;
609 | target = 3B18BAF31E289899009C125A /* SwiftyGif */;
610 | targetProxy = 3B18BAF91E289899009C125A /* PBXContainerItemProxy */;
611 | };
612 | EF34CB5122A51591002A6C92 /* PBXTargetDependency */ = {
613 | isa = PBXTargetDependency;
614 | target = 3B18BAF31E289899009C125A /* SwiftyGif */;
615 | targetProxy = EF34CB5022A51591002A6C92 /* PBXContainerItemProxy */;
616 | };
617 | /* End PBXTargetDependency section */
618 |
619 | /* Begin PBXVariantGroup section */
620 | 230188C624D964FF00EFE1BC /* Main.storyboard */ = {
621 | isa = PBXVariantGroup;
622 | children = (
623 | 230188C724D964FF00EFE1BC /* Base */,
624 | );
625 | name = Main.storyboard;
626 | sourceTree = "";
627 | };
628 | FA57CC8A1CB3EDAA000F3476 /* LaunchScreen.storyboard */ = {
629 | isa = PBXVariantGroup;
630 | children = (
631 | FA57CC8B1CB3EDAA000F3476 /* Base */,
632 | );
633 | name = LaunchScreen.storyboard;
634 | sourceTree = "";
635 | };
636 | FA57CC8C1CB3EDAA000F3476 /* Main.storyboard */ = {
637 | isa = PBXVariantGroup;
638 | children = (
639 | FA57CC8D1CB3EDAA000F3476 /* Base */,
640 | );
641 | name = Main.storyboard;
642 | sourceTree = "";
643 | };
644 | /* End PBXVariantGroup section */
645 |
646 | /* Begin XCBuildConfiguration section */
647 | 2301889E24D95CA100EFE1BC /* Debug */ = {
648 | isa = XCBuildConfiguration;
649 | buildSettings = {
650 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
651 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
652 | CLANG_ENABLE_OBJC_WEAK = YES;
653 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
654 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
655 | CODE_SIGN_STYLE = Automatic;
656 | DEAD_CODE_STRIPPING = YES;
657 | DEVELOPMENT_TEAM = 57W7KG4A63;
658 | EXECUTABLE_PREFIX = lib;
659 | GCC_C_LANGUAGE_STANDARD = gnu11;
660 | MACOSX_DEPLOYMENT_TARGET = 10.15;
661 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
662 | MTL_FAST_MATH = YES;
663 | PRODUCT_MODULE_NAME = SwiftyGif;
664 | PRODUCT_NAME = "$(TARGET_NAME)";
665 | SDKROOT = macosx;
666 | SKIP_INSTALL = YES;
667 | };
668 | name = Debug;
669 | };
670 | 2301889F24D95CA100EFE1BC /* Release */ = {
671 | isa = XCBuildConfiguration;
672 | buildSettings = {
673 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
674 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
675 | CLANG_ENABLE_OBJC_WEAK = YES;
676 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
677 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
678 | CODE_SIGN_STYLE = Automatic;
679 | DEAD_CODE_STRIPPING = YES;
680 | DEVELOPMENT_TEAM = 57W7KG4A63;
681 | EXECUTABLE_PREFIX = lib;
682 | GCC_C_LANGUAGE_STANDARD = gnu11;
683 | MACOSX_DEPLOYMENT_TARGET = 10.15;
684 | MTL_FAST_MATH = YES;
685 | PRODUCT_MODULE_NAME = SwiftyGif;
686 | PRODUCT_NAME = "$(TARGET_NAME)";
687 | SDKROOT = macosx;
688 | SKIP_INSTALL = YES;
689 | };
690 | name = Release;
691 | };
692 | 230188CC24D964FF00EFE1BC /* Debug */ = {
693 | isa = XCBuildConfiguration;
694 | buildSettings = {
695 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
696 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
697 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
698 | CLANG_ENABLE_OBJC_WEAK = YES;
699 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
700 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
701 | CODE_SIGN_ENTITLEMENTS = SwiftyGifMacExample/SwiftyGifMacExample.entitlements;
702 | CODE_SIGN_IDENTITY = "-";
703 | CODE_SIGN_STYLE = Automatic;
704 | COMBINE_HIDPI_IMAGES = YES;
705 | DEAD_CODE_STRIPPING = YES;
706 | DEVELOPMENT_TEAM = 57W7KG4A63;
707 | ENABLE_HARDENED_RUNTIME = YES;
708 | GCC_C_LANGUAGE_STANDARD = gnu11;
709 | INFOPLIST_FILE = SwiftyGifMacExample/Info.plist;
710 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
711 | MACOSX_DEPLOYMENT_TARGET = 10.15;
712 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
713 | MTL_FAST_MATH = YES;
714 | PRODUCT_BUNDLE_IDENTIFIER = me.carlorapisarda.SwiftyGifMacExample;
715 | PRODUCT_NAME = "$(TARGET_NAME)";
716 | SDKROOT = macosx;
717 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
718 | SWIFT_VERSION = 5.0;
719 | };
720 | name = Debug;
721 | };
722 | 230188CD24D964FF00EFE1BC /* Release */ = {
723 | isa = XCBuildConfiguration;
724 | buildSettings = {
725 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
726 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
727 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
728 | CLANG_ENABLE_OBJC_WEAK = YES;
729 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
730 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
731 | CODE_SIGN_ENTITLEMENTS = SwiftyGifMacExample/SwiftyGifMacExample.entitlements;
732 | CODE_SIGN_IDENTITY = "-";
733 | CODE_SIGN_STYLE = Automatic;
734 | COMBINE_HIDPI_IMAGES = YES;
735 | DEAD_CODE_STRIPPING = YES;
736 | DEVELOPMENT_TEAM = 57W7KG4A63;
737 | ENABLE_HARDENED_RUNTIME = YES;
738 | GCC_C_LANGUAGE_STANDARD = gnu11;
739 | INFOPLIST_FILE = SwiftyGifMacExample/Info.plist;
740 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
741 | MACOSX_DEPLOYMENT_TARGET = 10.15;
742 | MTL_FAST_MATH = YES;
743 | PRODUCT_BUNDLE_IDENTIFIER = me.carlorapisarda.SwiftyGifMacExample;
744 | PRODUCT_NAME = "$(TARGET_NAME)";
745 | SDKROOT = macosx;
746 | SWIFT_VERSION = 5.0;
747 | };
748 | name = Release;
749 | };
750 | 3B18BAFE1E289899009C125A /* Debug */ = {
751 | isa = XCBuildConfiguration;
752 | buildSettings = {
753 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
754 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
755 | CODE_SIGN_IDENTITY = "";
756 | CURRENT_PROJECT_VERSION = 1;
757 | DEFINES_MODULE = YES;
758 | DYLIB_COMPATIBILITY_VERSION = 1;
759 | DYLIB_CURRENT_VERSION = 1;
760 | DYLIB_INSTALL_NAME_BASE = "@rpath";
761 | INFOPLIST_FILE = SwiftyGif/Info.plist;
762 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
763 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
764 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
765 | PRODUCT_BUNDLE_IDENTIFIER = com.alexiscreuzot.SwiftyGif.SwiftyGif;
766 | PRODUCT_NAME = "$(TARGET_NAME)";
767 | SKIP_INSTALL = YES;
768 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
769 | SWIFT_VERSION = 5.0;
770 | VERSIONING_SYSTEM = "apple-generic";
771 | VERSION_INFO_PREFIX = "";
772 | };
773 | name = Debug;
774 | };
775 | 3B18BAFF1E289899009C125A /* Release */ = {
776 | isa = XCBuildConfiguration;
777 | buildSettings = {
778 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
779 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
780 | CODE_SIGN_IDENTITY = "";
781 | CURRENT_PROJECT_VERSION = 1;
782 | DEFINES_MODULE = YES;
783 | DYLIB_COMPATIBILITY_VERSION = 1;
784 | DYLIB_CURRENT_VERSION = 1;
785 | DYLIB_INSTALL_NAME_BASE = "@rpath";
786 | INFOPLIST_FILE = SwiftyGif/Info.plist;
787 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
788 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
789 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
790 | PRODUCT_BUNDLE_IDENTIFIER = com.alexiscreuzot.SwiftyGif.SwiftyGif;
791 | PRODUCT_NAME = "$(TARGET_NAME)";
792 | SKIP_INSTALL = YES;
793 | SWIFT_VERSION = 5.0;
794 | VERSIONING_SYSTEM = "apple-generic";
795 | VERSION_INFO_PREFIX = "";
796 | };
797 | name = Release;
798 | };
799 | EF34CB5322A51591002A6C92 /* Debug */ = {
800 | isa = XCBuildConfiguration;
801 | buildSettings = {
802 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
803 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
804 | CLANG_ENABLE_OBJC_WEAK = YES;
805 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
806 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
807 | CODE_SIGN_IDENTITY = "iPhone Developer";
808 | CODE_SIGN_STYLE = Automatic;
809 | FRAMEWORK_SEARCH_PATHS = (
810 | "$(inherited)",
811 | "$(PROJECT_DIR)/Carthage/Build/iOS",
812 | );
813 | GCC_C_LANGUAGE_STANDARD = gnu11;
814 | INFOPLIST_FILE = SwiftyGifTests/Info.plist;
815 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
816 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
817 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
818 | MTL_FAST_MATH = YES;
819 | PRODUCT_BUNDLE_IDENTIFIER = BillChan.SwiftyGifTests;
820 | PRODUCT_NAME = "$(TARGET_NAME)";
821 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
822 | SWIFT_VERSION = 5.0;
823 | TARGETED_DEVICE_FAMILY = "1,2";
824 | };
825 | name = Debug;
826 | };
827 | EF34CB5422A51591002A6C92 /* Release */ = {
828 | isa = XCBuildConfiguration;
829 | buildSettings = {
830 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
831 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
832 | CLANG_ENABLE_OBJC_WEAK = YES;
833 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
834 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
835 | CODE_SIGN_IDENTITY = "iPhone Developer";
836 | CODE_SIGN_STYLE = Automatic;
837 | FRAMEWORK_SEARCH_PATHS = (
838 | "$(inherited)",
839 | "$(PROJECT_DIR)/Carthage/Build/iOS",
840 | );
841 | GCC_C_LANGUAGE_STANDARD = gnu11;
842 | INFOPLIST_FILE = SwiftyGifTests/Info.plist;
843 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
844 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
845 | MTL_FAST_MATH = YES;
846 | PRODUCT_BUNDLE_IDENTIFIER = BillChan.SwiftyGifTests;
847 | PRODUCT_NAME = "$(TARGET_NAME)";
848 | SWIFT_VERSION = 5.0;
849 | TARGETED_DEVICE_FAMILY = "1,2";
850 | };
851 | name = Release;
852 | };
853 | FA29E94D1CA9340F00E579D5 /* Debug */ = {
854 | isa = XCBuildConfiguration;
855 | buildSettings = {
856 | ALWAYS_SEARCH_USER_PATHS = NO;
857 | CLANG_ANALYZER_NONNULL = YES;
858 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
859 | CLANG_CXX_LIBRARY = "libc++";
860 | CLANG_ENABLE_MODULES = YES;
861 | CLANG_ENABLE_OBJC_ARC = YES;
862 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
863 | CLANG_WARN_BOOL_CONVERSION = YES;
864 | CLANG_WARN_COMMA = YES;
865 | CLANG_WARN_CONSTANT_CONVERSION = YES;
866 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
867 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
868 | CLANG_WARN_EMPTY_BODY = YES;
869 | CLANG_WARN_ENUM_CONVERSION = YES;
870 | CLANG_WARN_INFINITE_RECURSION = YES;
871 | CLANG_WARN_INT_CONVERSION = YES;
872 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
873 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
874 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
875 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
876 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
877 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
878 | CLANG_WARN_STRICT_PROTOTYPES = YES;
879 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
880 | CLANG_WARN_UNREACHABLE_CODE = YES;
881 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
882 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
883 | COPY_PHASE_STRIP = NO;
884 | DEBUG_INFORMATION_FORMAT = dwarf;
885 | ENABLE_STRICT_OBJC_MSGSEND = YES;
886 | ENABLE_TESTABILITY = YES;
887 | GCC_C_LANGUAGE_STANDARD = gnu99;
888 | GCC_DYNAMIC_NO_PIC = NO;
889 | GCC_NO_COMMON_BLOCKS = YES;
890 | GCC_OPTIMIZATION_LEVEL = 0;
891 | GCC_PREPROCESSOR_DEFINITIONS = (
892 | "DEBUG=1",
893 | "$(inherited)",
894 | );
895 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
896 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
897 | GCC_WARN_UNDECLARED_SELECTOR = YES;
898 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
899 | GCC_WARN_UNUSED_FUNCTION = YES;
900 | GCC_WARN_UNUSED_VARIABLE = YES;
901 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
902 | MTL_ENABLE_DEBUG_INFO = YES;
903 | ONLY_ACTIVE_ARCH = YES;
904 | SDKROOT = iphoneos;
905 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
906 | SWIFT_VERSION = 5.0;
907 | TARGETED_DEVICE_FAMILY = "1,2";
908 | };
909 | name = Debug;
910 | };
911 | FA29E94E1CA9340F00E579D5 /* Release */ = {
912 | isa = XCBuildConfiguration;
913 | buildSettings = {
914 | ALWAYS_SEARCH_USER_PATHS = NO;
915 | CLANG_ANALYZER_NONNULL = YES;
916 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
917 | CLANG_CXX_LIBRARY = "libc++";
918 | CLANG_ENABLE_MODULES = YES;
919 | CLANG_ENABLE_OBJC_ARC = YES;
920 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
921 | CLANG_WARN_BOOL_CONVERSION = YES;
922 | CLANG_WARN_COMMA = YES;
923 | CLANG_WARN_CONSTANT_CONVERSION = YES;
924 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
925 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
926 | CLANG_WARN_EMPTY_BODY = YES;
927 | CLANG_WARN_ENUM_CONVERSION = YES;
928 | CLANG_WARN_INFINITE_RECURSION = YES;
929 | CLANG_WARN_INT_CONVERSION = YES;
930 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
931 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
932 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
933 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
934 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
935 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
936 | CLANG_WARN_STRICT_PROTOTYPES = YES;
937 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
938 | CLANG_WARN_UNREACHABLE_CODE = YES;
939 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
940 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
941 | COPY_PHASE_STRIP = NO;
942 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
943 | ENABLE_NS_ASSERTIONS = NO;
944 | ENABLE_STRICT_OBJC_MSGSEND = YES;
945 | GCC_C_LANGUAGE_STANDARD = gnu99;
946 | GCC_NO_COMMON_BLOCKS = YES;
947 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
948 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
949 | GCC_WARN_UNDECLARED_SELECTOR = YES;
950 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
951 | GCC_WARN_UNUSED_FUNCTION = YES;
952 | GCC_WARN_UNUSED_VARIABLE = YES;
953 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
954 | MTL_ENABLE_DEBUG_INFO = NO;
955 | SDKROOT = iphoneos;
956 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
957 | SWIFT_VERSION = 5.0;
958 | TARGETED_DEVICE_FAMILY = "1,2";
959 | VALIDATE_PRODUCT = YES;
960 | };
961 | name = Release;
962 | };
963 | FA29E9501CA9340F00E579D5 /* Debug */ = {
964 | isa = XCBuildConfiguration;
965 | buildSettings = {
966 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
967 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
968 | DEVELOPMENT_TEAM = "";
969 | INFOPLIST_FILE = "$(SRCROOT)/SwiftyGifExample/Info.plist";
970 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
971 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
972 | PRODUCT_BUNDLE_IDENTIFIER = com.alexiscreuzot.SwiftyGif;
973 | PRODUCT_NAME = "$(TARGET_NAME)";
974 | SWIFT_VERSION = 5.0;
975 | };
976 | name = Debug;
977 | };
978 | FA29E9511CA9340F00E579D5 /* Release */ = {
979 | isa = XCBuildConfiguration;
980 | buildSettings = {
981 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
982 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
983 | DEVELOPMENT_TEAM = "";
984 | INFOPLIST_FILE = "$(SRCROOT)/SwiftyGifExample/Info.plist";
985 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
986 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
987 | PRODUCT_BUNDLE_IDENTIFIER = com.alexiscreuzot.SwiftyGif;
988 | PRODUCT_NAME = "$(TARGET_NAME)";
989 | SWIFT_VERSION = 5.0;
990 | };
991 | name = Release;
992 | };
993 | /* End XCBuildConfiguration section */
994 |
995 | /* Begin XCConfigurationList section */
996 | 230188A024D95CA100EFE1BC /* Build configuration list for PBXNativeTarget "SwiftyGifMac" */ = {
997 | isa = XCConfigurationList;
998 | buildConfigurations = (
999 | 2301889E24D95CA100EFE1BC /* Debug */,
1000 | 2301889F24D95CA100EFE1BC /* Release */,
1001 | );
1002 | defaultConfigurationIsVisible = 0;
1003 | defaultConfigurationName = Release;
1004 | };
1005 | 230188CB24D964FF00EFE1BC /* Build configuration list for PBXNativeTarget "SwiftyGifMacExample" */ = {
1006 | isa = XCConfigurationList;
1007 | buildConfigurations = (
1008 | 230188CC24D964FF00EFE1BC /* Debug */,
1009 | 230188CD24D964FF00EFE1BC /* Release */,
1010 | );
1011 | defaultConfigurationIsVisible = 0;
1012 | defaultConfigurationName = Release;
1013 | };
1014 | 3B18BAFD1E289899009C125A /* Build configuration list for PBXNativeTarget "SwiftyGif" */ = {
1015 | isa = XCConfigurationList;
1016 | buildConfigurations = (
1017 | 3B18BAFE1E289899009C125A /* Debug */,
1018 | 3B18BAFF1E289899009C125A /* Release */,
1019 | );
1020 | defaultConfigurationIsVisible = 0;
1021 | defaultConfigurationName = Release;
1022 | };
1023 | EF34CB5222A51591002A6C92 /* Build configuration list for PBXNativeTarget "SwiftyGifTests" */ = {
1024 | isa = XCConfigurationList;
1025 | buildConfigurations = (
1026 | EF34CB5322A51591002A6C92 /* Debug */,
1027 | EF34CB5422A51591002A6C92 /* Release */,
1028 | );
1029 | defaultConfigurationIsVisible = 0;
1030 | defaultConfigurationName = Release;
1031 | };
1032 | FA29E92D1CA9340E00E579D5 /* Build configuration list for PBXProject "SwiftyGif" */ = {
1033 | isa = XCConfigurationList;
1034 | buildConfigurations = (
1035 | FA29E94D1CA9340F00E579D5 /* Debug */,
1036 | FA29E94E1CA9340F00E579D5 /* Release */,
1037 | );
1038 | defaultConfigurationIsVisible = 0;
1039 | defaultConfigurationName = Release;
1040 | };
1041 | FA29E94F1CA9340F00E579D5 /* Build configuration list for PBXNativeTarget "SwiftyGifExample" */ = {
1042 | isa = XCConfigurationList;
1043 | buildConfigurations = (
1044 | FA29E9501CA9340F00E579D5 /* Debug */,
1045 | FA29E9511CA9340F00E579D5 /* Release */,
1046 | );
1047 | defaultConfigurationIsVisible = 0;
1048 | defaultConfigurationName = Release;
1049 | };
1050 | /* End XCConfigurationList section */
1051 | };
1052 | rootObject = FA29E92A1CA9340E00E579D5 /* Project object */;
1053 | }
1054 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/xcshareddata/xcschemes/SwiftyGif.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/xcshareddata/xcschemes/SwiftyGifExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
64 |
70 |
71 |
72 |
73 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/xcshareddata/xcschemes/SwiftyGifMacExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
61 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/xcuserdata/alex.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | SwiftyGif.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | SwiftyGifExample.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 | SwiftyGifMac.xcscheme_^#shared#^_
18 |
19 | orderHint
20 | 3
21 |
22 | SwiftyGifMacExample.xcscheme_^#shared#^_
23 |
24 | orderHint
25 | 2
26 |
27 |
28 | SuppressBuildableAutocreation
29 |
30 | 3B18BAF31E289899009C125A
31 |
32 | primary
33 |
34 |
35 | FA29E9311CA9340E00E579D5
36 |
37 | primary
38 |
39 |
40 | FA29E9451CA9340F00E579D5
41 |
42 | primary
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/SwiftyGif.xcodeproj/xcuserdata/travasonig.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SuppressBuildableAutocreation
6 |
7 | FA29E9311CA9340E00E579D5
8 |
9 | primary
10 |
11 |
12 | FA29E9451CA9340F00E579D5
13 |
14 | primary
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SwiftyGif/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/SwiftyGif/NSImage+SwiftyGif.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSImage+SwiftyGif.swift
3 | //
4 |
5 | #if os(macOS)
6 |
7 | import ImageIO
8 | import AppKit
9 |
10 | public typealias GifLevelOfIntegrity = Float
11 |
12 | extension GifLevelOfIntegrity {
13 | public static let highestNoFrameSkipping: GifLevelOfIntegrity = 1
14 | public static let `default`: GifLevelOfIntegrity = 1
15 | public static let lowForManyGifs: GifLevelOfIntegrity = 0.5
16 | public static let lowForTooManyGifs: GifLevelOfIntegrity = 0.2
17 | public static let superLowForSlideShow: GifLevelOfIntegrity = 0.1
18 | }
19 |
20 | enum GifParseError: Error {
21 | case invalidFilename
22 | case noImages
23 | case noProperties
24 | case noGifDictionary
25 | case noTimingInfo
26 | }
27 |
28 | extension GifParseError: LocalizedError {
29 | public var errorDescription: String? {
30 | switch self {
31 | case .invalidFilename:
32 | return "Invalid file name"
33 | case .noImages,.noProperties, .noGifDictionary,.noTimingInfo:
34 | return "Invalid gif file "
35 | }
36 | }
37 | }
38 |
39 | public extension NSImage {
40 | /// Convenience initializer. Creates a gif with its backing data.
41 | ///
42 | /// - Parameter imageData: The actual image data, can be GIF or some other format
43 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
44 | convenience init?(imageData:Data, levelOfIntegrity: GifLevelOfIntegrity = .default) throws {
45 | do {
46 | try self.init(gifData: imageData, levelOfIntegrity: levelOfIntegrity)
47 | } catch {
48 | self.init(data: imageData)
49 | }
50 | }
51 |
52 | /// Convenience initializer. Creates a image with its backing data.
53 | ///
54 | /// - Parameter imageName: Filename
55 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
56 | convenience init?(imageName: String, levelOfIntegrity: GifLevelOfIntegrity = .default, bundle: Bundle = Bundle.main) throws {
57 | self.init()
58 |
59 | do {
60 | try setGif(imageName, levelOfIntegrity: levelOfIntegrity, bundle: bundle)
61 | } catch {
62 | self.init(named: imageName)
63 | }
64 | }
65 | }
66 |
67 | // MARK: - Inits
68 |
69 | public extension NSImage {
70 |
71 | /// Convenience initializer. Creates a gif with its backing data.
72 | ///
73 | /// - Parameter gifData: The actual gif data
74 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
75 | convenience init(gifData:Data, levelOfIntegrity: GifLevelOfIntegrity = .default) throws {
76 | self.init()
77 | try setGifFromData(gifData, levelOfIntegrity: levelOfIntegrity)
78 | }
79 |
80 | /// Convenience initializer. Creates a gif with its backing data.
81 | ///
82 | /// - Parameter gifName: Filename
83 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
84 | convenience init(gifName: String, levelOfIntegrity: GifLevelOfIntegrity = .default) throws {
85 | self.init()
86 | try setGif(gifName, levelOfIntegrity: levelOfIntegrity)
87 | }
88 |
89 | /// Set backing data for this gif. Overwrites any existing data.
90 | ///
91 | /// - Parameter data: The actual gif data
92 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
93 | func setGifFromData(_ data: Data, levelOfIntegrity: GifLevelOfIntegrity) throws {
94 | guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return }
95 | self.imageSource = imageSource
96 | imageData = data
97 |
98 | calculateFrameDelay(try delayTimes(imageSource), levelOfIntegrity: levelOfIntegrity)
99 | calculateFrameSize()
100 | }
101 |
102 | /// Set backing data for this gif. Overwrites any existing data.
103 | ///
104 | /// - Parameter name: Filename
105 | func setGif(_ name: String) throws {
106 | try setGif(name, levelOfIntegrity: .default)
107 | }
108 |
109 | /// Check the number of frame for this gif
110 | ///
111 | /// - Return number of frames
112 | func framesCount() -> Int {
113 | return displayOrder?.count ?? 0
114 | }
115 |
116 | private func giflog(_ msg: String) {
117 | print("SwiftyGIF: \(msg)")
118 | }
119 |
120 | /// Set backing data for this gif. Overwrites any existing data.
121 | ///
122 | /// - Parameter name: Filename
123 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
124 | func setGif(_ name: String, levelOfIntegrity: GifLevelOfIntegrity, bundle: Bundle = Bundle.main) throws {
125 |
126 | if let url = bundle.url(forResource: name,
127 | withExtension: name.pathExtension() == "gif" ? "" : "gif") {
128 | if let data = try? Data(contentsOf: url) {
129 | try setGifFromData(data, levelOfIntegrity: levelOfIntegrity)
130 | }
131 | } else {
132 | throw GifParseError.invalidFilename
133 | }
134 | }
135 |
136 | func clear() {
137 | imageData = nil
138 | imageSource = nil
139 | displayOrder = nil
140 | imageCount = nil
141 | imageSize = nil
142 | displayRefreshFactor = nil
143 | }
144 |
145 | // MARK: Logic
146 |
147 | private func convertToDelay(_ pointer:UnsafeRawPointer?) -> Float? {
148 | if pointer == nil {
149 | return nil
150 | }
151 |
152 | return unsafeBitCast(pointer, to:AnyObject.self).floatValue
153 | }
154 |
155 | /// Get delay times for each frames
156 | ///
157 | /// - Parameter imageSource: reference to the gif image source
158 | /// - Returns array of delays
159 | private func delayTimes(_ imageSource:CGImageSource) throws -> [Float] {
160 | let imageCount = CGImageSourceGetCount(imageSource)
161 |
162 | guard imageCount > 0 else {
163 | throw GifParseError.noImages
164 | }
165 |
166 | var imageProperties = [CFDictionary]()
167 |
168 | for i in 0.. CFDictionary in
177 | let key = Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()
178 | let value = CFDictionaryGetValue(dict, key)
179 |
180 | if value == nil {
181 | throw GifParseError.noGifDictionary
182 | }
183 |
184 | return unsafeBitCast(value, to: CFDictionary.self)
185 | }
186 |
187 | let EPS:Float = 1e-6
188 |
189 | let frameDelays:[Float] = try frameProperties.map() {
190 | let unclampedKey = Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()
191 | let unclampedPointer:UnsafeRawPointer? = CFDictionaryGetValue($0, unclampedKey)
192 |
193 | if let value = convertToDelay(unclampedPointer), value >= EPS {
194 | return value
195 | }
196 |
197 | let clampedKey = Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()
198 | let clampedPointer:UnsafeRawPointer? = CFDictionaryGetValue($0, clampedKey)
199 |
200 | if let value = convertToDelay(clampedPointer) {
201 | return value
202 | }
203 |
204 | throw GifParseError.noTimingInfo
205 | }
206 |
207 | return frameDelays
208 | }
209 |
210 | /// Compute backing data for this gif
211 | ///
212 | /// - Parameter delaysArray: decoded delay times for this gif
213 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
214 | private func calculateFrameDelay(_ delaysArray: [Float], levelOfIntegrity: GifLevelOfIntegrity) {
215 | let levelOfIntegrity = max(0, min(1, levelOfIntegrity))
216 | var delays = delaysArray
217 |
218 | // Factors send to CADisplayLink.frameInterval
219 | let displayRefreshFactors = [60, 30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1]
220 |
221 | // maxFramePerSecond,default is 60
222 | let maxFramePerSecond = displayRefreshFactors[0]
223 |
224 | // frame numbers per second
225 | let displayRefreshRates = displayRefreshFactors.map { maxFramePerSecond / $0 }
226 |
227 | // time interval per frame
228 | let displayRefreshDelayTime = displayRefreshRates.map { 1 / Float($0) }
229 |
230 | // calculate the time when each frame should be displayed at(start at 0)
231 | for i in delays.indices.dropFirst() {
232 | delays[i] += delays[i - 1]
233 | }
234 |
235 | //find the appropriate Factors then BREAK
236 | for (i, delayTime) in displayRefreshDelayTime.enumerated() {
237 | let displayPosition = delays.map { Int($0 / delayTime) }
238 | var frameLoseCount: Float = 0
239 |
240 | for j in displayPosition.indices.dropFirst() where displayPosition[j] == displayPosition[j - 1] {
241 | frameLoseCount += 1
242 | }
243 |
244 | if displayPosition.first == 0 {
245 | frameLoseCount += 1
246 | }
247 |
248 | if frameLoseCount <= Float(displayPosition.count) * (1 - levelOfIntegrity) || i == displayRefreshDelayTime.count - 1 {
249 | imageCount = displayPosition.last
250 | displayRefreshFactor = displayRefreshFactors[i]
251 | displayOrder = []
252 | var oldIndex = 0
253 | var newIndex = 1
254 | let imageCount = self.imageCount ?? 0
255 |
256 | while newIndex <= imageCount && oldIndex < displayPosition.count {
257 | if newIndex <= displayPosition[oldIndex] {
258 | displayOrder?.append(oldIndex)
259 | newIndex += 1
260 | } else {
261 | oldIndex += 1
262 | }
263 | }
264 |
265 | break
266 | }
267 | }
268 | }
269 |
270 | /// Compute frame size for this gif
271 | private func calculateFrameSize(){
272 | guard let imageSource = imageSource,
273 | let imageCount = imageCount,
274 | let cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
275 | return
276 | }
277 |
278 |
279 | let image = NSImage(cgImage: cgImage, size: .zero)
280 | imageSize = Int(image.size.height * image.size.width * 4) * imageCount / 1_000_000
281 | }
282 | }
283 |
284 | // MARK: - Properties
285 |
286 | private let _imageSourceKey = malloc(4)
287 | private let _displayRefreshFactorKey = malloc(4)
288 | private let _imageSizeKey = malloc(4)
289 | private let _imageCountKey = malloc(4)
290 | private let _displayOrderKey = malloc(4)
291 | private let _imageDataKey = malloc(4)
292 |
293 | public extension NSImage {
294 |
295 | var imageSource: CGImageSource? {
296 | get {
297 | let result = objc_getAssociatedObject(self, _imageSourceKey!)
298 | return result == nil ? nil : (result as! CGImageSource)
299 | }
300 | set {
301 | objc_setAssociatedObject(self, _imageSourceKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
302 | }
303 | }
304 |
305 | var displayRefreshFactor: Int?{
306 | get { return objc_getAssociatedObject(self, _displayRefreshFactorKey!) as? Int }
307 | set { objc_setAssociatedObject(self, _displayRefreshFactorKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
308 | }
309 |
310 | var imageSize: Int?{
311 | get { return objc_getAssociatedObject(self, _imageSizeKey!) as? Int }
312 | set { objc_setAssociatedObject(self, _imageSizeKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
313 | }
314 |
315 | var imageCount: Int?{
316 | get { return objc_getAssociatedObject(self, _imageCountKey!) as? Int }
317 | set { objc_setAssociatedObject(self, _imageCountKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
318 | }
319 |
320 | var displayOrder: [Int]?{
321 | get { return objc_getAssociatedObject(self, _displayOrderKey!) as? [Int] }
322 | set { objc_setAssociatedObject(self, _displayOrderKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
323 | }
324 |
325 | var imageData:Data? {
326 | get {
327 | let result = objc_getAssociatedObject(self, _imageDataKey!)
328 | return result == nil ? nil : (result as? Data)
329 | }
330 | set {
331 | objc_setAssociatedObject(self, _imageDataKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
332 | }
333 | }
334 | }
335 |
336 | extension String {
337 | fileprivate func pathExtension() -> String {
338 | return (self as NSString).pathExtension
339 | }
340 | }
341 |
342 | #endif
343 |
--------------------------------------------------------------------------------
/SwiftyGif/NSImageView+SwiftyGif.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSImageView+SwiftyGif.swift
3 | //
4 |
5 | #if os(macOS)
6 |
7 | import ImageIO
8 | import AppKit
9 |
10 | @objc public protocol SwiftyGifDelegate {
11 | @objc optional func gifDidStart(sender: NSImageView)
12 | @objc optional func gifDidLoop(sender: NSImageView)
13 | @objc optional func gifDidStop(sender: NSImageView)
14 | @objc optional func gifURLDidFinish(sender: NSImageView)
15 | @objc optional func gifURLDidFail(sender: NSImageView, url: URL, error: Error?)
16 | }
17 |
18 | public extension NSImageView {
19 | /// Set an image and a manager to an existing NSImageView. If the image is not an GIF image, set it in normal way and remove self form SwiftyGifManager
20 | ///
21 | /// WARNING : this overwrite any previous gif.
22 | /// - Parameter gifImage: The NSImage containing the gif backing data
23 | /// - Parameter manager: The manager to handle the gif display
24 | /// - Parameter loopCount: The number of loops we want for this gif. -1 means infinite.
25 | func setImage(_ image: NSImage, manager: SwiftyGifManager = .defaultManager, loopCount: Int = -1) {
26 | if let _ = image.imageData {
27 | setGifImage(image, manager: manager, loopCount: loopCount)
28 | } else {
29 | manager.deleteImageView(self)
30 | self.image = image
31 | }
32 | }
33 | }
34 |
35 | public extension NSImageView {
36 |
37 | // MARK: - Inits
38 |
39 | /// Convenience initializer. Creates a gif holder (defaulted to infinite loop).
40 | ///
41 | /// - Parameter gifImage: The NSImage containing the gif backing data
42 | /// - Parameter manager: The manager to handle the gif display
43 | convenience init(gifImage: NSImage, manager: SwiftyGifManager = .defaultManager, loopCount: Int = -1) {
44 | self.init()
45 | setGifImage(gifImage,manager: manager, loopCount: loopCount)
46 | }
47 |
48 | /// Convenience initializer. Creates a gif holder (defaulted to infinite loop).
49 | ///
50 | /// - Parameter gifImage: The NSImage containing the gif backing data
51 | /// - Parameter manager: The manager to handle the gif display
52 | convenience init(
53 | gifURL: URL,
54 | manager: SwiftyGifManager = .defaultManager,
55 | loopCount: Int = -1,
56 | callback: @escaping (Result) -> Void = { _ in }
57 | ) {
58 | self.init()
59 | setGifFromURL(gifURL, manager: manager, loopCount: loopCount, callback: callback)
60 | }
61 |
62 | /// Set a gif image and a manager to an existing NSImageView.
63 | ///
64 | /// WARNING : this overwrite any previous gif.
65 | /// - Parameter gifImage: The NSImage containing the gif backing data
66 | /// - Parameter manager: The manager to handle the gif display
67 | /// - Parameter loopCount: The number of loops we want for this gif. -1 means infinite.
68 | func setGifImage(_ gifImage: NSImage, manager: SwiftyGifManager = .defaultManager, loopCount: Int = -1) {
69 | if let imageData = gifImage.imageData, (gifImage.imageCount ?? 0) < 1 {
70 | image = NSImage(data: imageData)
71 | return
72 | }
73 |
74 | self.loopCount = loopCount
75 | self.gifImage = gifImage
76 | animationManager = manager
77 | syncFactor = 0
78 | displayOrderIndex = 0
79 | cache = NSCache()
80 | haveCache = false
81 |
82 | if let source = gifImage.imageSource, let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) {
83 | currentImage = NSImage(cgImage: cgImage, size: .zero)
84 |
85 | if manager.addImageView(self) {
86 | startDisplay()
87 | startAnimatingGif()
88 | }
89 | }
90 | }
91 | }
92 |
93 | // MARK: - Download gif
94 |
95 | public extension NSImageView {
96 |
97 | /// Download gif image and sets it.
98 | ///
99 | /// - Parameters:
100 | /// - url: The URL pointing to the gif data
101 | /// - manager: The manager to handle the gif display
102 | /// - loopCount: The number of loops we want for this gif. -1 means infinite.
103 | /// - showLoader: Show UIActivityIndicatorView or not
104 | /// - Returns: An URL session task. Note: You can cancel the downloading task if it needed.
105 | @discardableResult
106 | func setGifFromURL(_ url: URL,
107 | manager: SwiftyGifManager = .defaultManager,
108 | loopCount: Int = -1,
109 | levelOfIntegrity: GifLevelOfIntegrity = .default,
110 | session: URLSession = URLSession.shared,
111 | showLoader: Bool = true,
112 | customLoader: NSView? = nil,
113 | callback: @escaping (Result) -> Void = {_ in }
114 | ) -> URLSessionDataTask? {
115 |
116 | if let data = manager.remoteCache[url] {
117 | self.parseDownloadedGif(url: url,
118 | data: data,
119 | error: nil,
120 | manager: manager,
121 | loopCount: loopCount,
122 | levelOfIntegrity: levelOfIntegrity,
123 | callback: callback
124 | )
125 | return nil
126 | }
127 |
128 | stopAnimatingGif()
129 |
130 | let loader: NSView? = showLoader ? createLoader(from: customLoader) : nil
131 |
132 | let task = session.dataTask(with: url) { [weak self] data, _, error in
133 | DispatchQueue.main.async {
134 | loader?.removeFromSuperview()
135 | self?.parseDownloadedGif(url: url,
136 | data: data,
137 | error: error,
138 | manager: manager,
139 | loopCount: loopCount,
140 | levelOfIntegrity: levelOfIntegrity,
141 | callback: callback)
142 | }
143 | }
144 |
145 | task.resume()
146 |
147 | return task
148 | }
149 |
150 | private func createLoader(from view: NSView? = nil) -> NSView {
151 | let loader = view ?? {
152 | let indicator = NSProgressIndicator()
153 | indicator.style = .spinning
154 | return indicator
155 | }()
156 |
157 | addSubview(loader)
158 | loader.translatesAutoresizingMaskIntoConstraints = false
159 |
160 | addConstraint(NSLayoutConstraint(
161 | item: loader,
162 | attribute: .centerX,
163 | relatedBy: .equal,
164 | toItem: self,
165 | attribute: .centerX,
166 | multiplier: 1,
167 | constant: 0))
168 |
169 | addConstraint(NSLayoutConstraint(
170 | item: loader,
171 | attribute: .centerY,
172 | relatedBy: .equal,
173 | toItem: self,
174 | attribute: .centerY,
175 | multiplier: 1,
176 | constant: 0))
177 |
178 | (loader as? NSProgressIndicator)?.startAnimation(nil)
179 |
180 | return loader
181 | }
182 |
183 | private func parseDownloadedGif(url: URL,
184 | data: Data?,
185 | error: Error?,
186 | manager: SwiftyGifManager,
187 | loopCount: Int,
188 | levelOfIntegrity: GifLevelOfIntegrity,
189 | callback: (Result) -> Void) {
190 | guard let data = data else {
191 | report(url: url, error: error)
192 | callback(.failure(error ?? SwiftyGifError.noGifData))
193 | return
194 | }
195 |
196 | do {
197 | let image = try NSImage(gifData: data, levelOfIntegrity: levelOfIntegrity)
198 | manager.remoteCache[url] = data
199 | setGifImage(image, manager: manager, loopCount: loopCount)
200 | startAnimatingGif()
201 | delegate?.gifURLDidFinish?(sender: self)
202 | callback(.success(data))
203 | } catch {
204 | report(url: url, error: error)
205 | callback(.failure(error))
206 | }
207 | }
208 |
209 | private func report(url: URL, error: Error?) {
210 | delegate?.gifURLDidFail?(sender: self, url: url, error: error)
211 | }
212 | }
213 |
214 | // MARK: - Logic
215 |
216 | public extension NSImageView {
217 |
218 | /// Start displaying the gif for this NSImageView.
219 | private func startDisplay() {
220 | displaying = true
221 | updateCache()
222 | }
223 |
224 | /// Stop displaying the gif for this NSImageView.
225 | private func stopDisplay() {
226 | displaying = false
227 | updateCache()
228 | }
229 |
230 | /// Start displaying the gif for this NSImageView.
231 | func startAnimatingGif() {
232 | isPlaying = true
233 | }
234 |
235 | /// Stop displaying the gif for this NSImageView.
236 | func stopAnimatingGif() {
237 | isPlaying = false
238 | }
239 |
240 | /// Check if this imageView is currently playing a gif
241 | ///
242 | /// - Returns wether the gif is currently playing
243 | func isAnimatingGif() -> Bool{
244 | return isPlaying
245 | }
246 |
247 | /// Show a specific frame based on a delta from current frame
248 | ///
249 | /// - Parameter delta: The delsta from current frame we want
250 | func showFrameForIndexDelta(_ delta: Int) {
251 | guard let gifImage = gifImage else { return }
252 | var nextIndex = displayOrderIndex + delta
253 |
254 | while nextIndex >= gifImage.framesCount() {
255 | nextIndex -= gifImage.framesCount()
256 | }
257 |
258 | while nextIndex < 0 {
259 | nextIndex += gifImage.framesCount()
260 | }
261 |
262 | showFrameAtIndex(nextIndex)
263 | }
264 |
265 | /// Show a specific frame
266 | ///
267 | /// - Parameter index: The index of frame to show
268 | func showFrameAtIndex(_ index: Int) {
269 | displayOrderIndex = index
270 | updateFrame()
271 | }
272 |
273 | /// Update cache for the current imageView.
274 | func updateCache() {
275 | guard let animationManager = animationManager else { return }
276 |
277 | if animationManager.hasCache(self) && !haveCache {
278 | prepareCache()
279 | haveCache = true
280 | } else if !animationManager.hasCache(self) && haveCache {
281 | cache?.removeAllObjects()
282 | haveCache = false
283 | }
284 | }
285 |
286 | /// Update current image displayed. This method is called by the manager.
287 | func updateCurrentImage() {
288 | if displaying {
289 | updateFrame()
290 | updateIndex()
291 |
292 | if loopCount == 0 || !isDisplayedInScreen(self) || !isPlaying {
293 | stopDisplay()
294 | }
295 | } else {
296 | if isDisplayedInScreen(self) && loopCount != 0 && isPlaying {
297 | startDisplay()
298 | }
299 |
300 | if isDiscarded(self) {
301 | animationManager?.deleteImageView(self)
302 | }
303 | }
304 | }
305 |
306 | /// Force update frame
307 | private func updateFrame() {
308 | if haveCache, let image = cache?.object(forKey: displayOrderIndex as AnyObject) as? NSImage {
309 | currentImage = image
310 | } else {
311 | currentImage = frameAtIndex(index: currentFrameIndex())
312 | }
313 | }
314 |
315 | /// Get current frame index
316 | func currentFrameIndex() -> Int{
317 | return displayOrderIndex
318 | }
319 |
320 | /// Get frame at specific index
321 | func frameAtIndex(index: Int) -> NSImage {
322 | guard let gifImage = gifImage,
323 | let imageSource = gifImage.imageSource,
324 | let displayOrder = gifImage.displayOrder, index < displayOrder.count,
325 | let cgImage = CGImageSourceCreateImageAtIndex(imageSource, displayOrder[index], nil) else {
326 | return NSImage()
327 | }
328 |
329 | return NSImage(cgImage: cgImage, size: .zero)
330 | }
331 |
332 | /// Check if the imageView has been discarded and is not in the view hierarchy anymore.
333 | ///
334 | /// - Returns : A boolean for weather the imageView was discarded
335 | func isDiscarded(_ imageView: NSView?) -> Bool {
336 | return imageView?.superview == nil
337 | }
338 |
339 | /// Check if the imageView is displayed.
340 | ///
341 | /// - Returns : A boolean for weather the imageView is displayed
342 | func isDisplayedInScreen(_ imageView: NSView?) -> Bool {
343 | guard !isHidden, window != nil, let imageView = imageView else {
344 | return false
345 | }
346 |
347 | for screen in NSScreen.screens {
348 | let screenRect = screen.visibleFrame
349 | let viewRect = imageView.convert(bounds, to: nil)
350 | let intersectionRect = viewRect.intersection(screenRect)
351 |
352 | if !intersectionRect.isEmpty && !intersectionRect.isNull {
353 | // The image view is visible on a screen
354 | return true
355 | }
356 | }
357 |
358 | return false
359 | }
360 |
361 | func clear() {
362 | if let gifImage = gifImage {
363 | gifImage.clear()
364 | }
365 |
366 | gifImage = nil
367 | currentImage = nil
368 | cache?.removeAllObjects()
369 | animationManager = nil
370 | image = nil
371 | }
372 |
373 | /// Update loop count and sync factor.
374 | private func updateIndex() {
375 | guard let gif = self.gifImage,
376 | let displayRefreshFactor = gif.displayRefreshFactor,
377 | displayRefreshFactor > 0 else {
378 | return
379 | }
380 |
381 | syncFactor = (syncFactor + 1) % displayRefreshFactor
382 |
383 | if syncFactor == 0, let imageCount = gif.imageCount, imageCount > 0 {
384 | displayOrderIndex = (displayOrderIndex+1) % imageCount
385 |
386 | if displayOrderIndex == 0 {
387 | if loopCount == -1 {
388 | delegate?.gifDidLoop?(sender: self)
389 | } else if loopCount > 1 {
390 | delegate?.gifDidLoop?(sender: self)
391 | loopCount -= 1
392 | } else {
393 | delegate?.gifDidStop?(sender: self)
394 | loopCount -= 1
395 | }
396 | }
397 | }
398 | }
399 |
400 | /// Prepare the cache by adding every images of the gif to an NSCache object.
401 | private func prepareCache() {
402 | guard let cache = self.cache else { return }
403 |
404 | cache.removeAllObjects()
405 |
406 | guard let gif = self.gifImage,
407 | let displayOrder = gif.displayOrder,
408 | let imageSource = gif.imageSource else { return }
409 |
410 | for (i, order) in displayOrder.enumerated() {
411 | guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, order, nil) else { continue }
412 |
413 | cache.setObject(NSImage(cgImage: cgImage, size: .zero), forKey: i as AnyObject)
414 | }
415 | }
416 | }
417 |
418 | // MARK: - Dynamic properties
419 |
420 | private let _gifImageKey = malloc(4)
421 | private let _cacheKey = malloc(4)
422 | private let _currentImageKey = malloc(4)
423 | private let _displayOrderIndexKey = malloc(4)
424 | private let _syncFactorKey = malloc(4)
425 | private let _haveCacheKey = malloc(4)
426 | private let _loopCountKey = malloc(4)
427 | private let _displayingKey = malloc(4)
428 | private let _isPlayingKey = malloc(4)
429 | private let _animationManagerKey = malloc(4)
430 | private let _delegateKey = malloc(4)
431 |
432 | public extension NSImageView {
433 |
434 | var gifImage: NSImage? {
435 | get { return possiblyNil(_gifImageKey) }
436 | set { objc_setAssociatedObject(self, _gifImageKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
437 | }
438 |
439 | var currentImage: NSImage? {
440 | get { return possiblyNil(_currentImageKey) }
441 | set { objc_setAssociatedObject(self, _currentImageKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
442 | }
443 |
444 | private var displayOrderIndex: Int {
445 | get { return value(_displayOrderIndexKey, 0) }
446 | set { objc_setAssociatedObject(self, _displayOrderIndexKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
447 | }
448 |
449 | private var syncFactor: Int {
450 | get { return value(_syncFactorKey, 0) }
451 | set { objc_setAssociatedObject(self, _syncFactorKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
452 | }
453 |
454 | var loopCount: Int {
455 | get { return value(_loopCountKey, 0) }
456 | set { objc_setAssociatedObject(self, _loopCountKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
457 | }
458 |
459 | var animationManager: SwiftyGifManager? {
460 | get { return (objc_getAssociatedObject(self, _animationManagerKey!) as? SwiftyGifManager) }
461 | set { objc_setAssociatedObject(self, _animationManagerKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
462 | }
463 |
464 | var delegate: SwiftyGifDelegate? {
465 | get { return (objc_getAssociatedWeakObject(self, _delegateKey!) as? SwiftyGifDelegate) }
466 | set { objc_setAssociatedWeakObject(self, _delegateKey!, newValue) }
467 | }
468 |
469 | private var haveCache: Bool {
470 | get { return value(_haveCacheKey, false) }
471 | set { objc_setAssociatedObject(self, _haveCacheKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
472 | }
473 |
474 | var displaying: Bool {
475 | get { return value(_displayingKey, false) }
476 | set { objc_setAssociatedObject(self, _displayingKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
477 | }
478 |
479 | private var isPlaying: Bool {
480 | get {
481 | return value(_isPlayingKey, false)
482 | }
483 | set {
484 | objc_setAssociatedObject(self, _isPlayingKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
485 |
486 | if newValue {
487 | delegate?.gifDidStart?(sender: self)
488 | } else {
489 | delegate?.gifDidStop?(sender: self)
490 | }
491 | }
492 | }
493 |
494 | private var cache: NSCache? {
495 | get { return (objc_getAssociatedObject(self, _cacheKey!) as? NSCache) }
496 | set { objc_setAssociatedObject(self, _cacheKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
497 | }
498 |
499 | private func value(_ key:UnsafeMutableRawPointer?, _ defaultValue:T) -> T {
500 | return (objc_getAssociatedObject(self, key!) as? T) ?? defaultValue
501 | }
502 |
503 | private func possiblyNil(_ key:UnsafeMutableRawPointer?) -> T? {
504 | let result = objc_getAssociatedObject(self, key!)
505 |
506 | if result == nil {
507 | return nil
508 | }
509 |
510 | return (result as? T)
511 | }
512 | }
513 |
514 | #endif
515 |
--------------------------------------------------------------------------------
/SwiftyGif/ObjcAssociatedWeakObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjcAssociatedWeakObject.swift
3 | //
4 |
5 | import Foundation
6 |
7 | func objc_getAssociatedWeakObject(_ object: AnyObject, _ key: UnsafeRawPointer) -> AnyObject? {
8 | let block: (() -> AnyObject?)? = objc_getAssociatedObject(object, key) as? (() -> AnyObject?)
9 | return block != nil ? block?() : nil
10 | }
11 |
12 | func objc_setAssociatedWeakObject(_ object: AnyObject, _ key: UnsafeRawPointer, _ value: AnyObject?) {
13 | weak var weakValue = value
14 | let block: (() -> AnyObject?)? = {
15 | return weakValue
16 | }
17 | objc_setAssociatedObject(object, key, block, .OBJC_ASSOCIATION_COPY)
18 | }
19 |
--------------------------------------------------------------------------------
/SwiftyGif/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTracking
6 |
7 | NSPrivacyCollectedDataTypes
8 |
9 | NSPrivacyTrackingDomains
10 |
11 | NSPrivacyAccessedAPITypes
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/SwiftyGif/SwiftyGif.h:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyGif.h
3 | // SwiftyGif
4 | //
5 | // Created by Scott Hoyt on 1/12/17.
6 | // Copyright © 2017 alexiscreuzot. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for SwiftyGif.
12 | FOUNDATION_EXPORT double SwiftyGifVersionNumber;
13 |
14 | //! Project version string for SwiftyGif.
15 | FOUNDATION_EXPORT const unsigned char SwiftyGifVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SwiftyGif/SwiftyGifError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyGifError.swift
3 | // SwiftyGif
4 | //
5 | // Created by Abbas Sabeti on 26.07.24.
6 | // Copyright © 2024 alexiscreuzot. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum SwiftyGifError: Error {
12 | case noGifData
13 | case corruptedGifData
14 | }
15 |
--------------------------------------------------------------------------------
/SwiftyGif/SwiftyGifManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyGifManager.swift
3 | //
4 | //
5 | import ImageIO
6 |
7 | #if os(macOS)
8 | import AppKit
9 | import CoreVideo
10 | #else
11 | import UIKit
12 | #endif
13 |
14 | #if os(macOS)
15 | public typealias PlatformImageView = NSImageView
16 | #else
17 | public typealias PlatformImageView = UIImageView
18 | #endif
19 |
20 | open class SwiftyGifManager {
21 |
22 | // A convenient default manager if we only have one gif to display here and there
23 | public static var defaultManager = SwiftyGifManager(memoryLimit: 50)
24 |
25 | #if os(macOS)
26 | fileprivate var timer: CVDisplayLink?
27 | #else
28 | fileprivate var timer: CADisplayLink?
29 | #endif
30 |
31 | fileprivate var displayViews: [PlatformImageView] = []
32 | fileprivate var totalGifSize: Int
33 | fileprivate var memoryLimit: Int
34 | open var haveCache: Bool
35 | open var remoteCache : [URL : Data] = [:]
36 | #if swift(>=4.2)
37 | public var mode: RunLoop.Mode = .common
38 | #else
39 | public var mode: RunLoopMode = RunLoopMode.commonModes
40 | #endif
41 |
42 |
43 | /// Initialize a manager
44 | ///
45 | /// - Parameter memoryLimit: The number of Mb max for this manager
46 | public init(memoryLimit: Int) {
47 | self.memoryLimit = memoryLimit
48 | totalGifSize = 0
49 | haveCache = true
50 | }
51 |
52 | deinit {
53 | stopTimer()
54 | }
55 |
56 | public func startTimerIfNeeded() {
57 | guard timer == nil else {
58 | return
59 | }
60 |
61 | #if os(macOS)
62 |
63 | func displayLinkOutputCallback(displayLink: CVDisplayLink,
64 | _ inNow: UnsafePointer,
65 | _ inOutputTime: UnsafePointer,
66 | _ flagsIn: CVOptionFlags,
67 | _ flagsOut: UnsafeMutablePointer,
68 | _ displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn {
69 | unsafeBitCast(displayLinkContext!, to: SwiftyGifManager.self).updateImageView()
70 | return kCVReturnSuccess
71 | }
72 |
73 | CVDisplayLinkCreateWithActiveCGDisplays(&timer)
74 | CVDisplayLinkSetOutputCallback(timer!, displayLinkOutputCallback, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
75 | CVDisplayLinkStart(timer!)
76 |
77 | #else
78 |
79 | timer = CADisplayLink(target: self, selector: #selector(updateImageView))
80 |
81 | timer?.add(to: .main, forMode: mode)
82 |
83 | #endif
84 | }
85 |
86 | public func stopTimer() {
87 | #if os(macOS)
88 | CVDisplayLinkStop(timer!)
89 | #else
90 | timer?.invalidate()
91 | #endif
92 |
93 | timer = nil
94 | }
95 |
96 | /// Add a new imageView to this manager if it doesn't exist
97 | /// - Parameter imageView: The image view we're adding to this manager
98 | open func addImageView(_ imageView: PlatformImageView) -> Bool {
99 | if containsImageView(imageView) {
100 | startTimerIfNeeded()
101 | return false
102 | }
103 |
104 | updateCacheSize(for: imageView, add: true)
105 | displayViews.append(imageView)
106 | startTimerIfNeeded()
107 |
108 | return true
109 | }
110 |
111 | /// Delete an imageView from this manager if it exists
112 | /// - Parameter imageView: The image view we want to delete
113 | open func deleteImageView(_ imageView: PlatformImageView) {
114 | guard let index = displayViews.firstIndex(of: imageView) else {
115 | return
116 | }
117 |
118 | displayViews.remove(at: index)
119 | updateCacheSize(for: imageView, add: false)
120 | }
121 |
122 | open func updateCacheSize(for imageView: PlatformImageView, add: Bool) {
123 | totalGifSize += (add ? 1 : -1) * (imageView.gifImage?.imageSize ?? 0)
124 | haveCache = totalGifSize <= memoryLimit
125 |
126 | for imageView in displayViews {
127 | DispatchQueue.global(qos: .userInteractive).sync(execute: imageView.updateCache)
128 | }
129 | }
130 |
131 | open func clear() {
132 | displayViews.forEach { $0.clear() }
133 | displayViews = []
134 | stopTimer()
135 | }
136 |
137 | /// Check if an imageView is already managed by this manager
138 | /// - Parameter imageView: The image view we're searching
139 | /// - Returns : a boolean for wether the imageView was found
140 | open func containsImageView(_ imageView: PlatformImageView) -> Bool{
141 | return displayViews.contains(imageView)
142 | }
143 |
144 | /// Check if this manager has cache for an imageView
145 | /// - Parameter imageView: The image view we're searching cache for
146 | /// - Returns : a boolean for wether we have cache for the imageView
147 | open func hasCache(_ imageView: PlatformImageView) -> Bool {
148 | return imageView.displaying && (imageView.loopCount == -1 || imageView.loopCount >= 5) ? haveCache : false
149 | }
150 |
151 | /// Update imageView current image. This method is called by the main loop.
152 | /// This is what create the animation.
153 | @objc func updateImageView() {
154 | guard !displayViews.isEmpty else {
155 | stopTimer()
156 | return
157 | }
158 |
159 | #if os(macOS)
160 | let queue = DispatchQueue.main
161 | #else
162 | let queue = DispatchQueue.global(qos: .userInteractive)
163 | #endif
164 |
165 | for imageView in displayViews {
166 | queue.sync {
167 | imageView.image = imageView.currentImage
168 | }
169 |
170 | if imageView.isAnimatingGif() {
171 | queue.sync(execute: imageView.updateCurrentImage)
172 | } else if imageView.isDiscarded(imageView) {
173 | queue.sync {
174 | self.deleteImageView(imageView)
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/SwiftyGif/UIImage+SwiftyGif.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+SwiftyGif.swift
3 | //
4 |
5 | #if !os(macOS)
6 |
7 | import ImageIO
8 | import UIKit
9 |
10 | public typealias GifLevelOfIntegrity = Float
11 |
12 | extension GifLevelOfIntegrity {
13 | public static let highestNoFrameSkipping: GifLevelOfIntegrity = 1
14 | public static let `default`: GifLevelOfIntegrity = 0.8
15 | public static let lowForManyGifs: GifLevelOfIntegrity = 0.5
16 | public static let lowForTooManyGifs: GifLevelOfIntegrity = 0.2
17 | public static let superLowForSlideShow: GifLevelOfIntegrity = 0.1
18 | }
19 |
20 | enum GifParseError: Error {
21 | case invalidFilename
22 | case noImages
23 | case noProperties
24 | case noGifDictionary
25 | case noTimingInfo
26 | }
27 |
28 | extension GifParseError: LocalizedError {
29 | public var errorDescription: String? {
30 | switch self {
31 | case .invalidFilename:
32 | return "Invalid file name"
33 | case .noImages,.noProperties, .noGifDictionary,.noTimingInfo:
34 | return "Invalid gif file "
35 | }
36 | }
37 | }
38 |
39 | public extension UIImage {
40 | /// Convenience initializer. Creates a gif with its backing data.
41 | ///
42 | /// - Parameter imageData: The actual image data, can be GIF or some other format
43 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
44 | convenience init?(imageData:Data, levelOfIntegrity: GifLevelOfIntegrity = .default) throws {
45 | do {
46 | try self.init(gifData: imageData, levelOfIntegrity: levelOfIntegrity)
47 | } catch {
48 | self.init(data: imageData)
49 | }
50 | }
51 |
52 | /// Convenience initializer. Creates a image with its backing data.
53 | ///
54 | /// - Parameter imageName: Filename
55 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
56 | convenience init?(imageName: String, levelOfIntegrity: GifLevelOfIntegrity = .default, bundle: Bundle = Bundle.main) throws {
57 | self.init()
58 |
59 | do {
60 | try setGif(imageName, levelOfIntegrity: levelOfIntegrity, bundle: bundle)
61 | } catch {
62 | self.init(named: imageName)
63 | }
64 | }
65 | }
66 |
67 | // MARK: - Inits
68 |
69 | public extension UIImage {
70 |
71 | /// Convenience initializer. Creates a gif with its backing data.
72 | ///
73 | /// - Parameter gifData: The actual gif data
74 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
75 | @objc convenience init(gifData:Data, floatLevelOfIntegrity: Float = GifLevelOfIntegrity.highestNoFrameSkipping) throws {
76 | self.init()
77 | try setGifFromData(gifData, levelOfIntegrity: floatLevelOfIntegrity)
78 | }
79 |
80 | /// Convenience initializer. Creates a gif with its backing data.
81 | ///
82 | /// - Parameter gifData: The actual gif data
83 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
84 | @objc convenience init(gifData:Data, levelOfIntegrity: GifLevelOfIntegrity = .default) throws {
85 | self.init()
86 | try setGifFromData(gifData, levelOfIntegrity: levelOfIntegrity)
87 | }
88 |
89 | /// Convenience initializer. Creates a gif with its backing data.
90 | ///
91 | /// - Parameter gifName: Filename
92 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
93 | convenience init(gifName: String, levelOfIntegrity: GifLevelOfIntegrity = .default, bundle: Bundle = Bundle.main) throws {
94 | self.init()
95 | try setGif(gifName, levelOfIntegrity: levelOfIntegrity, bundle: bundle)
96 | }
97 |
98 | /// Set backing data for this gif. Overwrites any existing data.
99 | ///
100 | /// - Parameter data: The actual gif data
101 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
102 | func setGifFromData(_ data: Data, levelOfIntegrity: GifLevelOfIntegrity) throws {
103 | guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return }
104 | self.imageSource = imageSource
105 | imageData = data
106 |
107 | calculateFrameDelay(try delayTimes(imageSource), levelOfIntegrity: levelOfIntegrity)
108 | calculateFrameSize()
109 | }
110 |
111 | /// Set backing data for this gif. Overwrites any existing data.
112 | ///
113 | /// - Parameter name: Filename
114 | func setGif(_ name: String, bundle: Bundle = Bundle.main) throws {
115 | try setGif(name, levelOfIntegrity: .default, bundle: bundle)
116 | }
117 |
118 | /// Check the number of frame for this gif
119 | ///
120 | /// - Return number of frames
121 | func framesCount() -> Int {
122 | return displayOrder?.count ?? 0
123 | }
124 |
125 | /// Set backing data for this gif. Overwrites any existing data.
126 | ///
127 | /// - Parameter name: Filename
128 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
129 | func setGif(_ name: String, levelOfIntegrity: GifLevelOfIntegrity, bundle: Bundle = Bundle.main) throws {
130 | if let url = bundle.url(forResource: name, withExtension: name.pathExtension() == "gif" ? "" : "gif") {
131 | if let data = try? Data(contentsOf: url) {
132 | try setGifFromData(data, levelOfIntegrity: levelOfIntegrity)
133 | }
134 | } else {
135 | throw GifParseError.invalidFilename
136 | }
137 | }
138 |
139 | func clear() {
140 | imageData = nil
141 | imageSource = nil
142 | displayOrder = nil
143 | imageCount = nil
144 | imageSize = nil
145 | displayRefreshFactor = nil
146 | }
147 |
148 | // MARK: Logic
149 |
150 | private func convertToDelay(_ pointer:UnsafeRawPointer?) -> Float? {
151 | if pointer == nil {
152 | return nil
153 | }
154 |
155 | return unsafeBitCast(pointer, to:AnyObject.self).floatValue
156 | }
157 |
158 | /// Get delay times for each frames
159 | ///
160 | /// - Parameter imageSource: reference to the gif image source
161 | /// - Returns array of delays
162 | private func delayTimes(_ imageSource:CGImageSource) throws -> [Float] {
163 | let imageCount = CGImageSourceGetCount(imageSource)
164 |
165 | guard imageCount > 0 else {
166 | throw GifParseError.noImages
167 | }
168 |
169 | var imageProperties = [CFDictionary]()
170 |
171 | for i in 0.. CFDictionary in
180 | let key = Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()
181 | let value = CFDictionaryGetValue(dict, key)
182 |
183 | if value == nil {
184 | throw GifParseError.noGifDictionary
185 | }
186 |
187 | return unsafeBitCast(value, to: CFDictionary.self)
188 | }
189 |
190 | let EPS:Float = 1e-6
191 |
192 | let frameDelays:[Float] = try frameProperties.map() {
193 | let unclampedKey = Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()
194 | let unclampedPointer:UnsafeRawPointer? = CFDictionaryGetValue($0, unclampedKey)
195 |
196 | if let value = convertToDelay(unclampedPointer), value >= EPS {
197 | return value
198 | }
199 |
200 | let clampedKey = Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()
201 | let clampedPointer:UnsafeRawPointer? = CFDictionaryGetValue($0, clampedKey)
202 |
203 | if let value = convertToDelay(clampedPointer) {
204 | return value
205 | }
206 |
207 | throw GifParseError.noTimingInfo
208 | }
209 |
210 | return frameDelays
211 | }
212 |
213 | /// Compute backing data for this gif
214 | ///
215 | /// - Parameter delaysArray: decoded delay times for this gif
216 | /// - Parameter levelOfIntegrity: 0 to 1, 1 meaning no frame skipping
217 | private func calculateFrameDelay(_ delaysArray: [Float], levelOfIntegrity: GifLevelOfIntegrity) {
218 | let levelOfIntegrity = max(0, min(1, levelOfIntegrity))
219 | var delays = delaysArray
220 |
221 | var displayRefreshFactors = [Int]()
222 |
223 | displayRefreshFactors.append(contentsOf: [60, 30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1])
224 |
225 | let defaultMaxFramePerSecond = 60
226 | var maxFramePerSecond = defaultMaxFramePerSecond
227 |
228 | if #available(iOS 10.3, *) {
229 | // Will be 120 on devices with ProMotion display, 60 otherwise.
230 | let maximumFramesPerSecond = UIScreen.main.maximumFramesPerSecond
231 | if maximumFramesPerSecond == 120 {
232 | maxFramePerSecond = maximumFramesPerSecond
233 | displayRefreshFactors.insert(maximumFramesPerSecond, at: 0)
234 | }
235 | }
236 |
237 | let frameRateRatio = Float(maxFramePerSecond / defaultMaxFramePerSecond)
238 |
239 | // frame numbers per second
240 | let displayRefreshRates = displayRefreshFactors.map { maxFramePerSecond / $0 }
241 |
242 | // time interval per frame
243 | let displayRefreshDelayTime = displayRefreshRates.map { frameRateRatio / Float($0) }
244 |
245 | // calculate the time when each frame should be displayed at(start at 0)
246 | for i in delays.indices.dropFirst() {
247 | delays[i] += delays[i - 1]
248 | }
249 |
250 | //find the appropriate Factors then BREAK
251 | for (i, delayTime) in displayRefreshDelayTime.enumerated() {
252 | let displayPosition = delays.map { Int($0 / delayTime) }
253 |
254 | var frameLoseCount: Float = 0
255 |
256 | for j in displayPosition.indices.dropFirst() where displayPosition[j] == displayPosition[j - 1] {
257 | frameLoseCount += 1
258 | }
259 |
260 | if displayPosition.first == 0 {
261 | frameLoseCount += 1
262 | }
263 |
264 | if frameLoseCount <= Float(displayPosition.count) * (1 - levelOfIntegrity) || i == displayRefreshDelayTime.count - 1 {
265 | imageCount = displayPosition.last
266 | displayRefreshFactor = displayRefreshFactors[i]
267 | displayOrder = []
268 | var oldIndex = 0
269 | var newIndex = 1
270 | let imageCount = self.imageCount ?? 0
271 |
272 | while newIndex <= imageCount && oldIndex < displayPosition.count {
273 | if newIndex <= displayPosition[oldIndex] {
274 | displayOrder?.append(oldIndex)
275 | newIndex += 1
276 | } else {
277 | oldIndex += 1
278 | }
279 | }
280 | break
281 | }
282 | }
283 | }
284 |
285 | /// Compute frame size for this gif
286 | private func calculateFrameSize(){
287 | guard let imageSource = imageSource,
288 | let imageCount = imageCount,
289 | let cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
290 | return
291 | }
292 |
293 | let image = UIImage(cgImage: cgImage)
294 | imageSize = Int(image.size.height * image.size.width * 4) * imageCount / 1_000_000
295 | }
296 | }
297 |
298 | // MARK: - Properties
299 |
300 | private let _imageSourceKey = malloc(4)
301 | private let _displayRefreshFactorKey = malloc(4)
302 | private let _imageSizeKey = malloc(4)
303 | private let _imageCountKey = malloc(4)
304 | private let _displayOrderKey = malloc(4)
305 | private let _imageDataKey = malloc(4)
306 |
307 | public extension UIImage {
308 |
309 | var imageSource: CGImageSource? {
310 | get {
311 | let result = objc_getAssociatedObject(self, _imageSourceKey!)
312 | return result == nil ? nil : (result as! CGImageSource)
313 | }
314 | set {
315 | objc_setAssociatedObject(self, _imageSourceKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
316 | }
317 | }
318 |
319 | var displayRefreshFactor: Int?{
320 | get { return objc_getAssociatedObject(self, _displayRefreshFactorKey!) as? Int }
321 | set { objc_setAssociatedObject(self, _displayRefreshFactorKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
322 | }
323 |
324 | var imageSize: Int?{
325 | get { return objc_getAssociatedObject(self, _imageSizeKey!) as? Int }
326 | set { objc_setAssociatedObject(self, _imageSizeKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
327 | }
328 |
329 | var imageCount: Int?{
330 | get { return objc_getAssociatedObject(self, _imageCountKey!) as? Int }
331 | set { objc_setAssociatedObject(self, _imageCountKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
332 | }
333 |
334 | var displayOrder: [Int]?{
335 | get { return objc_getAssociatedObject(self, _displayOrderKey!) as? [Int] }
336 | set { objc_setAssociatedObject(self, _displayOrderKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
337 | }
338 |
339 | var imageData:Data? {
340 | get {
341 | let result = objc_getAssociatedObject(self, _imageDataKey!)
342 | return result == nil ? nil : (result as? Data)
343 | }
344 | set {
345 | objc_setAssociatedObject(self, _imageDataKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
346 | }
347 | }
348 | }
349 |
350 | extension String {
351 | fileprivate func pathExtension() -> String {
352 | return (self as NSString).pathExtension
353 | }
354 | }
355 |
356 | #endif
357 |
--------------------------------------------------------------------------------
/SwiftyGif/UIImageView+SwiftyGif.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+SwiftyGif.swift
3 | //
4 |
5 | #if !os(macOS)
6 |
7 | import ImageIO
8 | import UIKit
9 |
10 | @objc public protocol SwiftyGifDelegate {
11 | @objc optional func gifDidStart(sender: UIImageView)
12 | @objc optional func gifDidLoop(sender: UIImageView)
13 | @objc optional func gifDidStop(sender: UIImageView)
14 | @objc optional func gifURLDidFinish(sender: UIImageView)
15 | @objc optional func gifURLDidFail(sender: UIImageView, url: URL, error: Error?)
16 | }
17 |
18 | public extension UIImageView {
19 | /// Set an image and a manager to an existing UIImageView. If the image is not an GIF image, set it in normal way and remove self form SwiftyGifManager
20 | ///
21 | /// WARNING : this overwrite any previous gif.
22 | /// - Parameter gifImage: The UIImage containing the gif backing data
23 | /// - Parameter manager: The manager to handle the gif display
24 | /// - Parameter loopCount: The number of loops we want for this gif. -1 means infinite.
25 | func setImage(_ image: UIImage, manager: SwiftyGifManager = .defaultManager, loopCount: Int = -1) {
26 | if let _ = image.imageData {
27 | setGifImage(image, manager: manager, loopCount: loopCount)
28 | } else {
29 | manager.deleteImageView(self)
30 | self.image = image
31 | }
32 | }
33 | }
34 |
35 | public extension UIImageView {
36 |
37 | // MARK: - Inits
38 |
39 | /// Convenience initializer. Creates a gif holder (defaulted to infinite loop).
40 | ///
41 | /// - Parameter gifImage: The UIImage containing the gif backing data
42 | /// - Parameter manager: The manager to handle the gif display
43 | convenience init(gifImage: UIImage, manager: SwiftyGifManager = .defaultManager, loopCount: Int = -1) {
44 | self.init()
45 | setGifImage(gifImage,manager: manager, loopCount: loopCount)
46 | }
47 |
48 | /// Convenience initializer. Creates a gif holder (defaulted to infinite loop).
49 | ///
50 | /// - Parameter gifImage: The UIImage containing the gif backing data
51 | /// - Parameter manager: The manager to handle the gif display
52 | convenience init(gifURL: URL, manager: SwiftyGifManager = .defaultManager, loopCount: Int = -1) {
53 | self.init()
54 | setGifFromURL(gifURL, manager: manager, loopCount: loopCount)
55 | }
56 |
57 | /// Set a gif image and a manager to an existing UIImageView.
58 | ///
59 | /// WARNING : this overwrite any previous gif.
60 | /// - Parameter gifImage: The UIImage containing the gif backing data
61 | /// - Parameter loopCount: The number of loops we want for this gif. -1 means infinite.
62 | @objc func setGifImage(_ gifImage: UIImage, loopCount: Int = -1) {
63 | if let imageData = gifImage.imageData, (gifImage.imageCount ?? 0) < 1 {
64 | image = UIImage(data: imageData)
65 | return
66 | }
67 |
68 | let manager: SwiftyGifManager = .defaultManager
69 |
70 | self.loopCount = loopCount
71 | self.gifImage = gifImage
72 | animationManager = manager
73 | syncFactor = 0
74 | displayOrderIndex = 0
75 | cache = NSCache()
76 | haveCache = false
77 |
78 | if let source = gifImage.imageSource, let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) {
79 | currentImage = UIImage(cgImage: cgImage)
80 |
81 | if manager.addImageView(self) {
82 | startDisplay()
83 | startAnimatingGif()
84 | }
85 | }
86 | }
87 |
88 | /// Set a gif image and a manager to an existing UIImageView.
89 | ///
90 | /// WARNING : this overwrite any previous gif.
91 | /// - Parameter gifImage: The UIImage containing the gif backing data
92 | /// - Parameter manager: The manager to handle the gif display
93 | /// - Parameter loopCount: The number of loops we want for this gif. -1 means infinite.
94 | func setGifImage(_ gifImage: UIImage, manager: SwiftyGifManager = .defaultManager, loopCount: Int = -1) {
95 | if let imageData = gifImage.imageData, (gifImage.imageCount ?? 0) < 1 {
96 | image = UIImage(data: imageData)
97 | return
98 | }
99 |
100 | self.loopCount = loopCount
101 | self.gifImage = gifImage
102 | animationManager = manager
103 | syncFactor = 0
104 | displayOrderIndex = 0
105 | cache = NSCache()
106 | haveCache = false
107 |
108 | if let source = gifImage.imageSource, let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) {
109 | currentImage = UIImage(cgImage: cgImage)
110 |
111 | if manager.addImageView(self) {
112 | startDisplay()
113 | startAnimatingGif()
114 | }
115 | }
116 | }
117 | }
118 |
119 | // MARK: - Download gif
120 |
121 | public extension UIImageView {
122 |
123 | /// Download gif image and sets it.
124 | ///
125 | /// - Parameters:
126 | /// - url: The URL pointing to the gif data
127 | /// - manager: The manager to handle the gif display
128 | /// - loopCount: The number of loops we want for this gif. -1 means infinite.
129 | /// - showLoader: Show UIActivityIndicatorView or not
130 | /// - Returns: An URL session task. Note: You can cancel the downloading task if it needed.
131 | @discardableResult
132 | func setGifFromURL(_ url: URL,
133 | manager: SwiftyGifManager = .defaultManager,
134 | loopCount: Int = -1,
135 | levelOfIntegrity: GifLevelOfIntegrity = .default,
136 | session: URLSession = URLSession.shared,
137 | showLoader: Bool = true,
138 | customLoader: UIView? = nil,
139 | callback: @escaping (Result) -> Void = {_ in }
140 | ) -> URLSessionDataTask? {
141 |
142 | if let data = manager.remoteCache[url] {
143 | self.parseDownloadedGif(url: url,
144 | data: data,
145 | error: nil,
146 | manager: manager,
147 | loopCount: loopCount,
148 | levelOfIntegrity: levelOfIntegrity,
149 | callback: callback)
150 | return nil
151 | }
152 |
153 | stopAnimatingGif()
154 |
155 | let loader: UIView? = showLoader ? createLoader(from: customLoader) : nil
156 |
157 | let task = session.dataTask(with: url) { [weak self] data, _, error in
158 | DispatchQueue.main.async {
159 | loader?.removeFromSuperview()
160 | self?.parseDownloadedGif(url: url,
161 | data: data,
162 | error: error,
163 | manager: manager,
164 | loopCount: loopCount,
165 | levelOfIntegrity: levelOfIntegrity,
166 | callback: callback)
167 | }
168 | }
169 |
170 | task.resume()
171 |
172 | return task
173 | }
174 |
175 | private func createLoader(from view: UIView? = nil) -> UIView {
176 | let loader = view ?? UIActivityIndicatorView()
177 | addSubview(loader)
178 | loader.translatesAutoresizingMaskIntoConstraints = false
179 |
180 | addConstraint(NSLayoutConstraint(
181 | item: loader,
182 | attribute: .centerX,
183 | relatedBy: .equal,
184 | toItem: self,
185 | attribute: .centerX,
186 | multiplier: 1,
187 | constant: 0))
188 |
189 | addConstraint(NSLayoutConstraint(
190 | item: loader,
191 | attribute: .centerY,
192 | relatedBy: .equal,
193 | toItem: self,
194 | attribute: .centerY,
195 | multiplier: 1,
196 | constant: 0))
197 |
198 | (loader as? UIActivityIndicatorView)?.startAnimating()
199 |
200 | return loader
201 | }
202 |
203 | private func parseDownloadedGif(url: URL,
204 | data: Data?,
205 | error: Error?,
206 | manager: SwiftyGifManager,
207 | loopCount: Int,
208 | levelOfIntegrity: GifLevelOfIntegrity,
209 | callback: (Result) -> Void) {
210 | guard let data = data else {
211 | report(url: url, error: error)
212 | callback(.failure(error ?? SwiftyGifError.noGifData))
213 | return
214 | }
215 |
216 | do {
217 | let image = try UIImage(gifData: data, levelOfIntegrity: levelOfIntegrity)
218 | manager.remoteCache[url] = data
219 | setGifImage(image, manager: manager, loopCount: loopCount)
220 | startAnimatingGif()
221 | delegate?.gifURLDidFinish?(sender: self)
222 | callback(.success(data))
223 | } catch {
224 | report(url: url, error: error)
225 | callback(.failure(error))
226 | }
227 | }
228 |
229 | private func report(url: URL, error: Error?) {
230 | delegate?.gifURLDidFail?(sender: self, url: url, error: error)
231 | }
232 | }
233 |
234 | // MARK: - Logic
235 |
236 | public extension UIImageView {
237 |
238 | /// Start displaying the gif for this UIImageView.
239 | private func startDisplay() {
240 | displaying = true
241 | updateCache()
242 | }
243 |
244 | /// Stop displaying the gif for this UIImageView.
245 | private func stopDisplay() {
246 | displaying = false
247 | updateCache()
248 | }
249 |
250 | /// Start displaying the gif for this UIImageView.
251 | @objc func startAnimatingGif() {
252 | isPlaying = true
253 | }
254 |
255 | /// Stop displaying the gif for this UIImageView.
256 | @objc func stopAnimatingGif() {
257 | isPlaying = false
258 | }
259 |
260 | /// Check if this imageView is currently playing a gif
261 | ///
262 | /// - Returns wether the gif is currently playing
263 | @objc func isAnimatingGif() -> Bool{
264 | return isPlaying
265 | }
266 |
267 | /// Show a specific frame based on a delta from current frame
268 | ///
269 | /// - Parameter delta: The delsta from current frame we want
270 | @objc func showFrameForIndexDelta(_ delta: Int) {
271 | guard let gifImage = gifImage else { return }
272 | var nextIndex = displayOrderIndex + delta
273 |
274 | while nextIndex >= gifImage.framesCount() {
275 | nextIndex -= gifImage.framesCount()
276 | }
277 |
278 | while nextIndex < 0 {
279 | nextIndex += gifImage.framesCount()
280 | }
281 |
282 | showFrameAtIndex(nextIndex)
283 | }
284 |
285 | /// Show a specific frame
286 | ///
287 | /// - Parameter index: The index of frame to show
288 | @objc func showFrameAtIndex(_ index: Int) {
289 | displayOrderIndex = index
290 | updateFrame()
291 | }
292 |
293 | /// Update cache for the current imageView.
294 | func updateCache() {
295 | guard let animationManager = animationManager else { return }
296 |
297 | if animationManager.hasCache(self) && !haveCache {
298 | prepareCache()
299 | haveCache = true
300 | } else if !animationManager.hasCache(self) && haveCache {
301 | cache?.removeAllObjects()
302 | haveCache = false
303 | }
304 | }
305 |
306 | /// Update current image displayed. This method is called by the manager.
307 | func updateCurrentImage() {
308 | if displaying {
309 | updateFrame()
310 | updateIndex()
311 |
312 | if loopCount == 0 || !isDisplayedInScreen(self) || !isPlaying {
313 | stopDisplay()
314 | }
315 | } else {
316 | if isDisplayedInScreen(self) && loopCount != 0 && isPlaying {
317 | startDisplay()
318 | }
319 |
320 | if isDiscarded(self) {
321 | animationManager?.deleteImageView(self)
322 | }
323 | }
324 | }
325 |
326 | /// Force update frame
327 | private func updateFrame() {
328 | if haveCache, let image = cache?.object(forKey: displayOrderIndex as AnyObject) as? UIImage {
329 | currentImage = image
330 | } else {
331 | currentImage = frameAtIndex(index: currentFrameIndex())
332 | }
333 | }
334 |
335 | /// Get current frame index
336 | func currentFrameIndex() -> Int{
337 | return displayOrderIndex
338 | }
339 |
340 | /// Get frame at specific index
341 | func frameAtIndex(index: Int) -> UIImage {
342 | guard let gifImage = gifImage,
343 | let imageSource = gifImage.imageSource,
344 | let displayOrder = gifImage.displayOrder, index < displayOrder.count,
345 | let cgImage = CGImageSourceCreateImageAtIndex(imageSource, displayOrder[index], nil) else {
346 | return UIImage()
347 | }
348 |
349 | return UIImage(cgImage: cgImage)
350 | }
351 |
352 | /// Check if the imageView has been discarded and is not in the view hierarchy anymore.
353 | ///
354 | /// - Returns : A boolean for weather the imageView was discarded
355 | func isDiscarded(_ imageView: UIView?) -> Bool {
356 | return imageView?.superview == nil
357 | }
358 |
359 | /// Check if the imageView is displayed.
360 | ///
361 | /// - Returns : A boolean for weather the imageView is displayed
362 | func isDisplayedInScreen(_ imageView: UIView?) -> Bool {
363 | guard !isHidden, let imageView = imageView else {
364 | return false
365 | }
366 |
367 | let screenRect = UIScreen.main.bounds
368 | let viewRect = imageView.convert(bounds, to:nil)
369 | let intersectionRect = viewRect.intersection(screenRect)
370 |
371 | return window != nil && !intersectionRect.isEmpty && !intersectionRect.isNull
372 | }
373 |
374 | @objc func clear() {
375 | if let gifImage = gifImage {
376 | gifImage.clear()
377 | }
378 |
379 | gifImage = nil
380 | currentImage = nil
381 | cache?.removeAllObjects()
382 | animationManager = nil
383 | image = nil
384 | }
385 |
386 | /// Update loop count and sync factor.
387 | private func updateIndex() {
388 | guard let gif = self.gifImage,
389 | let displayRefreshFactor = gif.displayRefreshFactor,
390 | displayRefreshFactor > 0 else {
391 | return
392 | }
393 |
394 | syncFactor = (syncFactor + 1) % displayRefreshFactor
395 |
396 | if syncFactor == 0, let imageCount = gif.imageCount, imageCount > 0 {
397 | displayOrderIndex = (displayOrderIndex+1) % imageCount
398 |
399 | if displayOrderIndex == 0 {
400 | if loopCount == -1 {
401 | delegate?.gifDidLoop?(sender: self)
402 | } else if loopCount > 1 {
403 | delegate?.gifDidLoop?(sender: self)
404 | loopCount -= 1
405 | } else {
406 | delegate?.gifDidStop?(sender: self)
407 | loopCount -= 1
408 | }
409 | }
410 | }
411 | }
412 |
413 | /// Prepare the cache by adding every images of the gif to an NSCache object.
414 | private func prepareCache() {
415 | guard let cache = self.cache else { return }
416 |
417 | cache.removeAllObjects()
418 |
419 | guard let gif = self.gifImage,
420 | let displayOrder = gif.displayOrder,
421 | let imageSource = gif.imageSource else { return }
422 |
423 | for (i, order) in displayOrder.enumerated() {
424 | guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, order, nil) else { continue }
425 |
426 | cache.setObject(UIImage(cgImage: cgImage), forKey: i as AnyObject)
427 | }
428 | }
429 | }
430 |
431 | // MARK: - Dynamic properties
432 |
433 | private let _gifImageKey = malloc(4)
434 | private let _cacheKey = malloc(4)
435 | private let _currentImageKey = malloc(4)
436 | private let _displayOrderIndexKey = malloc(4)
437 | private let _syncFactorKey = malloc(4)
438 | private let _haveCacheKey = malloc(4)
439 | private let _loopCountKey = malloc(4)
440 | private let _displayingKey = malloc(4)
441 | private let _isPlayingKey = malloc(4)
442 | private let _animationManagerKey = malloc(4)
443 | private let _delegateKey = malloc(4)
444 |
445 | public extension UIImageView {
446 |
447 | var gifImage: UIImage? {
448 | get { return possiblyNil(_gifImageKey) }
449 | set { objc_setAssociatedObject(self, _gifImageKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
450 | }
451 |
452 | @objc var currentImage: UIImage? {
453 | get { return possiblyNil(_currentImageKey) }
454 | set { objc_setAssociatedObject(self, _currentImageKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
455 | }
456 |
457 | private var displayOrderIndex: Int {
458 | get { return value(_displayOrderIndexKey, 0) }
459 | set { objc_setAssociatedObject(self, _displayOrderIndexKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
460 | }
461 |
462 | private var syncFactor: Int {
463 | get { return value(_syncFactorKey, 0) }
464 | set { objc_setAssociatedObject(self, _syncFactorKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
465 | }
466 |
467 | @objc var loopCount: Int {
468 | get { return value(_loopCountKey, 0) }
469 | set { objc_setAssociatedObject(self, _loopCountKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
470 | }
471 |
472 | var animationManager: SwiftyGifManager? {
473 | get { return (objc_getAssociatedObject(self, _animationManagerKey!) as? SwiftyGifManager) }
474 | set { objc_setAssociatedObject(self, _animationManagerKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
475 | }
476 |
477 | @objc var delegate: SwiftyGifDelegate? {
478 | get { return (objc_getAssociatedWeakObject(self, _delegateKey!) as? SwiftyGifDelegate) }
479 | set { objc_setAssociatedWeakObject(self, _delegateKey!, newValue) }
480 | }
481 |
482 | private var haveCache: Bool {
483 | get { return value(_haveCacheKey, false) }
484 | set { objc_setAssociatedObject(self, _haveCacheKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
485 | }
486 |
487 | @objc var displaying: Bool {
488 | get { return value(_displayingKey, false) }
489 | set { objc_setAssociatedObject(self, _displayingKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
490 | }
491 |
492 | private var isPlaying: Bool {
493 | get {
494 | return value(_isPlayingKey, false)
495 | }
496 | set {
497 | objc_setAssociatedObject(self, _isPlayingKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
498 |
499 | if newValue {
500 | delegate?.gifDidStart?(sender: self)
501 | } else {
502 | delegate?.gifDidStop?(sender: self)
503 | }
504 | }
505 | }
506 |
507 | private var cache: NSCache? {
508 | get { return (objc_getAssociatedObject(self, _cacheKey!) as? NSCache) }
509 | set { objc_setAssociatedObject(self, _cacheKey!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
510 | }
511 |
512 | private func value(_ key:UnsafeMutableRawPointer?, _ defaultValue:T) -> T {
513 | return (objc_getAssociatedObject(self, key!) as? T) ?? defaultValue
514 | }
515 |
516 | private func possiblyNil(_ key:UnsafeMutableRawPointer?) -> T? {
517 | let result = objc_getAssociatedObject(self, key!)
518 |
519 | if result == nil {
520 | return nil
521 | }
522 |
523 | return (result as? T)
524 | }
525 | }
526 |
527 | #endif
528 |
--------------------------------------------------------------------------------
/SwiftyGifExample/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifExample/2.gif
--------------------------------------------------------------------------------
/SwiftyGifExample/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifExample/3.gif
--------------------------------------------------------------------------------
/SwiftyGifExample/4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifExample/4.gif
--------------------------------------------------------------------------------
/SwiftyGifExample/5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifExample/5.gif
--------------------------------------------------------------------------------
/SwiftyGifExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SwiftyGif
4 | //
5 | // Created by Alexis Creuzot on 28/03/16.
6 | // Copyright © 2016 alexiscreuzot. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | func applicationWillResignActive(_ application: UIApplication) {
22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
24 | }
25 |
26 | func applicationDidEnterBackground(_ application: UIApplication) {
27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29 | }
30 |
31 | func applicationWillEnterForeground(_ application: UIApplication) {
32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
33 | }
34 |
35 | func applicationDidBecomeActive(_ application: UIApplication) {
36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
37 | }
38 |
39 | func applicationWillTerminate(_ application: UIApplication) {
40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
41 | }
42 |
43 |
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/SwiftyGifExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/SwiftyGifExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SwiftyGifExample/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/SwiftyGifExample/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
124 |
138 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/SwiftyGifExample/Cell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Cell.swift
3 | // SwiftyGif
4 | //
5 | // Created by Alexis Creuzot on 04/04/16.
6 | // Copyright © 2016 alexiscreuzot. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class Cell: UITableViewCell {
12 |
13 | @IBOutlet weak var gifImageView: UIImageView!
14 |
15 | override func awakeFromNib() {
16 | super.awakeFromNib()
17 | self.backgroundColor = .clear
18 | self.contentView.backgroundColor = .clear
19 | // Initialization code
20 | }
21 |
22 | override func prepareForReuse() {
23 | super.prepareForReuse()
24 | self.gifImageView.clear()
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/SwiftyGifExample/DetailController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailController.swift
3 | // SwiftyGif
4 | //
5 | // Created by Alexis Creuzot on 04/04/16.
6 | // Copyright © 2016 alexiscreuzot. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftyGif
11 |
12 | class DetailController: UIViewController {
13 |
14 | @IBOutlet private weak var imageView: UIImageView!
15 | @IBOutlet private weak var playPauseButton: UIButton!
16 | @IBOutlet private weak var forwardButton: UIButton!
17 | @IBOutlet private weak var rewindButton: UIButton!
18 |
19 | var path: String?
20 | let gifManager = SwiftyGifManager(memoryLimit: 60)
21 | var _rewindTimer: Timer?
22 | var _forwardTimer: Timer?
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | if let path = self.path {
28 | if let image = try? UIImage(imageName: path) {
29 | self.imageView.setImage(image, manager: gifManager, loopCount: -1)
30 | } else if let url = URL.init(string: path) {
31 | let loader = UIActivityIndicatorView.init(style: .white)
32 | self.imageView.setGifFromURL(url, customLoader: loader)
33 | } else {
34 | self.imageView.clear()
35 | }
36 | }
37 |
38 | // Gestures for gif control
39 | let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture))
40 | self.imageView.addGestureRecognizer(panGesture)
41 | self.imageView.isUserInteractionEnabled = true
42 | self.imageView.delegate = self
43 |
44 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.togglePlay))
45 | self.imageView.addGestureRecognizer(tapGesture)
46 | }
47 |
48 | // PRAGMA - Logic
49 |
50 | @objc func rewind(){
51 | self.imageView.showFrameForIndexDelta(-1)
52 | }
53 |
54 | @objc func forward(){
55 | self.imageView.showFrameForIndexDelta(1)
56 | }
57 |
58 | func stop(){
59 | self.imageView.stopAnimatingGif()
60 | self.playPauseButton.setTitle("►", for: .normal)
61 | }
62 |
63 | func play(){
64 | self.imageView.startAnimatingGif()
65 | self.playPauseButton.setTitle("❚❚", for: .normal)
66 | }
67 |
68 | // PRAGMA - Actions
69 |
70 | @IBAction func togglePlay(){
71 | if self.imageView.isAnimatingGif() {
72 | stop()
73 | }else {
74 | play()
75 | }
76 | }
77 |
78 | @IBAction func rewindDown(){
79 | stop()
80 | _rewindTimer = Timer.scheduledTimer(timeInterval: 1.0/30.0, target: self, selector: #selector(self.rewind), userInfo: nil, repeats: true)
81 | }
82 |
83 | @IBAction func rewindUp(){
84 | _rewindTimer?.invalidate()
85 | _rewindTimer = nil
86 | }
87 |
88 | @IBAction func forwardDown(){
89 | stop()
90 | _forwardTimer = Timer.scheduledTimer(timeInterval: 1.0/30.0, target: self, selector: #selector(self.forward), userInfo: nil, repeats: true)
91 | }
92 |
93 | @IBAction func forwardUp(){
94 | _forwardTimer?.invalidate()
95 | _forwardTimer = nil
96 | }
97 |
98 | // PRAGMA - Gestures
99 |
100 | @objc func panGesture(sender:UIPanGestureRecognizer){
101 |
102 | switch sender.state {
103 | case .began:
104 | stop()
105 | break
106 |
107 | case .changed:
108 | if sender.velocity(in: sender.view).x > 0 {
109 | forward()
110 | } else{
111 | rewind()
112 | }
113 | break
114 |
115 | default:
116 | break
117 | }
118 | }
119 | }
120 |
121 | extension DetailController : SwiftyGifDelegate {
122 |
123 | func gifDidStart(sender: UIImageView) {
124 | print("gifDidStart")
125 | }
126 |
127 | func gifDidLoop(sender: UIImageView) {
128 | print("gifDidLoop")
129 | }
130 |
131 | func gifDidStop(sender: UIImageView) {
132 | print("gifDidStop")
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/SwiftyGifExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/SwiftyGifExample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SwiftyGifManager
4 | //
5 |
6 | import UIKit
7 | import SwiftyGif
8 |
9 | class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
10 |
11 | @IBOutlet weak var tableView: UITableView!
12 |
13 | let gifManager = SwiftyGifManager(memoryLimit:100)
14 | let images = [
15 | "https://media.giphy.com/media/5tkEiBCurffluctzB7/giphy.gif",
16 | "2.gif",
17 | "https://media.giphy.com/media/5xtDarmOIekHPQSZEpq/giphy.gif",
18 | "3.gif",
19 | "https://media.giphy.com/media/3oEjHM2xehrp0lv6bC/giphy.gif",
20 | "5.gif",
21 | "https://media.giphy.com/media/l1J9qg0MqSZcQTuGk/giphy.gif",
22 | "4.gif",
23 | ]
24 |
25 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
26 | if let detailController = segue.destination as? DetailController {
27 | let indexPath = self.tableView.indexPathForSelectedRow
28 | detailController.path = images[indexPath!.row]
29 | }
30 | }
31 |
32 | // MARK: - TableView Datasource
33 |
34 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
35 | return images.count
36 | }
37 |
38 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
39 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as! Cell
40 |
41 | if let image = try? UIImage(imageName: images[indexPath.row]) {
42 | cell.gifImageView.setImage(image, manager: gifManager, loopCount: -1)
43 | } else if let url = URL.init(string: images[indexPath.row]) {
44 | let loader = UIActivityIndicatorView.init(style: .white)
45 | cell.gifImageView.setGifFromURL(url, customLoader: loader)
46 | } else {
47 | cell.gifImageView.clear()
48 | }
49 |
50 | return cell
51 | }
52 |
53 | // MARK: - TableView Delegate
54 |
55 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
56 | return 200
57 | }
58 |
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/SwiftyGifMacExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SwiftyGifMacExample
4 | //
5 | // Created by Carlo Rapisarda on 2020-08-04.
6 | // Copyright © 2020 alexiscreuzot. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 |
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | // Insert code here to initialize your application
18 | }
19 |
20 | func applicationWillTerminate(_ aNotification: Notification) {
21 | // Insert code here to tear down your application
22 | }
23 |
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/SwiftyGifMacExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/SwiftyGifMacExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftyGifMacExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2020 alexiscreuzot. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 | NSSupportsAutomaticTermination
32 |
33 | NSSupportsSuddenTermination
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/SwiftyGifMacExample/SwiftyGifMacExample.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/SwiftyGifMacExample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SwiftyGifMacExample
4 | //
5 | // Created by Carlo Rapisarda on 2020-08-04.
6 | // Copyright © 2020 alexiscreuzot. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftyGif
11 |
12 | class ViewController: NSViewController {
13 |
14 | @IBOutlet private var gifImageView: NSImageView!
15 |
16 | let gifManager = SwiftyGifManager(memoryLimit:100)
17 | let images = [
18 | "https://media.giphy.com/media/5tkEiBCurffluctzB7/giphy.gif",
19 | "2.gif",
20 | "https://media.giphy.com/media/5xtDarmOIekHPQSZEpq/giphy.gif",
21 | "3.gif",
22 | "https://media.giphy.com/media/3oEjHM2xehrp0lv6bC/giphy.gif",
23 | "5.gif",
24 | "https://media.giphy.com/media/l1J9qg0MqSZcQTuGk/giphy.gif",
25 | "4.gif",
26 | ]
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 |
31 | let imageSource = images[0]
32 |
33 | if let image = try? NSImage(imageName: imageSource) {
34 | gifImageView.setImage(image, manager: gifManager, loopCount: -1)
35 | } else if let url = URL.init(string: imageSource) {
36 | gifImageView.setGifFromURL(url, showLoader: true)
37 | } else {
38 | gifImageView.clear()
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/SwiftyGifTests/Images/20000x20000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/Images/20000x20000.gif
--------------------------------------------------------------------------------
/SwiftyGifTests/Images/no_property_dictionary.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/Images/no_property_dictionary.gif
--------------------------------------------------------------------------------
/SwiftyGifTests/Images/single_frame_Zt2012.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/Images/single_frame_Zt2012.gif
--------------------------------------------------------------------------------
/SwiftyGifTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SwiftyGifTests/SwiftyGifTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyGifTests.swift
3 | // SwiftyGifTests
4 | //
5 | // Created by Bill, Chan Yiu Por on 03/06/19.
6 | // Copyright © 2019 BillChan. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import SwiftyGif
11 | import SnapshotTesting
12 |
13 | extension XCTestCase {
14 |
15 | func data(filename: String) -> Data? {
16 | let data = try? Data(contentsOf: url(for: filename))
17 |
18 | return data
19 | }
20 |
21 | func url(for filename: String) -> URL {
22 | let bundle = Bundle(for: type(of: self))
23 |
24 | let url = bundle.url(forResource: filename, withExtension: "")
25 |
26 | if let isFileURL = url?.isFileURL {
27 | XCTAssert(isFileURL)
28 | } else {
29 | XCTFail("\(filename) does not exist")
30 | }
31 |
32 | return url!
33 | }
34 | }
35 |
36 | final class SwiftyGifTests: XCTestCase {
37 |
38 | var sut: UIImage!
39 | let gifManager = SwiftyGifManager(memoryLimit:100)
40 |
41 | override func setUp() {
42 | // Put setup code here. This method is called before the invocation of each test method in the class.
43 | }
44 |
45 | override func tearDown() {
46 | // Put teardown code here. This method is called after the invocation of each test method in the class.
47 | sut = nil
48 | }
49 |
50 |
51 | @discardableResult
52 | private func createImage(gifName: String, file: StaticString = #file, testName: String = #function, line: UInt = #line) -> UIImage! {
53 | let data = self.data(filename: gifName)!
54 |
55 | do {
56 | sut = try UIImage(imageData: data)
57 | } catch let error {
58 | XCTFail(error.localizedDescription)
59 | return nil
60 | }
61 |
62 | return sut
63 | }
64 |
65 | private func createImageView(gifName: String, gifManager: SwiftyGifManager = .defaultManager, file: StaticString = #file, testName: String = #function, line: UInt = #line) -> UIImageView! {
66 |
67 | createImage(gifName: gifName)
68 |
69 | if sut == nil {
70 | return nil
71 | }
72 |
73 | let imageView = UIImageView()
74 | imageView.setImage(sut, manager: gifManager)
75 |
76 | return imageView
77 | }
78 |
79 | private func asset(gifName: String, file: StaticString = #file, testName: String = #function, line: UInt = #line) {
80 | let imageView = createImageView(gifName: gifName)
81 |
82 | // can not snapshot the UIImageView directly since it would produce nil image. Snapshot imageView.currentImage instead
83 | let image: UIImage
84 | if let gifImage = imageView?.currentImage {
85 | image = gifImage
86 | } else {
87 | image = imageView!.image!
88 | }
89 | assertSnapshot(matching: image, as: .image, file: file, testName: testName, line: line)
90 | }
91 |
92 | func testThatNonAnimatedGIFCanBeLoadedWithUIImage() {
93 | // GIVEN
94 | let gifName = "single_frame_Zt2012.gif"
95 | let data = self.data(filename: gifName)!
96 |
97 | // WHEN
98 | sut = UIImage(data: data)
99 |
100 | // THEN
101 |
102 | let imageView = UIImageView(image: sut)
103 |
104 | assertSnapshot(matching: imageView, as: .image)
105 | }
106 |
107 | func testThatNonAnimatedGIFCanBeLoaded() {
108 | asset(gifName: "single_frame_Zt2012.gif")
109 | }
110 |
111 | func testThatVeryBigGIFCanBeLoaded() {
112 | asset(gifName: "20000x20000.gif")
113 | }
114 |
115 | func DISABLE_testThatVeryBigGIFCanBeLoaded() { ///TODO: used 18GB of memory and the output snapshot PNG is 31 MB
116 | // GIVEN
117 | let gifName = "20000x20000.gif"
118 | let data = self.data(filename: gifName)!
119 |
120 | // WHEN
121 | sut = UIImage(data: data)
122 |
123 | // THEN
124 |
125 | let imageView = UIImageView(image: sut)
126 |
127 | assertSnapshot(matching: imageView, as: .image)
128 | }
129 |
130 | func testThatGIFWithoutkCGImagePropertyGIFDictionaryCanBeLoaded() {
131 | asset(gifName: "no_property_dictionary.gif")
132 | }
133 |
134 | func testThatNonGifImageCanBeLoaded() {
135 | asset(gifName: "sample.jpg")
136 | }
137 |
138 | func testThat15MBGIFCanBeLoaded() {
139 | asset(gifName: "15MB_Einstein_rings_zoom.gif")
140 | }
141 |
142 | func testThatImageViewCanBeRecycledForGIF() {
143 | let data = self.data(filename: "sample.jpg")!
144 |
145 | let imageView = UIImageView()
146 |
147 | let normalImage = UIImage(data: data)!
148 | imageView.setImage(normalImage, manager: gifManager)
149 | imageView.frame = CGRect(origin: .zero, size: normalImage.size)
150 |
151 |
152 | XCTAssertFalse(gifManager.containsImageView(imageView))
153 | ///snapshot of the normal image
154 | assertSnapshot(matching: imageView, as: .image)
155 |
156 | createImage(gifName: "15MB_Einstein_rings_zoom.gif")
157 | imageView.setGifImage(sut, manager: gifManager)
158 |
159 | ///snapshot of the GIF
160 | assertSnapshot(matching: imageView.currentImage!, as: .image)
161 |
162 | ///snapshot of the normal image
163 | gifManager.deleteImageView(imageView)
164 |
165 | imageView.image = normalImage
166 | assertSnapshot(matching: imageView, as: .image)
167 | }
168 |
169 | /// GIF -> normal image recycling
170 | func testThatImageViewCanBeRecycledForNormalImage() {
171 | // GIVEN
172 | let imageView = createImageView(gifName: "15MB_Einstein_rings_zoom.gif", gifManager: gifManager)!
173 |
174 | ///snapshot of the GIF
175 | assertSnapshot(matching: imageView.currentImage!, as: .image)
176 |
177 | // WHEN
178 | let data = self.data(filename: "sample.jpg")!
179 |
180 | let updateImage = UIImage(data: data)!
181 | imageView.setImage(updateImage, manager: gifManager)
182 | imageView.frame = CGRect(origin: .zero, size: updateImage.size)
183 |
184 | // THEN
185 | XCTAssertFalse(gifManager.containsImageView(imageView))
186 |
187 | ///snapshot of the updated JPG
188 | assertSnapshot(matching: imageView, as: .image)
189 |
190 | }
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThat15MBGIFCanBeLoaded.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThat15MBGIFCanBeLoaded.1.png
--------------------------------------------------------------------------------
/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatGIFWithoutkCGImagePropertyGIFDictionaryCanBeLoaded.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatGIFWithoutkCGImagePropertyGIFDictionaryCanBeLoaded.1.png
--------------------------------------------------------------------------------
/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatImageViewCanBeRecycled.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatImageViewCanBeRecycled.1.png
--------------------------------------------------------------------------------
/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatImageViewCanBeRecycledForGIF.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatImageViewCanBeRecycledForGIF.2.png
--------------------------------------------------------------------------------
/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatImageViewCanBeRecycledForNormalImage.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatImageViewCanBeRecycledForNormalImage.1.png
--------------------------------------------------------------------------------
/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatNonAnimatedGIFCanBeLoaded.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatNonAnimatedGIFCanBeLoaded.1.png
--------------------------------------------------------------------------------
/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatNonAnimatedGIFCanBeLoadedWithUIImage.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/SwiftyGifTests/__Snapshots__/SwiftyGifTests/testThatNonAnimatedGIFCanBeLoadedWithUIImage.1.png
--------------------------------------------------------------------------------
/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/example.gif
--------------------------------------------------------------------------------
/projec-file-explain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexiscreuzot/SwiftyGif/c44e2b19f80baa9df496d6415c81d12bac852d46/projec-file-explain.png
--------------------------------------------------------------------------------