diff --git a/Cartfile.resolved b/Cartfile.resolved
index 4c51bd6..51a6c61 100644
--- a/Cartfile.resolved
+++ b/Cartfile.resolved
@@ -1 +1 @@
-github "onevcat/Kingfisher" "2.0.4"
+github "onevcat/Kingfisher" "2.1.0"
diff --git a/Carthage/Checkouts/Kingfisher/CHANGELOG.md b/Carthage/Checkouts/Kingfisher/CHANGELOG.md
index b6cbf76..15408f6 100644
--- a/Carthage/Checkouts/Kingfisher/CHANGELOG.md
+++ b/Carthage/Checkouts/Kingfisher/CHANGELOG.md
@@ -2,6 +2,19 @@
-----
+## [2.1.0 - Prefetching](https://github.com/onevcat/Kingfisher/releases/tag/2.1.0) (2016-03-10)
+
+#### Add
+* Add `ImagePrefetcher` and related prefetching methods to allow downloading and caching images before you need to display them. [#249](https://github.com/onevcat/Kingfisher/pull/249)
+* A protocol (`AuthenticationChallengeResponable`) for responsing authentication challenge. You can now set `authenticationChallengeResponder` of `ImageDownloader` and use your own authentication policy. [#226](https://github.com/onevcat/Kingfisher/issues/226)
+* An API (`cachePathForKey(:)`) to get real path for a specified key in a cache. [#256](https://github.com/onevcat/Kingfisher/pull/256)
+
+#### Fix
+* Disable background decoding for images from memory cache. This improves the performance of image loading for in-memory cached images and fix a flicker when you try to load image with background decoding. [#257](https://github.com/onevcat/Kingfisher/pull/257)
+* A potential crash in `ImageCache` when an empty image is passed into.
+
+---
+
## [2.0.4 - Sorry Pipelining](https://github.com/onevcat/Kingfisher/releases/tag/2.0.4) (2016-02-27)
#### Fix
diff --git a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-Demo/Info.plist b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-Demo/Info.plist
index f8069ff..74cebc2 100644
--- a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-Demo/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-Demo/Info.plist
@@ -15,11 +15,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
LSRequiresIPhoneOS
UILaunchStoryboardName
diff --git a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-OSX-Demo/Info.plist b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-OSX-Demo/Info.plist
index f6f0485..33a1ec9 100644
--- a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-OSX-Demo/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-OSX-Demo/Info.plist
@@ -17,11 +17,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSHumanReadableCopyright
diff --git a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-tvOS-Demo/Info.plist b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-tvOS-Demo/Info.plist
index 774fdf7..19cb8c8 100644
--- a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-tvOS-Demo/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-tvOS-Demo/Info.plist
@@ -15,11 +15,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
LSRequiresIPhoneOS
UIMainStoryboardFile
diff --git a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo Extension/Info.plist b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo Extension/Info.plist
index 774b9b1..8964666 100644
--- a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo Extension/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo Extension/Info.plist
@@ -17,11 +17,11 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
NSExtension
NSExtensionAttributes
diff --git a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo/Info.plist b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo/Info.plist
index f0b9c93..64dcbbb 100644
--- a/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Demo/Kingfisher-watchOS-Demo/Info.plist
@@ -17,11 +17,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
diff --git a/Carthage/Checkouts/Kingfisher/Kingfisher.podspec b/Carthage/Checkouts/Kingfisher/Kingfisher.podspec
index 5bd04de..a015694 100644
--- a/Carthage/Checkouts/Kingfisher/Kingfisher.podspec
+++ b/Carthage/Checkouts/Kingfisher/Kingfisher.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "Kingfisher"
- s.version = "2.0.4"
+ s.version = "2.1.0"
s.summary = "A lightweight and pure Swift implemented library for downloading and cacheing image from the web."
s.description = <<-DESC
diff --git a/Carthage/Checkouts/Kingfisher/Kingfisher.xcodeproj/project.pbxproj b/Carthage/Checkouts/Kingfisher/Kingfisher.xcodeproj/project.pbxproj
index c599019..2c4131d 100644
--- a/Carthage/Checkouts/Kingfisher/Kingfisher.xcodeproj/project.pbxproj
+++ b/Carthage/Checkouts/Kingfisher/Kingfisher.xcodeproj/project.pbxproj
@@ -119,6 +119,13 @@
D1ED2D401AD2D09F00CFC3EB /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1ED2D351AD2D09F00CFC3EB /* Kingfisher.framework */; };
D1ED2D4C1AD2D09F00CFC3EB /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1ED2D351AD2D09F00CFC3EB /* Kingfisher.framework */; };
D1ED2D4D1AD2D09F00CFC3EB /* Kingfisher.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D1ED2D351AD2D09F00CFC3EB /* Kingfisher.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ D9638BA01C7DBA660046523D /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9638B9F1C7DBA660046523D /* ImagePrefetcher.swift */; };
+ D9638BA11C7DBA660046523D /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9638B9F1C7DBA660046523D /* ImagePrefetcher.swift */; };
+ D9638BA21C7DBA660046523D /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9638B9F1C7DBA660046523D /* ImagePrefetcher.swift */; };
+ D9638BA31C7DBA660046523D /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9638B9F1C7DBA660046523D /* ImagePrefetcher.swift */; };
+ D9638BA61C7DC71F0046523D /* ImagePrefetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9638BA41C7DC71F0046523D /* ImagePrefetcherTests.swift */; };
+ D9638BA71C7DCF560046523D /* ImagePrefetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9638BA41C7DC71F0046523D /* ImagePrefetcherTests.swift */; };
+ D9638BA81C7DCF570046523D /* ImagePrefetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9638BA41C7DC71F0046523D /* ImagePrefetcherTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -314,6 +321,8 @@
D1ED2D351AD2D09F00CFC3EB /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D1ED2D3F1AD2D09F00CFC3EB /* KingfisherTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KingfisherTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D809C0611AAB7CA1AE240862 /* Pods-KingfisherTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests/Pods-KingfisherTests.debug.xcconfig"; sourceTree = ""; };
+ D9638B9F1C7DBA660046523D /* ImagePrefetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImagePrefetcher.swift; path = Sources/ImagePrefetcher.swift; sourceTree = ""; };
+ D9638BA41C7DC71F0046523D /* ImagePrefetcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePrefetcherTests.swift; sourceTree = ""; };
FE96DF45BEE5F8EBB01C7956 /* Pods-KingfisherTests-OSX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests-OSX.release.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests-OSX/Pods-KingfisherTests-OSX.release.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
@@ -430,6 +439,7 @@
D10945EA1C526B6C001408EB /* Image.swift */,
D10945EB1C526B6C001408EB /* ImageCache.swift */,
D10945EC1C526B6C001408EB /* ImageDownloader.swift */,
+ D9638B9F1C7DBA660046523D /* ImagePrefetcher.swift */,
D10945ED1C526B6C001408EB /* ImageTransition.swift */,
D10945EE1C526B6C001408EB /* ImageView+Kingfisher.swift */,
D10945EF1C526B6C001408EB /* Info.plist */,
@@ -475,6 +485,7 @@
D12E0C451C47F23500AC98AD /* ImageCacheTests.swift */,
D12E0C461C47F23500AC98AD /* ImageDownloaderTests.swift */,
D12E0C471C47F23500AC98AD /* ImageExtensionTests.swift */,
+ D9638BA41C7DC71F0046523D /* ImagePrefetcherTests.swift */,
D12E0C481C47F23500AC98AD /* ImageViewExtensionTests.swift */,
D12E0C4A1C47F23500AC98AD /* KingfisherManagerTests.swift */,
D12E0C4B1C47F23500AC98AD /* KingfisherOptionsInfoTests.swift */,
@@ -1252,6 +1263,7 @@
D109461F1C526C61001408EB /* KingfisherManager.swift in Sources */,
D10946201C526C61001408EB /* KingfisherOptionsInfo.swift in Sources */,
D10946211C526C61001408EB /* Resource.swift in Sources */,
+ D9638BA21C7DBA660046523D /* ImagePrefetcher.swift in Sources */,
D10946221C526C61001408EB /* String+MD5.swift in Sources */,
D10946231C526C61001408EB /* ThreadHelper.swift in Sources */,
);
@@ -1273,6 +1285,7 @@
D12E0C761C47F71700AC98AD /* KingfisherTestHelper.swift in Sources */,
D12E0C6E1C47F6FE00AC98AD /* ImageCacheTests.swift in Sources */,
D12E0C6F1C47F6FE00AC98AD /* ImageDownloaderTests.swift in Sources */,
+ D9638BA71C7DCF560046523D /* ImagePrefetcherTests.swift in Sources */,
D12E0C701C47F6FE00AC98AD /* ImageExtensionTests.swift in Sources */,
D12E0C711C47F6FE00AC98AD /* ImageViewExtensionTests.swift in Sources */,
D12E0C721C47F6FE00AC98AD /* KingfisherManagerTests.swift in Sources */,
@@ -1288,6 +1301,7 @@
D12E0C891C47F7B700AC98AD /* KingfisherTestHelper.swift in Sources */,
D12E0C821C47F7AF00AC98AD /* ImageCacheTests.swift in Sources */,
D12E0C831C47F7AF00AC98AD /* ImageDownloaderTests.swift in Sources */,
+ D9638BA81C7DCF570046523D /* ImagePrefetcherTests.swift in Sources */,
D12E0C841C47F7AF00AC98AD /* ImageExtensionTests.swift in Sources */,
D12E0C851C47F7AF00AC98AD /* ImageViewExtensionTests.swift in Sources */,
D12E0C861C47F7AF00AC98AD /* KingfisherManagerTests.swift in Sources */,
@@ -1316,6 +1330,7 @@
D10946121C526C0D001408EB /* ImageView+Kingfisher.swift in Sources */,
D10946131C526C0D001408EB /* KingfisherManager.swift in Sources */,
D10946141C526C0D001408EB /* KingfisherOptionsInfo.swift in Sources */,
+ D9638BA11C7DBA660046523D /* ImagePrefetcher.swift in Sources */,
D10946151C526C0D001408EB /* Resource.swift in Sources */,
D10946161C526C0D001408EB /* String+MD5.swift in Sources */,
D10946171C526C0D001408EB /* ThreadHelper.swift in Sources */,
@@ -1330,6 +1345,7 @@
D109462D1C526CF5001408EB /* ImageTransition.swift in Sources */,
D10946251C526CE8001408EB /* Image.swift in Sources */,
D10946261C526CE8001408EB /* ImageCache.swift in Sources */,
+ D9638BA31C7DBA660046523D /* ImagePrefetcher.swift in Sources */,
D10946271C526CE8001408EB /* ImageDownloader.swift in Sources */,
D10946281C526CE8001408EB /* KingfisherManager.swift in Sources */,
D10946291C526CE8001408EB /* KingfisherOptionsInfo.swift in Sources */,
@@ -1369,6 +1385,7 @@
D10945FB1C526B86001408EB /* ImageView+Kingfisher.swift in Sources */,
D10945FC1C526B86001408EB /* KingfisherManager.swift in Sources */,
D10945FD1C526B86001408EB /* KingfisherOptionsInfo.swift in Sources */,
+ D9638BA01C7DBA660046523D /* ImagePrefetcher.swift in Sources */,
D10945FE1C526B86001408EB /* Resource.swift in Sources */,
D10945FF1C526B86001408EB /* String+MD5.swift in Sources */,
D10946001C526B86001408EB /* ThreadHelper.swift in Sources */,
@@ -1383,6 +1400,7 @@
D12E0C571C47F23500AC98AD /* KingfisherTestHelper.swift in Sources */,
D12E0C581C47F23500AC98AD /* UIButtonExtensionTests.swift in Sources */,
D12E0C561C47F23500AC98AD /* KingfisherOptionsInfoTests.swift in Sources */,
+ D9638BA61C7DC71F0046523D /* ImagePrefetcherTests.swift in Sources */,
D12E0C551C47F23500AC98AD /* KingfisherManagerTests.swift in Sources */,
D12E0C511C47F23500AC98AD /* ImageDownloaderTests.swift in Sources */,
D12E0C521C47F23500AC98AD /* ImageExtensionTests.swift in Sources */,
@@ -1485,11 +1503,11 @@
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
GCC_NO_COMMON_BLOCKS = YES;
@@ -1511,11 +1529,11 @@
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
GCC_NO_COMMON_BLOCKS = YES;
@@ -1677,11 +1695,11 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = Sources/Info.plist;
@@ -1704,11 +1722,11 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = Sources/Info.plist;
@@ -1729,11 +1747,11 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = Sources/Info.plist;
@@ -1754,11 +1772,11 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = Sources/Info.plist;
@@ -1961,10 +1979,10 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@@ -1985,10 +2003,10 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 543;
+ CURRENT_PROJECT_VERSION = 567;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 543;
+ DYLIB_CURRENT_VERSION = 567;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
diff --git a/Carthage/Checkouts/Kingfisher/README.md b/Carthage/Checkouts/Kingfisher/README.md
index 6291608..cf823a4 100644
--- a/Carthage/Checkouts/Kingfisher/README.md
+++ b/Carthage/Checkouts/Kingfisher/README.md
@@ -18,8 +18,11 @@
+
+
+
Kingfisher is a lightweight and pure Swift implemented library for downloading and caching image from the web. This project is heavily inspired by the popular [SDWebImage](https://github.com/rs/SDWebImage). And it provides you a chance to use pure Swift alternative in your next app.
@@ -31,6 +34,7 @@ Kingfisher is a lightweight and pure Swift implemented library for downloading a
* Cache management. You can set the max duration or size the cache takes. From this, the cache will be cleaned automatically to prevent taking too many resources.
* Modern framework. Kingfisher uses `NSURLSession` and the latest technology of GCD, which makes it a strong and swift framework. It also provides you easy APIs to use.
* Cancelable processing task. You can cancel the downloading process if it is not needed anymore.
+* Prefetching. You can prefetch and cache the images which might soon appear in the page. It will bring your users great experience.
* Independent components. You can use the downloader or caching system separately. Or even create your own cache based on Kingfisher's code.
* Options to decompress the image in background before rendering it, which could improve the UI performance.
* Categories over `UIImageView`, `NSImage` and `UIButton` for setting image from an URL directly. Use the same code across all Apple platforms.
@@ -61,7 +65,7 @@ source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
-pod 'Kingfisher', '~> 2.0'
+pod 'Kingfisher', '~> 2.1'
```
Then, run the following command:
@@ -86,7 +90,7 @@ $ brew install carthage
To integrate Kingfisher into your Xcode project using Carthage, specify it in your `Cartfile`:
``` ogdl
-github "onevcat/Kingfisher" ~> 2.0
+github "onevcat/Kingfisher" ~> 2.1
```
Then, run the following command to build the Kingfisher framework:
@@ -302,6 +306,29 @@ cache.clearDiskCache()
cache.cleanExpiredDiskCache()
```
+### Prefetching
+
+You could prefetch some images and cache them before you display them on the screen. This is useful when you know a list of image resources you know they would probably be shown later. Since the prefetched images are already in the cache system, there is no need to request them again when you really need to display them in a image view. It will boost your UI and bring your users great experience.
+
+To prefetch some images, you could use the `ImagePrefetcher`:
+
+```swift
+let urls = ["http://example.com/image1.jpg", "http://example.com/image2.jpg"].map { NSURL(string: $0)! }
+let prefetcher = ImagePrefetcher(urls: urls, optionsInfo: nil, progressBlock: nil, completionHandler: {
+ (skippedResources, failedResources, completedResources) -> () in
+ print("These resources are prefetched: \(completedResources)")
+})
+prefetcher.start()
+```
+
+You can also stop a prefetch whenever you need:
+
+```swift
+prefetcher.stop()
+```
+
+After prefetching, you could retrieve image or set the image view with other Kingfisher's methods, with the same `ImageCache` object you used for the prefetching.
+
## Future of Kingfisher
I want to keep Kingfisher slim. This framework will focus on providing a simple solution for image downloading and caching. But that does not mean the framework will not be improved. Kingfisher is far away from perfect, and necessary and useful features will be added later to make it better.
@@ -317,5 +344,3 @@ Follow and contact me on [Twitter](http://twitter.com/onevcat) or [Sina Weibo](h
## License
Kingfisher is released under the MIT license. See LICENSE for details.
-
-
diff --git a/Carthage/Checkouts/Kingfisher/Sources/Image.swift b/Carthage/Checkouts/Kingfisher/Sources/Image.swift
index a800846..260b789 100644
--- a/Carthage/Checkouts/Kingfisher/Sources/Image.swift
+++ b/Carthage/Checkouts/Kingfisher/Sources/Image.swift
@@ -145,8 +145,11 @@ extension Image {
// MARK: - PNG
func ImagePNGRepresentation(image: Image) -> NSData? {
#if os(OSX)
- let rep = NSBitmapImageRep(CGImage: image.CGImage)
- return rep.representationUsingType(.NSPNGFileType, properties:[:])
+ if let cgimage = image.CGImage {
+ let rep = NSBitmapImageRep(CGImage: cgimage)
+ return rep.representationUsingType(.NSPNGFileType, properties:[:])
+ }
+ return nil
#else
return UIImagePNGRepresentation(image)
#endif
diff --git a/Carthage/Checkouts/Kingfisher/Sources/ImageCache.swift b/Carthage/Checkouts/Kingfisher/Sources/ImageCache.swift
index bd009c1..3f788d5 100644
--- a/Carthage/Checkouts/Kingfisher/Sources/ImageCache.swift
+++ b/Carthage/Checkouts/Kingfisher/Sources/ImageCache.swift
@@ -279,18 +279,8 @@ extension ImageCache {
let options = options ?? KingfisherEmptyOptionsInfo
if let image = self.retrieveImageInMemoryCacheForKey(key) {
- //Found image in memory cache.
- if options.backgroundDecode {
- dispatch_async(self.processQueue, { () -> Void in
- let result = image.kf_decodedImage(scale: options.scaleFactor)
- dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
- completionHandler(result, .Memory)
- })
- })
- } else {
- dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
- completionHandler(image, .Memory)
- })
+ dispatch_async_safely_to_queue(options.callbackDispatchQueue) { () -> Void in
+ completionHandler(image, .Memory)
}
} else {
var sSelf: ImageCache! = self
@@ -552,6 +542,21 @@ extension ImageCache {
public let cacheType: CacheType?
}
+ /**
+ Determine if a cached image exists for the given image, as keyed by the URL. It will return true if the
+ image is found either in memory or on disk. Essentially as long as there is a cache of the image somewhere
+ true is returned. A convenience method that decodes `isImageCachedForKey`.
+
+ - parameter url: The image URL.
+
+ - returns: True if the image is cached, false otherwise.
+ */
+ public func cachedImageExistsforURL(url: NSURL) -> Bool {
+ let resource = Resource(downloadURL: url)
+ let result = isImageCachedForKey(resource.cacheKey)
+ return result.cached
+ }
+
/**
Check whether an image is cached for a key.
@@ -604,6 +609,21 @@ extension ImageCache {
})
})
}
+
+ /**
+ Get the cache path for the key.
+ It is useful for projects with UIWebView or anyone that needs access to the local file path.
+
+ i.e. ``
+
+ - Note: This method does not guarantee there is an image already cached in the path.
+ You could use `isImageCachedForKey` method to check whether the image is cached under that key.
+ */
+ public func cachePathForKey(key: String) -> String {
+ let fileName = cacheFileNameForKey(key)
+ return (diskCachePath as NSString).stringByAppendingPathComponent(fileName)
+ }
+
}
// MARK: - Internal Helper
@@ -622,11 +642,6 @@ extension ImageCache {
return NSData(contentsOfFile: filePath)
}
- func cachePathForKey(key: String) -> String {
- let fileName = cacheFileNameForKey(key)
- return (diskCachePath as NSString).stringByAppendingPathComponent(fileName)
- }
-
func cacheFileNameForKey(key: String) -> String {
return key.kf_MD5
}
diff --git a/Carthage/Checkouts/Kingfisher/Sources/ImageDownloader.swift b/Carthage/Checkouts/Kingfisher/Sources/ImageDownloader.swift
index 3a9316c..55a4d80 100644
--- a/Carthage/Checkouts/Kingfisher/Sources/ImageDownloader.swift
+++ b/Carthage/Checkouts/Kingfisher/Sources/ImageDownloader.swift
@@ -101,6 +101,37 @@ public enum KingfisherError: Int {
optional func imageDownloader(downloader: ImageDownloader, didDownloadImage image: Image, forURL URL: NSURL, withResponse response: NSURLResponse)
}
+/// Protocol indicates that an authentication challenge could be handled.
+public protocol AuthenticationChallengeResponable: class {
+ /**
+ Called when an session level authentication challenge is received.
+ This method provide a chance to handle and response to the authentication challenge before downloading could start.
+
+ - parameter downloader: The downloader which receives this challenge.
+ - parameter challenge: An object that contains the request for authentication.
+ - parameter completionHandler: A handler that your delegate method must call.
+
+ - Note: This method is a forward from `URLSession(:didReceiveChallenge:completionHandler:)`. Please refer to the document of it in `NSURLSessionDelegate`.
+ */
+ func downloder(downloader: ImageDownloader, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)
+}
+
+extension AuthenticationChallengeResponable {
+
+ func downloder(downloader: ImageDownloader, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
+
+ if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
+ if let trustedHosts = downloader.trustedHosts where trustedHosts.contains(challenge.protectionSpace.host) {
+ let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
+ completionHandler(.UseCredential, credential)
+ return
+ }
+ }
+
+ completionHandler(.PerformDefaultHandling, nil)
+ }
+}
+
/// `ImageDownloader` represents a downloading manager for requesting the image with a URL from server.
public class ImageDownloader: NSObject {
@@ -121,7 +152,7 @@ public class ImageDownloader: NSObject {
/// The duration before the download is timeout. Default is 15 seconds.
public var downloadTimeout: NSTimeInterval = 15.0
- /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this set will be ignored. You can use this set to specify the self-signed site.
+ /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this set will be ignored. You can use this set to specify the self-signed site. It only will be used if you don't specify the `authenticationChallengeResponder`. If `authenticationChallengeResponder` is set, this property will be ignored and the implemention of `authenticationChallengeResponder` will be used instead.
public var trustedHosts: Set?
/// Use this to set supply a configuration for the downloader. By default, NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used. You could change the configuration before a downloaing task starts. A configuration without persistent storage for caches is requsted for downloader working correctly.
@@ -134,12 +165,16 @@ public class ImageDownloader: NSObject {
/// Whether the download requests should use pipeling or not. Default is false.
public var requestsUsePipeling = false
- private var sessionHandler: ImageDownloaderSessionHandler?
+ private let sessionHandler: ImageDownloaderSessionHandler
private var session: NSURLSession?
/// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more.
public weak var delegate: ImageDownloaderDelegate?
+ /// A responder for authentication challenge.
+ /// Downloader will forward the received authentication challenge for the downloading session to this responder.
+ public weak var authenticationChallengeResponder: AuthenticationChallengeResponable?
+
// MARK: - Internal property
let barrierQueue: dispatch_queue_t
let processQueue: dispatch_queue_t
@@ -169,9 +204,13 @@ public class ImageDownloader: NSObject {
barrierQueue = dispatch_queue_create(downloaderBarrierName + name, DISPATCH_QUEUE_CONCURRENT)
processQueue = dispatch_queue_create(imageProcessQueueName + name, DISPATCH_QUEUE_CONCURRENT)
+ sessionHandler = ImageDownloaderSessionHandler()
+
super.init()
- sessionHandler = ImageDownloaderSessionHandler()
+ // Provide a default implement for challenge responder.
+ authenticationChallengeResponder = sessionHandler
+
session = NSURLSession(configuration: sessionConfiguration, delegate: sessionHandler, delegateQueue: NSOperationQueue.mainQueue())
}
@@ -260,7 +299,7 @@ extension ImageDownloader {
dataTask.resume()
// Hold self while the task is executing.
- self.sessionHandler?.downloadHolder = self
+ self.sessionHandler.downloadHolder = self
}
fetchLoad.downloadTaskCount += 1
@@ -315,7 +354,7 @@ extension ImageDownloader {
/// The session object will hold its delegate until it gets invalidated.
/// If we use `ImageDownloader` as the session delegate, it will not be released.
/// So we need an additional handler to break the retain cycle.
-class ImageDownloaderSessionHandler: NSObject, NSURLSessionDataDelegate {
+class ImageDownloaderSessionHandler: NSObject, NSURLSessionDataDelegate, AuthenticationChallengeResponable {
// The holder will keep downloader not released while a data task is being executed.
// It will be set when the task started, and reset when the task finished.
@@ -372,15 +411,7 @@ class ImageDownloaderSessionHandler: NSObject, NSURLSessionDataDelegate {
return
}
- if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
- if let trustedHosts = downloader.trustedHosts where trustedHosts.contains(challenge.protectionSpace.host) {
- let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
- completionHandler(.UseCredential, credential)
- return
- }
- }
-
- completionHandler(.PerformDefaultHandling, nil)
+ downloader.authenticationChallengeResponder?.downloder(downloader, didReceiveChallenge: challenge, completionHandler: completionHandler)
}
private func callbackWithImage(image: Image?, error: NSError?, imageURL: NSURL, originalData: NSData?) {
diff --git a/Carthage/Checkouts/Kingfisher/Sources/ImagePrefetcher.swift b/Carthage/Checkouts/Kingfisher/Sources/ImagePrefetcher.swift
new file mode 100644
index 0000000..9ab6b07
--- /dev/null
+++ b/Carthage/Checkouts/Kingfisher/Sources/ImagePrefetcher.swift
@@ -0,0 +1,272 @@
+//
+// ImagePrefetcher.swift
+// Kingfisher
+//
+// Created by Claire Knight on 24/02/2016
+//
+// Copyright (c) 2016 Wei Wang
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+
+#if os(OSX)
+ import AppKit
+#else
+ import UIKit
+#endif
+
+
+/// Progress update block of prefetcher.
+///
+/// - `skippedResources`: An array of resources that are already cached before the prefetching starting.
+/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while downloading, encountered an error when downloading or the download not being started at all.
+/// - `completedResources`: An array of resources that are downloaded and cached successfully.
+public typealias PrefetcherProgressBlock = ((skippedResources: [Resource], failedResources: [Resource], completedResources: [Resource]) -> ())
+
+/// Completion block of prefetcher.
+///
+/// - `skippedResources`: An array of resources that are already cached before the prefetching starting.
+/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while downloading, encountered an error when downloading or the download not being started at all.
+/// - `completedResources`: An array of resources that are downloaded and cached successfully.
+public typealias PrefetcherCompletionHandler = ((skippedResources: [Resource], failedResources: [Resource], completedResources: [Resource]) -> ())
+
+/// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them.
+/// This is useful when you know a list of image resources and want to download them before showing.
+public class ImagePrefetcher {
+
+ /// The maximum concurrent downloads to use when prefetching images. Default is 5.
+ public var maxConcurrentDownloads = 5
+
+ private let prefetchResources: [Resource]
+ private let optionsInfo: KingfisherOptionsInfo
+ private var progressBlock: PrefetcherProgressBlock?
+ private var completionHandler: PrefetcherCompletionHandler?
+
+ private var tasks = [NSURL: RetrieveImageDownloadTask]()
+
+ private var skippedResources = [Resource]()
+ private var completedResources = [Resource]()
+ private var failedResources = [Resource]()
+
+ private var requestedCount = 0
+ private var stopped = false
+
+ // The created manager used for prefetch. We will use the helper method in manager.
+ private let manager: KingfisherManager
+
+ private var finished: Bool {
+ return failedResources.count + skippedResources.count + completedResources.count == prefetchResources.count
+ }
+
+ /**
+ Init an image prefetcher with an array of URLs.
+
+ The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable.
+ After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process.
+ The images already cached will be skipped without downloading again.
+
+ - parameter urls: The URLs which should be prefetched.
+ - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
+ - parameter progressBlock: Called every time an resource is downloaded, skipped or cancelled.
+ - parameter completionHandler: Called when the whole prefetching process finished.
+
+ - returns: An `ImagePrefetcher` object.
+
+ - Note: By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
+ the downloader and cache target respectively. You can specify another downloader or cache by using a customized `KingfisherOptionsInfo`.
+ Both the progress and completion block will be invoked in main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method.
+ */
+ public convenience init(urls: [NSURL],
+ optionsInfo: KingfisherOptionsInfo? = nil,
+ progressBlock: PrefetcherProgressBlock? = nil,
+ completionHandler: PrefetcherCompletionHandler? = nil)
+ {
+ let resources = urls.map { Resource(downloadURL: $0) }
+ self.init(resources: resources, optionsInfo: optionsInfo, progressBlock: progressBlock, completionHandler: completionHandler)
+ }
+
+ /**
+ Init an image prefetcher with an array of resources.
+
+ The prefetcher should be initiated with a list of prefetching targets. The resources list is immutable.
+ After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process.
+ The images already cached will be skipped without downloading again.
+
+ - parameter resources: The resources which should be prefetched. See `Resource` type for more.
+ - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
+ - parameter progressBlock: Called every time an resource is downloaded, skipped or cancelled.
+ - parameter completionHandler: Called when the whole prefetching process finished.
+
+ - returns: An `ImagePrefetcher` object.
+
+ - Note: By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
+ the downloader and cache target respectively. You can specify another downloader or cache by using a customized `KingfisherOptionsInfo`.
+ Both the progress and completion block will be invoked in main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method.
+ */
+ public init(resources: [Resource],
+ optionsInfo: KingfisherOptionsInfo? = nil,
+ progressBlock: PrefetcherProgressBlock? = nil,
+ completionHandler: PrefetcherCompletionHandler? = nil)
+ {
+ prefetchResources = resources
+
+ // We want all callbacks from main queue, so we ignore the call back queue in options
+ let optionsInfoWithoutQueue = optionsInfo?.kf_removeAllMatchesIgnoringAssociatedValue(.CallbackDispatchQueue(nil))
+ self.optionsInfo = optionsInfoWithoutQueue ?? KingfisherEmptyOptionsInfo
+
+ let cache = self.optionsInfo.targetCache ?? ImageCache.defaultCache
+ let downloader = self.optionsInfo.downloader ?? ImageDownloader.defaultDownloader
+ manager = KingfisherManager(downloader: downloader, cache: cache)
+
+ self.progressBlock = progressBlock
+ self.completionHandler = completionHandler
+ }
+
+ /**
+ Start to download the resources and cache them. This can be useful for background downloading
+ of assets that are required for later use in an app. This code will not try and update any UI
+ with the results of the process.
+ */
+ public func start()
+ {
+ // Since we want to handle the resources cancellation in main thread only.
+ dispatch_async_safely_to_main_queue { () -> () in
+
+ guard !self.stopped else {
+ assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.")
+ self.handleComplete()
+ return
+ }
+
+ guard self.maxConcurrentDownloads > 0 else {
+ assertionFailure("There should be concurrent downloads value should be at least 1.")
+ self.handleComplete()
+ return
+ }
+
+ guard self.prefetchResources.count > 0 else {
+ self.handleComplete()
+ return
+ }
+
+ let initialConcurentDownloads = min(self.prefetchResources.count, self.maxConcurrentDownloads)
+ for i in 0 ..< initialConcurentDownloads {
+ self.startPrefetchingResource(self.prefetchResources[i])
+ }
+ }
+ }
+
+
+ /**
+ Stop current downloading progress, and cancel any future prefetching activity that might be occuring.
+ */
+ public func stop() {
+ dispatch_async_safely_to_main_queue {
+
+ if self.finished {
+ return
+ }
+
+ self.stopped = true
+ self.tasks.forEach { (_, task) -> () in
+ task.cancel()
+ }
+ }
+ }
+
+ func downloadAndCacheResource(resource: Resource) {
+
+ let task = RetrieveImageTask()
+ let downloadTask = manager.downloadAndCacheImageWithURL(
+ resource.downloadURL,
+ forKey: resource.cacheKey,
+ retrieveImageTask: task,
+ progressBlock: nil,
+ completionHandler: {
+ (image, error, _, _) -> () in
+
+ self.tasks.removeValueForKey(resource.downloadURL)
+
+ if let _ = error {
+ self.failedResources.append(resource)
+ } else {
+ self.completedResources.append(resource)
+ }
+
+ self.reportProgress()
+
+ if self.stopped {
+ if self.tasks.isEmpty {
+ let pendingResources = self.prefetchResources[self.requestedCount..CFBundlePackageType
FMWK
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
NSPrincipalClass
diff --git a/Carthage/Checkouts/Kingfisher/Sources/KingfisherManager.swift b/Carthage/Checkouts/Kingfisher/Sources/KingfisherManager.swift
index 944e1a3..4850d7b 100644
--- a/Carthage/Checkouts/Kingfisher/Sources/KingfisherManager.swift
+++ b/Carthage/Checkouts/Kingfisher/Sources/KingfisherManager.swift
@@ -89,11 +89,15 @@ public class KingfisherManager {
/**
Default init method
- - returns: A Kingfisher manager object with default cache and default downloader.
+ - returns: A Kingfisher manager object with default cache, default downloader, and default prefetcher.
*/
- public init() {
- cache = ImageCache.defaultCache
- downloader = ImageDownloader.defaultDownloader
+ public convenience init() {
+ self.init(downloader: ImageDownloader.defaultDownloader, cache: ImageCache.defaultCache)
+ }
+
+ init(downloader: ImageDownloader, cache: ImageCache) {
+ self.downloader = downloader
+ self.cache = cache
}
/**
@@ -164,10 +168,10 @@ public class KingfisherManager {
retrieveImageTask: RetrieveImageTask,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?,
- options: KingfisherOptionsInfo?)
+ options: KingfisherOptionsInfo?) -> RetrieveImageDownloadTask?
{
let downloader = options?.downloader ?? self.downloader
- downloader.downloadImageWithURL(URL, retrieveImageTask: retrieveImageTask, options: options,
+ return downloader.downloadImageWithURL(URL, retrieveImageTask: retrieveImageTask, options: options,
progressBlock: { receivedSize, totalSize in
progressBlock?(receivedSize: receivedSize, totalSize: totalSize)
},
@@ -187,8 +191,9 @@ public class KingfisherManager {
if let image = image, originalData = originalData {
targetCache.storeImage(image, originalData: originalData, forKey: key, toDisk: !(options?.cacheMemoryOnly ?? false), completionHandler: nil)
}
-
+
completionHandler?(image: image, error: error, cacheType: .None, imageURL: URL)
+
})
}
diff --git a/Carthage/Checkouts/Kingfisher/Sources/KingfisherOptionsInfo.swift b/Carthage/Checkouts/Kingfisher/Sources/KingfisherOptionsInfo.swift
index d917905..8e12822 100644
--- a/Carthage/Checkouts/Kingfisher/Sources/KingfisherOptionsInfo.swift
+++ b/Carthage/Checkouts/Kingfisher/Sources/KingfisherOptionsInfo.swift
@@ -67,8 +67,6 @@ infix operator <== {
precedence 160
}
-
-
// This operator returns true if two `KingfisherOptionsInfoItem` enum is the same, without considering the associated values.
func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Bool {
switch (lhs, rhs) {
@@ -89,6 +87,10 @@ extension CollectionType where Generator.Element == KingfisherOptionsInfoItem {
func kf_firstMatchIgnoringAssociatedValue(target: Generator.Element) -> Generator.Element? {
return indexOf { $0 <== target }.flatMap { self[$0] }
}
+
+ func kf_removeAllMatchesIgnoringAssociatedValue(target: Generator.Element) -> [Generator.Element] {
+ return self.filter { !($0 <== target) }
+ }
}
extension CollectionType where Generator.Element == KingfisherOptionsInfoItem {
diff --git a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-OSX/Info.plist b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-OSX/Info.plist
index 06326d0..73bdd34 100644
--- a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-OSX/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-OSX/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
diff --git a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-tvOS/Info.plist b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-tvOS/Info.plist
index 06326d0..73bdd34 100644
--- a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-tvOS/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests-tvOS/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
diff --git a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/ImageCacheTests.swift b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/ImageCacheTests.swift
index f4afde1..0d7f790 100644
--- a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/ImageCacheTests.swift
+++ b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/ImageCacheTests.swift
@@ -153,6 +153,66 @@ class ImageCacheTests: XCTestCase {
waitForExpectationsWithTimeout(5, handler: nil)
}
+ func testCachedFileExists() {
+ let expectation = expectationWithDescription("cache does contain image")
+
+ let URLString = testKeys[0]
+ let URL = NSURL(string: URLString)!
+
+ let exists = cache.cachedImageExistsforURL(URL)
+ XCTAssertFalse(exists)
+
+ cache.retrieveImageForKey(URLString, options: nil, completionHandler: { (image, type) -> () in
+ XCTAssertNil(image, "Should not be cached yet")
+ XCTAssertEqual(type, nil)
+
+ self.cache.storeImage(testImage, forKey: URLString, toDisk: true) { () -> () in
+ self.cache.retrieveImageForKey(URLString, options: nil, completionHandler: { (image, type) -> () in
+ XCTAssertNotNil(image, "Should be cached (memory or disk)")
+ XCTAssertEqual(type, CacheType.Memory)
+
+ let exists = self.cache.cachedImageExistsforURL(URL)
+ XCTAssertTrue(exists, "Image should exist in the cache (memory or disk)")
+
+ self.cache.clearMemoryCache()
+ self.cache.retrieveImageForKey(URLString, options: nil, completionHandler: { (image, type) -> () in
+ XCTAssertNotNil(image, "Should be cached (disk)")
+ XCTAssertEqual(type, CacheType.Disk)
+
+ let exists = self.cache.cachedImageExistsforURL(URL)
+ XCTAssertTrue(exists, "Image should exist in the cache (disk)")
+
+ expectation.fulfill()
+ })
+ })
+ }
+ })
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+ }
+
+ func testCachedFileDoesNotExist() {
+ let URLString = testKeys[0]
+ let URL = NSURL(string: URLString)!
+
+ let exists = cache.cachedImageExistsforURL(URL)
+ XCTAssertFalse(exists)
+ }
+
+ func testCachedImageIsFetchedSyncronouslyFromTheMemoryCache() {
+ cache.storeImage(testImage, forKey: testKeys[0], toDisk: false) { () -> () in
+ // do nothing
+ }
+
+ var foundImage: Image?
+
+ cache.retrieveImageForKey(testKeys[0], options: [.BackgroundDecode]) { (image, type) -> () in
+ foundImage = image
+ }
+
+ XCTAssertEqual(testImage, foundImage, "should have found the image immediately")
+ }
+
func testIsImageCachedForKey() {
let expectation = self.expectationWithDescription("wait for caching image")
diff --git a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/ImagePrefetcherTests.swift b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/ImagePrefetcherTests.swift
new file mode 100644
index 0000000..3dc1cf9
--- /dev/null
+++ b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/ImagePrefetcherTests.swift
@@ -0,0 +1,192 @@
+//
+// ImagePrefetcherTests.swift
+// Kingfisher
+//
+// Created by Claire Knight on 24/02/2016
+//
+// Copyright (c) 2016 Wei Wang
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import XCTest
+import Kingfisher
+
+#if os(OSX)
+ import AppKit
+#else
+ import UIKit
+#endif
+
+class ImagePrefetcherTests: XCTestCase {
+
+ override class func setUp() {
+ super.setUp()
+ LSNocilla.sharedInstance().start()
+ }
+
+ override class func tearDown() {
+ super.tearDown()
+ LSNocilla.sharedInstance().stop()
+ }
+
+ override func setUp() {
+ super.setUp()
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ cleanDefaultCache()
+ }
+
+ override func tearDown() {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ cleanDefaultCache()
+ super.tearDown()
+ }
+
+ func testPrefetchingImages() {
+ let expectation = expectationWithDescription("wait for prefetching images")
+
+ var urls = [NSURL]()
+ for URLString in testKeys {
+ stubRequest("GET", URLString).andReturn(200).withBody(testImageData)
+ urls.append(NSURL(string: URLString)!)
+ }
+
+ var progressCalledCount = 0
+ let prefetcher = ImagePrefetcher(urls: urls, optionsInfo: nil, progressBlock: { (skippedResources, failedResources, completedResources) -> () in
+ progressCalledCount += 1
+ }) { (skippedResources, failedResources, completedResources) -> () in
+ expectation.fulfill()
+ XCTAssertEqual(skippedResources.count, 0, "There should be no items skipped.")
+ XCTAssertEqual(failedResources.count, 0, "There should be no failed downloading.")
+ XCTAssertEqual(completedResources.count, urls.count, "All resources prefetching should be completed.")
+ XCTAssertEqual(progressCalledCount, urls.count, "Progress should be called the same time of download count.")
+ for url in urls {
+ XCTAssertTrue(KingfisherManager.sharedManager.cache.isImageCachedForKey(url.absoluteString).cached)
+ }
+ }
+
+ prefetcher.start()
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+ }
+
+ func testCancelPrefetching() {
+ let expectation = expectationWithDescription("wait for prefetching images")
+
+ var urls = [NSURL]()
+ var responses = [LSStubResponseDSL!]()
+ for URLString in testKeys {
+ let response = stubRequest("GET", URLString).andReturn(200).withBody(testImageData).delay()
+ responses.append(response)
+ urls.append(NSURL(string: URLString)!)
+ }
+
+ let maxConcurrentCount = 2
+ let prefetcher = ImagePrefetcher(urls: urls, optionsInfo: nil, progressBlock: { (skippedResources, failedResources, completedResources) -> () in
+
+ }) { (skippedResources, failedResources, completedResources) -> () in
+ expectation.fulfill()
+ XCTAssertEqual(skippedResources.count, 0, "There should be no items skipped.")
+ XCTAssertEqual(failedResources.count, urls.count, "The failed count should be the same with started downloads due to cancellation.")
+ XCTAssertEqual(completedResources.count, 0, "None resources prefetching should complete.")
+ }
+
+ prefetcher.maxConcurrentDownloads = maxConcurrentCount
+
+ prefetcher.start()
+ prefetcher.stop()
+
+ let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
+ dispatch_after(delayTime, dispatch_get_main_queue()) {
+ for response in responses {
+ response.go()
+ }
+ }
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+ }
+
+
+ func testPrefetcherCouldSkipCachedImages() {
+ let expectation = expectationWithDescription("wait for prefetching images")
+ KingfisherManager.sharedManager.cache.storeImage(Image(), forKey: testKeys[0])
+
+ var urls = [NSURL]()
+ for URLString in testKeys {
+ stubRequest("GET", URLString).andReturn(200).withBody(testImageData)
+ urls.append(NSURL(string: URLString)!)
+ }
+
+ let prefetcher = ImagePrefetcher(urls: urls, optionsInfo: nil, progressBlock: { (skippedResources, failedResources, completedResources) -> () in
+
+ }) { (skippedResources, failedResources, completedResources) -> () in
+ expectation.fulfill()
+ XCTAssertEqual(skippedResources.count, 1, "There should be 1 item skipped.")
+ XCTAssertEqual(skippedResources[0].downloadURL.absoluteString, testKeys[0], "The correct image key should be skipped.")
+
+ XCTAssertEqual(failedResources.count, 0, "There should be no failed downloading.")
+ XCTAssertEqual(completedResources.count, urls.count - 1, "All resources prefetching should be completed.")
+ }
+
+ prefetcher.start()
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+ }
+
+ func testPrefetcherForceRefreshDownloadImages() {
+ let expectation = expectationWithDescription("wait for prefetching images")
+
+ // Store an image in cache.
+ KingfisherManager.sharedManager.cache.storeImage(Image(), forKey: testKeys[0])
+
+ var urls = [NSURL]()
+ for URLString in testKeys {
+ stubRequest("GET", URLString).andReturn(200).withBody(testImageData)
+ urls.append(NSURL(string: URLString)!)
+ }
+
+ // Use `.ForceRefresh` to download it forcely.
+ let prefetcher = ImagePrefetcher(urls: urls, optionsInfo: [.ForceRefresh], progressBlock: { (skippedResources, failedResources, completedResources) -> () in
+
+ }) { (skippedResources, failedResources, completedResources) -> () in
+ expectation.fulfill()
+
+ XCTAssertEqual(skippedResources.count, 0, "There should be no item skipped.")
+ XCTAssertEqual(failedResources.count, 0, "There should be no failed downloading.")
+ XCTAssertEqual(completedResources.count, urls.count, "All resources prefetching should be completed.")
+ }
+
+ prefetcher.start()
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+ }
+
+ func testPrefetchWithWrongInitParameters() {
+ let expectation = expectationWithDescription("wait for prefetching images")
+ let prefetcher = ImagePrefetcher(urls: [], optionsInfo: nil, progressBlock: nil) { (skippedResources, failedResources, completedResources) -> () in
+ expectation.fulfill()
+
+ XCTAssertEqual(skippedResources.count, 0, "There should be no item skipped.")
+ XCTAssertEqual(failedResources.count, 0, "There should be no failed downloading.")
+ XCTAssertEqual(completedResources.count, 0, "There should be no completed downloading.")
+ }
+
+ prefetcher.start()
+ waitForExpectationsWithTimeout(5, handler: nil)
+ }
+}
diff --git a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/Info.plist b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/Info.plist
index 06326d0..73bdd34 100644
--- a/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/Info.plist
+++ b/Carthage/Checkouts/Kingfisher/Tests/KingfisherTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 2.0.4
+ 2.1.0
CFBundleSignature
????
CFBundleVersion
- 543
+ 567
diff --git a/PhotoBrowser.xcodeproj/project.pbxproj b/PhotoBrowser.xcodeproj/project.pbxproj
index e97f5a2..ddd48af 100644
--- a/PhotoBrowser.xcodeproj/project.pbxproj
+++ b/PhotoBrowser.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 4A0739991C98FB3C0004FEA5 /* PBCustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0739981C98FB3C0004FEA5 /* PBCustomView.swift */; };
4A0B3FDF1C76DB300049338C /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0B3FDE1C76DB300049338C /* Kingfisher.framework */; };
4A52D1E41C72CB2E001C257B /* PhotoBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A52D1E31C72CB2E001C257B /* PhotoBrowser.h */; settings = {ATTRIBUTES = (Public, ); }; };
4A52D1FE1C72CE69001C257B /* PhotoBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A52D1FD1C72CE69001C257B /* PhotoBrowser.swift */; };
@@ -14,9 +15,11 @@
4A52D2361C72F524001C257B /* WaitingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A52D2351C72F524001C257B /* WaitingView.swift */; };
4A52D2381C72F568001C257B /* PhotoPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A52D2371C72F568001C257B /* PhotoPreviewController.swift */; };
4A6BC7C11C770F6400DACDA5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A6BC7C01C770F6400DACDA5 /* Images.xcassets */; };
+ 4A87F61F1C96CF63005A9667 /* PBAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A87F61E1C96CF63005A9667 /* PBAnimation.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 4A0739981C98FB3C0004FEA5 /* PBCustomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PBCustomView.swift; sourceTree = ""; };
4A0B3FDE1C76DB300049338C /* Kingfisher.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Kingfisher.framework; path = Carthage/Build/iOS/Kingfisher.framework; sourceTree = ""; };
4A52D1E01C72CB2E001C257B /* PhotoBrowser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PhotoBrowser.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4A52D1E31C72CB2E001C257B /* PhotoBrowser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhotoBrowser.h; sourceTree = ""; };
@@ -26,6 +29,7 @@
4A52D2351C72F524001C257B /* WaitingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaitingView.swift; sourceTree = ""; };
4A52D2371C72F568001C257B /* PhotoPreviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoPreviewController.swift; sourceTree = ""; };
4A6BC7C01C770F6400DACDA5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
+ 4A87F61E1C96CF63005A9667 /* PBAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PBAnimation.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -67,6 +71,8 @@
4A52D1FD1C72CE69001C257B /* PhotoBrowser.swift */,
4A52D2371C72F568001C257B /* PhotoPreviewController.swift */,
4A52D2351C72F524001C257B /* WaitingView.swift */,
+ 4A87F61E1C96CF63005A9667 /* PBAnimation.swift */,
+ 4A0739981C98FB3C0004FEA5 /* PBCustomView.swift */,
);
path = PhotoBrowser;
sourceTree = "";
@@ -152,7 +158,9 @@
buildActionMask = 2147483647;
files = (
4A52D2301C72F47C001C257B /* Photo.swift in Sources */,
+ 4A0739991C98FB3C0004FEA5 /* PBCustomView.swift in Sources */,
4A52D1FE1C72CE69001C257B /* PhotoBrowser.swift in Sources */,
+ 4A87F61F1C96CF63005A9667 /* PBAnimation.swift in Sources */,
4A52D2361C72F524001C257B /* WaitingView.swift in Sources */,
4A52D2381C72F568001C257B /* PhotoPreviewController.swift in Sources */,
);
diff --git a/PhotoBrowser.xcworkspace/xcuserdata/wangwei.xcuserdatad/UserInterfaceState.xcuserstate b/PhotoBrowser.xcworkspace/xcuserdata/wangwei.xcuserdatad/UserInterfaceState.xcuserstate
index 02337a5..44a6737 100644
Binary files a/PhotoBrowser.xcworkspace/xcuserdata/wangwei.xcuserdatad/UserInterfaceState.xcuserstate and b/PhotoBrowser.xcworkspace/xcuserdata/wangwei.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/PhotoBrowser/Images.xcassets/icon-cross.imageset/Contents.json b/PhotoBrowser/Images.xcassets/icon-cross.imageset/Contents.json
new file mode 100644
index 0000000..fd8e244
--- /dev/null
+++ b/PhotoBrowser/Images.xcassets/icon-cross.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "icon_cross.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/PhotoBrowser/Images.xcassets/icon-cross.imageset/icon_cross.pdf b/PhotoBrowser/Images.xcassets/icon-cross.imageset/icon_cross.pdf
new file mode 100644
index 0000000..222d032
Binary files /dev/null and b/PhotoBrowser/Images.xcassets/icon-cross.imageset/icon_cross.pdf differ
diff --git a/PhotoBrowser/Images.xcassets/icon-share.imageset/Contents.json b/PhotoBrowser/Images.xcassets/icon-share.imageset/Contents.json
new file mode 100644
index 0000000..be1a909
--- /dev/null
+++ b/PhotoBrowser/Images.xcassets/icon-share.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "icon_share.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/PhotoBrowser/Images.xcassets/icon-share.imageset/icon_share.pdf b/PhotoBrowser/Images.xcassets/icon-share.imageset/icon_share.pdf
new file mode 100644
index 0000000..468df4e
Binary files /dev/null and b/PhotoBrowser/Images.xcassets/icon-share.imageset/icon_share.pdf differ
diff --git a/PhotoBrowser/PBAnimation.swift b/PhotoBrowser/PBAnimation.swift
new file mode 100644
index 0000000..f66f84a
--- /dev/null
+++ b/PhotoBrowser/PBAnimation.swift
@@ -0,0 +1,207 @@
+//
+// PresentAnimation.swift
+// PhotoBrowser
+//
+// Created by WangWei on 16/3/14.
+// Copyright © 2016年 Teambition. All rights reserved.
+//
+
+import UIKit
+
+let TransitionDuration = 0.3
+
+extension UIViewController {
+ public func showPhotoBrowser(photoBrowser: PhotoBrowser, fromView: UIImageView) {
+ photoBrowser.transitionDelegate = TransitionDelegate(photoBrowser: photoBrowser, fromView: fromView)
+ presentViewController(photoBrowser, animated: true, completion: nil)
+ }
+
+ public func dismissPhotoBrowser(photoBrowser: PhotoBrowser, toView: UIView? = nil) {
+ photoBrowser.transitionDelegate?.toView = toView
+ dismissViewControllerAnimated(true, completion: nil)
+ }
+}
+
+public class TransitionDelegate: NSObject, UIViewControllerTransitioningDelegate {
+ var startIndex: Int!
+ public var fromView: UIImageView!
+ public var toView: UIView?
+ public weak var photoBrowser: PhotoBrowser!
+
+ init(photoBrowser: PhotoBrowser, fromView: UIImageView) {
+ super.init()
+ self.fromView = fromView
+ self.photoBrowser = photoBrowser
+ startIndex = photoBrowser.currentIndex
+ }
+
+ public func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
+ return PresentAnimation(photoBrowser: photoBrowser, fromView: fromView)
+ }
+
+ public func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
+ if let destView = toView {
+ return DismissAnimation(photoBrowser: photoBrowser, toView: destView)
+ } else {
+ return DismissImmediatelyAnimation()
+ }
+ }
+}
+
+public class PresentAnimation: NSObject, UIViewControllerAnimatedTransitioning {
+
+ public var fromView: UIImageView!
+ public weak var photoBrowser: PhotoBrowser!
+
+ public init(photoBrowser: PhotoBrowser, fromView: UIImageView) {
+ super.init()
+ self.fromView = fromView
+ self.photoBrowser = photoBrowser
+
+ }
+
+ public func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
+ return TransitionDuration
+ }
+
+ public func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
+ let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
+ let container = transitionContext.containerView()!
+
+ let snapshotView = UIImageView(image: fromView.image)
+ snapshotView.contentMode = fromView.contentMode
+ snapshotView.clipsToBounds = fromView.clipsToBounds
+ snapshotView.frame = container.convertRect(fromView.frame, fromView: fromView.superview)
+
+ toVC.view.alpha = 0
+ photoBrowser.currentImageView()?.alpha = 0
+ fromView.hidden = true
+
+
+ container.addSubview(toVC.view)
+ container.addSubview(snapshotView)
+
+ UIView.animateWithDuration(TransitionDuration, animations: { () -> Void in
+ let finalFrame = self.finalFrameForImage(self.fromView.image, inTransitionContext: transitionContext)
+ snapshotView.frame = finalFrame
+ toVC.view.alpha = 1
+ }) { (finished) -> Void in
+ self.fromView.hidden = false
+ self.photoBrowser.currentImageView()?.alpha = 1
+ snapshotView.removeFromSuperview()
+ transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
+ }
+ }
+
+ func finalFrameForImage(image: UIImage?, inTransitionContext transitionContext: UIViewControllerContextTransitioning) -> CGRect {
+ let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
+ guard let destVC = toVC, let image = image else {
+ return CGRectZero
+ }
+
+ let viewSize = transitionContext.finalFrameForViewController(destVC).size
+ let imageSize = image.size
+
+ let xScale = imageSize.width / viewSize.width
+ let yScale = imageSize.height / viewSize.height
+
+ let finalScale = max(xScale, yScale)
+ let finalSize = CGSizeMake(imageSize.width / finalScale, imageSize.height / finalScale)
+
+ let center = destVC.view.center
+
+ return CGRectMake(center.x - finalSize.width/2, center.y-finalSize.height/2, finalSize.width, finalSize.height)
+ }
+}
+
+public class DismissAnimation: NSObject, UIViewControllerAnimatedTransitioning {
+
+ public weak var photoBrowser: PhotoBrowser!
+ public var toView: UIView!
+
+ init(photoBrowser: PhotoBrowser, toView: UIView) {
+ super.init()
+ self.photoBrowser = photoBrowser
+ self.toView = toView
+ }
+
+ public func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
+ return TransitionDuration
+ }
+
+ public func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
+ guard let currentPhoto = photoBrowser.currentPhoto, let currentImageView = photoBrowser.currentImageView() else {
+ transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
+ return
+ }
+ let image = currentPhoto.localOriginalPhoto() ?? currentPhoto.localThumbnailPhoto()
+ let snapshotView = UIImageView(image: image)
+ snapshotView.frame = currentImageView.frame
+ snapshotView.clipsToBounds = currentImageView.clipsToBounds
+ snapshotView.contentMode = currentImageView.contentMode
+
+ let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
+ let container = transitionContext.containerView()!
+ container.addSubview(toVC.view)
+ container.addSubview(snapshotView)
+ toView.alpha = 0
+ currentImageView.hidden = true
+
+ let finalFrame = container.convertRect(toView.frame, fromView: toView.superview)
+
+
+ UIView.animateWithDuration(TransitionDuration, animations: { () -> Void in
+ snapshotView.frame = finalFrame
+ }) { (_) -> Void in
+ self.toView.alpha = 1
+ currentImageView.hidden = false
+ transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
+ }
+ }
+
+}
+
+public class DismissImmediatelyAnimation: NSObject, UIViewControllerAnimatedTransitioning {
+
+ public func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
+ return TransitionDuration/2
+ }
+
+ public func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
+ let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
+ let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
+
+ transitionContext.containerView()?.addSubview(toVC.view)
+ transitionContext.containerView()?.addSubview(fromVC.view)
+
+ UIView.animateWithDuration(TransitionDuration/2, animations: { () -> Void in
+ fromVC.view.alpha = 0
+ }) { (_) -> Void in
+ transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PhotoBrowser/PBCustomView.swift b/PhotoBrowser/PBCustomView.swift
new file mode 100644
index 0000000..d80784a
--- /dev/null
+++ b/PhotoBrowser/PBCustomView.swift
@@ -0,0 +1,169 @@
+//
+// PBCustomView.swift
+// PhotoBrowser
+//
+// Created by WangWei on 16/3/16.
+// Copyright © 2016年 Teambition. All rights reserved.
+//
+
+import UIKit
+
+let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.size.height
+
+class PBNavigationBar: UIView {
+
+ lazy var contentView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+
+ view.addSubview(self.leftButton)
+ view.addSubview(self.rightButton)
+ view.addSubview(self.titleLabel)
+ view.addSubview(self.indexLabel)
+
+ view.addConstraint(NSLayoutConstraint(item: view, attribute: .CenterY, relatedBy: .Equal, toItem: self.rightButton, attribute: .CenterY, multiplier: 1.0, constant: 0))
+ view.addConstraint(NSLayoutConstraint(item: view, attribute: .CenterY, relatedBy: .Equal, toItem: self.leftButton, attribute: .CenterY, multiplier: 1.0, constant: 0))
+
+ view.addConstraint(NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: self.leftButton, attribute: .Leading, multiplier: 1.0, constant: -8))
+ view.addConstraint(NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: self.rightButton, attribute: .Trailing, multiplier: 1.0, constant: 8))
+
+ view.addConstraint(NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: self.titleLabel, attribute: .Top, multiplier: 1.0, constant: 0))
+ view.addConstraint(NSLayoutConstraint(item: view, attribute: .CenterX, relatedBy: .Equal, toItem: self.titleLabel, attribute: .CenterX, multiplier: 1.0, constant: 0))
+
+ view.addConstraint(NSLayoutConstraint(item: self.titleLabel, attribute: .Bottom, relatedBy: .Equal, toItem: self.indexLabel, attribute: .Top, multiplier: 1.0, constant: -3))
+ view.addConstraint(NSLayoutConstraint(item: self.titleLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self.indexLabel, attribute: .CenterX, multiplier: 1.0, constant: 0))
+ return view
+ }()
+
+ lazy var titleLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Title"
+ label.textColor = UIColor.whiteColor()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+ var indexLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Index"
+ label.textColor = UIColor.whiteColor()
+ label.font = UIFont.systemFontOfSize(14)
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+ var leftButton: UIButton = {
+ let button = UIButton()
+ let image = UIImage(named: "icon-cross", inBundle: NSBundle(forClass: classForCoder()), compatibleWithTraitCollection: nil)
+ button.setImage(image, forState: .Normal)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.addConstraint(NSLayoutConstraint(item: button, attribute: .Width, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 30))
+ button.addConstraint(NSLayoutConstraint(item: button, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 30))
+ return button
+ }()
+
+ var rightButton: UIButton = {
+ let button = UIButton()
+ let image = UIImage(named: "icon-share", inBundle: NSBundle(forClass: classForCoder()), compatibleWithTraitCollection: nil)
+ button.setImage(image, forState: .Normal)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.addConstraint(NSLayoutConstraint(item: button, attribute: .Width, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 30))
+ button.addConstraint(NSLayoutConstraint(item: button, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 30))
+ return button
+ }()
+
+ var gradientView = GradientView()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setup()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ }
+
+ func setup() {
+ addSubview(gradientView)
+ gradientView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
+ gradientView.colors = [UIColor.blackColor().colorWithAlphaComponent(0.48).CGColor, UIColor.clearColor().CGColor]
+ gradientView.startPoint = CGPoint(x: 0, y: 0)
+ gradientView.endPoint = CGPoint(x: 0, y: 1)
+ addSubview(contentView)
+ addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[contentView]-0-|", options: [], metrics: nil, views: ["contentView": contentView]))
+ addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-statusBarHeight-[contentView]-0-|", options: [], metrics: ["statusBarHeight":statusBarHeight], views: ["contentView": contentView]))
+ }
+}
+
+public class PBToolbar: UIToolbar {
+
+ var gradientView = GradientView()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setBackgroundImage(UIImage(), forToolbarPosition: .Any, barMetrics: .Default)
+ clipsToBounds = true
+ addSubview(gradientView)
+ gradientView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
+ gradientView.colors = [UIColor.blackColor().colorWithAlphaComponent(0.48).CGColor, UIColor.clearColor().CGColor]
+ gradientView.startPoint = CGPoint(x: 0, y: 1)
+ gradientView.endPoint = CGPoint(x: 0, y: 0)
+ }
+
+ required public init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ }
+}
+
+class GradientView: UIView {
+
+ var colors: [AnyObject]? {
+ get {
+ return (layer as! CAGradientLayer).colors
+ }
+ set {
+ (layer as! CAGradientLayer).colors = newValue
+ }
+ }
+
+ var startPoint: CGPoint {
+ get {
+ return (layer as! CAGradientLayer).startPoint
+ }
+ set {
+ (layer as! CAGradientLayer).startPoint = newValue
+ }
+ }
+
+ var endPoint: CGPoint {
+ get {
+ return (layer as! CAGradientLayer).endPoint
+ }
+ set {
+ (layer as! CAGradientLayer).endPoint = newValue
+ }
+ }
+
+ override class func layerClass() -> AnyClass {
+ return CAGradientLayer.self
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PhotoBrowser/Photo.swift b/PhotoBrowser/Photo.swift
index df75c2a..adf7dd0 100644
--- a/PhotoBrowser/Photo.swift
+++ b/PhotoBrowser/Photo.swift
@@ -16,10 +16,12 @@ public struct Photo {
public var thumbnailImage: UIImage?
public var photoUrl: NSURL?
public var thumbnailUrl: NSURL?
+ public var title: String?
public var object: AnyObject?
- public init(image: UIImage?, thumbnailImage: UIImage?, photoUrl: NSURL? = nil, thumbnailUrl: NSURL? = nil, object: AnyObject? = nil) {
+ public init(image: UIImage?, title: String? = nil, thumbnailImage: UIImage? = nil, photoUrl: NSURL? = nil, thumbnailUrl: NSURL? = nil, object: AnyObject? = nil) {
self.image = image
+ self.title = title
self.thumbnailImage = thumbnailImage
self.photoUrl = photoUrl
self.thumbnailUrl = thumbnailUrl
diff --git a/PhotoBrowser/PhotoBrowser.swift b/PhotoBrowser/PhotoBrowser.swift
index 2f187c5..5fef639 100644
--- a/PhotoBrowser/PhotoBrowser.swift
+++ b/PhotoBrowser/PhotoBrowser.swift
@@ -11,22 +11,35 @@ import UIKit
import Kingfisher
let ToolbarHeight: CGFloat = 44
+let PadToolbarItemSpace: CGFloat = 72
-public protocol PhotoBrowserDelegate: class {
- func longPressOn(photo: Photo, gesture: UILongPressGestureRecognizer)
+@objc public protocol PhotoBrowserDelegate: NSObjectProtocol {
+ optional func dismissPhotoBrowser(photoBrowser: PhotoBrowser)
+ optional func longPressOnImage(gesture: UILongPressGestureRecognizer)
}
public class PhotoBrowser: UIPageViewController {
var isFullScreen = false
- public var currentIndex: Int = 0
+ var toolbarHeightConstraint: NSLayoutConstraint?
+ var toolbarBottomConstraint: NSLayoutConstraint?
+ var navigationTopConstraint: NSLayoutConstraint?
+ var navigationHeightConstraint: NSLayoutConstraint?
+
+ var headerView: PBNavigationBar?
+
+ var transitionDelegate: TransitionDelegate? {
+ didSet {
+ transitioningDelegate = transitionDelegate
+ }
+ }
public var photos: [Photo]?
- public var toolbar: UIToolbar?
+ public var toolbar: PBToolbar?
+ public var backgroundColor = UIColor.blackColor()
public weak var photoBrowserDelegate: PhotoBrowserDelegate?
- public var toolbarHeightConstraint: NSLayoutConstraint?
- public var toolbarBottomConstraint: NSLayoutConstraint?
+ public var currentIndex: Int = 0
public var currentPhoto: Photo? {
return photos?[currentIndex]
}
@@ -45,7 +58,7 @@ public class PhotoBrowser: UIPageViewController {
public override func viewDidLoad() {
super.viewDidLoad()
- view.backgroundColor = UIColor.whiteColor()
+ view.backgroundColor = backgroundColor
extendedLayoutIncludesOpaqueBars = true
automaticallyAdjustsScrollViewInsets = false
edgesForExtendedLayout = UIRectEdge.Top
@@ -57,6 +70,11 @@ public class PhotoBrowser: UIPageViewController {
initPage.delegate = self
setViewControllers([initPage], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
+
+ }
+
+ public override func viewDidAppear(animated: Bool) {
+ super.viewDidAppear(animated)
updateNavigationBarTitle()
updateToolbar(view.bounds.size)
}
@@ -65,6 +83,10 @@ public class PhotoBrowser: UIPageViewController {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
updateToolbar(size)
}
+
+ public override func preferredStatusBarStyle() -> UIStatusBarStyle {
+ return .LightContent
+ }
}
extension PhotoBrowser {
@@ -73,11 +95,35 @@ extension PhotoBrowser {
return isFullScreen
}
+ public override func preferredStatusBarUpdateAnimation() -> UIStatusBarAnimation {
+ return .Fade
+ }
+
func updateNavigationBarTitle() {
guard let photos = photos else {
return
}
- title = "\(currentIndex + 1) / \(photos.count)"
+
+ if headerView == nil {
+ headerView = PBNavigationBar()
+ if let headerView = headerView {
+ view.addSubview(headerView)
+ headerView.translatesAutoresizingMaskIntoConstraints = false
+ view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[headerView]-0-|", options: [], metrics: nil, views: ["headerView":headerView]))
+ navigationHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 64)
+ navigationTopConstraint = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: headerView, attribute: .Top, multiplier: 1.0, constant: 0)
+ if let topConstraint = navigationTopConstraint, let heightConstraint = navigationHeightConstraint {
+ view.addConstraints([topConstraint, heightConstraint])
+ }
+
+ headerView.leftButton.addTarget(self, action: "leftButtonTap:", forControlEvents: .TouchUpInside)
+ headerView.rightButton.addTarget(self, action: "rightButtonTap:", forControlEvents: .TouchUpInside)
+ }
+ }
+ if let headerView = headerView {
+ headerView.titleLabel.text = photos[currentIndex].title
+ headerView.indexLabel.text = "\(currentIndex + 1)/\(photos.count)"
+ }
}
func updateToolbar(size: CGSize) {
@@ -85,7 +131,7 @@ extension PhotoBrowser {
return
}
if toolbar == nil {
- toolbar = UIToolbar()
+ toolbar = PBToolbar()
if let toolbar = toolbar {
view.addSubview(toolbar)
toolbar.translatesAutoresizingMaskIntoConstraints = false
@@ -101,29 +147,67 @@ extension PhotoBrowser {
if let toolbar = toolbar {
let itemsArray = layoutToolbar(items)
toolbar.setItems(itemsArray, animated: false)
- toolbar.tintColor = UIColor.whiteColor()
}
}
func layoutToolbar(items: [UIBarButtonItem]) -> [UIBarButtonItem]? {
let flexSpace = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
+ let fixedSpace = UIBarButtonItem(barButtonSystemItem: .FixedSpace, target: self, action: nil)
+ fixedSpace.width = PadToolbarItemSpace
var itemsArray = [UIBarButtonItem]()
- if items.count == 1, let first = items.first {
- itemsArray = [flexSpace, first, flexSpace]
- } else if items.count == 2, let first = items.first, let last = items.last {
- itemsArray = [flexSpace, first, flexSpace, flexSpace, last, flexSpace]
- } else {
+
+ if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
+ itemsArray.append(flexSpace)
for item in items {
itemsArray.append(item)
- itemsArray.append(flexSpace)
+ itemsArray.append(fixedSpace)
}
- if itemsArray.count > 0 {
- itemsArray.removeLast()
+ itemsArray.removeLast()
+ itemsArray.append(flexSpace)
+ } else {
+ if items.count == 1, let first = items.first {
+ itemsArray = [flexSpace, first, flexSpace]
+ } else if items.count == 2, let first = items.first, let last = items.last {
+ itemsArray = [flexSpace, first, flexSpace, flexSpace, last, flexSpace]
+ } else {
+ for item in items {
+ itemsArray.append(item)
+ itemsArray.append(flexSpace)
+ }
+ if itemsArray.count > 0 {
+ itemsArray.removeLast()
+ }
}
}
+
return itemsArray
}
+ func leftButtonTap(sender: AnyObject) {
+ if let delegate = photoBrowserDelegate where delegate.respondsToSelector("dismissPhotoBrowser:") {
+ delegate.dismissPhotoBrowser!(self)
+ } else {
+ dismissViewControllerAnimated(true, completion: nil)
+ }
+ }
+
+ func rightButtonTap(sender: AnyObject) {
+
+ if let image = currentImageView()?.image, let button = sender as? UIButton {
+ let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil)
+
+ switch UIDevice.currentDevice().userInterfaceIdiom {
+ case .Phone:
+ presentViewController(activityController, animated: true, completion: nil)
+ case .Pad:
+ let popover = UIPopoverController(contentViewController: activityController)
+ popover.presentPopoverFromRect(button.frame, inView: view, permittedArrowDirections: .Any, animated: true)
+ default:
+ presentViewController(activityController, animated: true, completion: nil)
+ }
+ }
+ }
+
}
extension PhotoBrowser: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
@@ -185,14 +269,11 @@ extension PhotoBrowser: PhotoPreviewControllerDelegate {
set(newValue) {
isFullScreen = newValue
- self.navigationController?.setNavigationBarHidden(newValue, animated: true)
-
- if let bottomConstraint = toolbarBottomConstraint, let heightConstraint = toolbarHeightConstraint {
- bottomConstraint.constant = newValue ? -heightConstraint.constant : 0
- }
- UIView.animateWithDuration(0.25) { () -> Void in
- self.view.backgroundColor = newValue ? UIColor.blackColor() : UIColor.whiteColor()
- self.view.layoutIfNeeded()
+ UIView.animateWithDuration(0.3) { () -> Void in
+ self.setNeedsStatusBarAppearanceUpdate()
+ self.view.backgroundColor = newValue ? UIColor.blackColor() : self.backgroundColor
+ self.headerView?.alpha = newValue ? 0 : 1
+ self.toolbar?.alpha = newValue ? 0 : 1
}
}
}
@@ -201,7 +282,17 @@ extension PhotoBrowser: PhotoPreviewControllerDelegate {
guard let browserDelegate = photoBrowserDelegate else {
return
}
- browserDelegate.longPressOn(photo, gesture: gesture)
+ if browserDelegate.respondsToSelector("longPressOnImage:") {
+ browserDelegate.longPressOnImage!(gesture)
+ }
+ }
+}
+
+extension PhotoBrowser {
+ func currentImageView() -> UIImageView? {
+ guard let page = viewControllers?.last as? PhotoPreviewController else {
+ return nil
+ }
+ return page.imageView
}
-
}
diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/Contents.json b/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/Contents.json
index beb1044..abbac69 100644
--- a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/Contents.json
+++ b/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/Contents.json
@@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
- "filename" : "thumbnail3.jpeg",
+ "filename" : "image.jpeg",
"scale" : "1x"
},
{
diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/image.jpeg b/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/image.jpeg
new file mode 100644
index 0000000..b5d54ab
Binary files /dev/null and b/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/image.jpeg differ
diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/thumbnail3.jpeg b/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/thumbnail3.jpeg
deleted file mode 100644
index 440f8a5..0000000
Binary files a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/thumbnail3.jpeg and /dev/null differ
diff --git a/PhotoBrowserDemo/Base.lproj/Main.storyboard b/PhotoBrowserDemo/Base.lproj/Main.storyboard
index 49f89e0..03e75f8 100644
--- a/PhotoBrowserDemo/Base.lproj/Main.storyboard
+++ b/PhotoBrowserDemo/Base.lproj/Main.storyboard
@@ -3,6 +3,7 @@
+
@@ -17,41 +18,31 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
@@ -77,4 +68,7 @@
+
+
+
diff --git a/PhotoBrowserDemo/ViewController.swift b/PhotoBrowserDemo/ViewController.swift
index ba6e4cf..fe53d3e 100644
--- a/PhotoBrowserDemo/ViewController.swift
+++ b/PhotoBrowserDemo/ViewController.swift
@@ -10,20 +10,19 @@ import UIKit
import PhotoBrowser
class ViewController: UIViewController {
-
- @IBOutlet weak var tableView: UITableView!
-
var photoBrowser: PhotoBrowser?
+ @IBOutlet weak var imageView: UIImageView!
+
override func viewDidLoad() {
super.viewDidLoad()
- tableView.dataSource = self
- tableView.delegate = self
+ let gesture = UITapGestureRecognizer(target: self, action: "showPhotoBrowser")
+ imageView.userInteractionEnabled = true
+ imageView.addGestureRecognizer(gesture)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
- // Dispose of any resources that can be recreated.
}
func saveToAlbum(image: UIImage) {
@@ -39,33 +38,6 @@ class ViewController: UIViewController {
}
}
-extension ViewController: UITableViewDataSource, UITableViewDelegate {
- func numberOfSectionsInTableView(tableView: UITableView) -> Int {
- return 1
- }
-
- func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return 1
- }
-
- func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
- cell.textLabel?.text = "\(indexPath.row)"
- return cell
- }
-
- func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
- tableView.deselectRowAtIndexPath(indexPath, animated: true)
- switch indexPath.row {
- case 0:
- showPhotoBrowser()
- default:
- print("default")
- }
- }
-
-}
-
extension ViewController {
func showPhotoBrowser() {
let thumbnail1 = UIImage.init(named: "thumbnail1")
@@ -78,26 +50,34 @@ extension ViewController {
let photoUrl3 = NSURL.init(string: "https://pic2.zhimg.com/a5455838750e168d97480d9247537d31_r.jpeg")
let item1 = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.Bookmarks, target: self, action: nil)
- item1.tintColor = UIColor.blackColor()
+ item1.tintColor = UIColor.whiteColor()
+ let item2 = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.Bookmarks, target: self, action: nil)
+ item2.tintColor = UIColor.whiteColor()
+ let item3 = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.Bookmarks, target: self, action: nil)
+ item3.tintColor = UIColor.whiteColor()
- let photo = Photo.init(image: nil, thumbnailImage: thumbnail1, photoUrl: photoUrl1)
- let photo2 = Photo.init(image: nil, thumbnailImage: thumbnail2, photoUrl: photoUrl2)
- let photo3 = Photo.init(image: nil, thumbnailImage: thumbnail3, photoUrl: photoUrl3)
+ let photo = Photo.init(image: nil, title:"Image1", thumbnailImage: thumbnail1, photoUrl: photoUrl1)
+ let photo2 = Photo.init(image: nil, title:"Image2", thumbnailImage: thumbnail2, photoUrl: photoUrl2)
+ let photo3 = Photo.init(image: nil, title:"Image3", thumbnailImage: thumbnail3, photoUrl: photoUrl3)
photoBrowser = PhotoBrowser()
guard let browser = photoBrowser else {
return
}
- browser.toolbarItems = [item1]
+ browser.toolbarItems = [item1, item2, item3]
browser.photoBrowserDelegate = self
- browser.currentIndex = 0
+ browser.currentIndex = 2
browser.photos = [photo, photo2, photo3]
- self.navigationController?.pushViewController(browser, animated: true)
+ navigationController!.showPhotoBrowser(browser, fromView: imageView)
}
}
extension ViewController: PhotoBrowserDelegate {
- func longPressOn(photo: Photo, gesture: UILongPressGestureRecognizer) {
+ func dismissPhotoBrowser(photoBrowser: PhotoBrowser) {
+ navigationController!.dismissPhotoBrowser(photoBrowser, toView: imageView)
+ }
+
+ func longPressOnImage(gesture: UILongPressGestureRecognizer) {
guard let imageView = gesture.view as? UIImageView else {
return
}
@@ -110,24 +90,15 @@ extension ViewController: PhotoBrowserDelegate {
}
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
- self.photoBrowser?.presentViewController(alertController, animated: true, completion: nil)
+ if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
+ self.photoBrowser?.presentViewController(alertController, animated: true, completion: nil)
+ } else {
+ let location = gesture.locationInView(gesture.view)
+ let rect = CGRectMake(location.x - 5, location.y - 5, 10, 10)
+ alertController.modalPresentationStyle = .Popover
+ alertController.popoverPresentationController?.sourceRect = rect
+ alertController.popoverPresentationController?.sourceView = gesture.view
+ self.photoBrowser?.presentViewController(alertController, animated: true, completion: nil)
+ }
}
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-