├── .gitignore ├── .travis.yml ├── Example ├── ParallexBanner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── ParallexBanner-Example.xcscheme ├── ParallexBanner.xcworkspace │ └── contents.xcworkspacedata ├── ParallexBanner │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Main.storyboard │ ├── MainTableViewController.swift │ ├── PureCodeViewController.swift │ ├── StoryboardViewController.swift │ ├── TableviewHeaderViewController.swift │ ├── ViewController.swift │ ├── p0.jpg │ ├── p1.jpg │ ├── p2.jpg │ └── placeholder.jpg ├── Podfile ├── Podfile.lock └── Pods │ ├── Kingfisher │ ├── LICENSE │ ├── README.md │ └── Sources │ │ ├── AnimatedImageView.swift │ │ ├── Image.swift │ │ ├── ImageCache.swift │ │ ├── ImageDownloader.swift │ │ ├── ImagePrefetcher.swift │ │ ├── ImageTransition.swift │ │ ├── ImageView+Kingfisher.swift │ │ ├── Kingfisher.h │ │ ├── KingfisherManager.swift │ │ ├── KingfisherOptionsInfo.swift │ │ ├── Resource.swift │ │ ├── String+MD5.swift │ │ ├── ThreadHelper.swift │ │ └── UIButton+Kingfisher.swift │ ├── Local Podspecs │ └── ParallexBanner.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ └── project.pbxproj │ └── Target Support Files │ ├── Kingfisher │ ├── Info.plist │ ├── Kingfisher-dummy.m │ ├── Kingfisher-prefix.pch │ ├── Kingfisher-umbrella.h │ ├── Kingfisher.modulemap │ └── Kingfisher.xcconfig │ ├── ParallexBanner │ ├── Info.plist │ ├── ParallexBanner-dummy.m │ ├── ParallexBanner-prefix.pch │ ├── ParallexBanner-umbrella.h │ ├── ParallexBanner.modulemap │ └── ParallexBanner.xcconfig │ ├── Pods-ParallexBanner_Example │ ├── Info.plist │ ├── Pods-ParallexBanner_Example-acknowledgements.markdown │ ├── Pods-ParallexBanner_Example-acknowledgements.plist │ ├── Pods-ParallexBanner_Example-dummy.m │ ├── Pods-ParallexBanner_Example-frameworks.sh │ ├── Pods-ParallexBanner_Example-resources.sh │ ├── Pods-ParallexBanner_Example-umbrella.h │ ├── Pods-ParallexBanner_Example.debug.xcconfig │ ├── Pods-ParallexBanner_Example.modulemap │ └── Pods-ParallexBanner_Example.release.xcconfig │ └── Pods-ParallexBanner_Tests │ ├── Info.plist │ ├── Pods-ParallexBanner_Tests-acknowledgements.markdown │ ├── Pods-ParallexBanner_Tests-acknowledgements.plist │ ├── Pods-ParallexBanner_Tests-dummy.m │ ├── Pods-ParallexBanner_Tests-frameworks.sh │ ├── Pods-ParallexBanner_Tests-resources.sh │ ├── Pods-ParallexBanner_Tests-umbrella.h │ ├── Pods-ParallexBanner_Tests.debug.xcconfig │ ├── Pods-ParallexBanner_Tests.modulemap │ └── Pods-ParallexBanner_Tests.release.xcconfig ├── LICENSE ├── ParallexBanner.podspec ├── ParallexBanner ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ └── ParallexBanner.swift ├── README.md ├── ScreenShots └── gif.gif └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | # Pods/ 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -workspace Example/ParallexBanner.xcworkspace -scheme ParallexBanner-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Example/ParallexBanner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/ParallexBanner.xcodeproj/xcshareddata/xcschemes/ParallexBanner-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/ParallexBanner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ParallexBanner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ParallexBanner 4 | // 5 | // Created by leo on 07/26/2016. 6 | // Copyright (c) 2016 leo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // 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. 24 | // 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. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // 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. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/ParallexBanner/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/ParallexBanner/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/ParallexBanner/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 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Example/ParallexBanner/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 | -------------------------------------------------------------------------------- /Example/ParallexBanner/MainTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ParallexBanner 4 | // 5 | // Created by Leo on 07/23/2016. 6 | // Copyright (c) 2016 Leo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ParallexBanner 11 | 12 | class MainTableViewController:UITableViewController{ 13 | let titles = ["Pure code","Storyboard","As TableView Header"] 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | self.navigationItem.title = "Demo list" 17 | } 18 | // MARK: - Delegate and dataSource - 19 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 20 | return titles.count 21 | } 22 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 23 | var cell = tableView.dequeueReusableCellWithIdentifier("cell") 24 | if cell == nil { 25 | cell = UITableViewCell(style: .Default, reuseIdentifier: "cell") 26 | } 27 | cell?.textLabel?.text = titles[indexPath.row] 28 | return cell! 29 | } 30 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 31 | tableView.deselectRowAtIndexPath(indexPath, animated: true) 32 | if indexPath.row == 0{ 33 | let dvc = PureCodeViewController() 34 | self.navigationController?.pushViewController(dvc, animated: true) 35 | }else if(indexPath.row == 1){ 36 | self.performSegueWithIdentifier("segue", sender: nil) 37 | }else if(indexPath.row == 2){ 38 | let dvc = TableviewHeaderViewController() 39 | self.navigationController?.pushViewController(dvc, animated: true) 40 | } 41 | } 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/ParallexBanner/PureCodeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PureCodeViewController.swift 3 | // ParallexBanner 4 | // 5 | // Created by huangwenchen on 16/7/26. 6 | // Copyright © 2016年 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import ParallexBanner 12 | class PureCodeViewController: UIViewController,ParallexBannerDelegate,ParallexBannerDataSource { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | self.navigationItem.title = "Pure code" 17 | self.view.backgroundColor = UIColor.whiteColor() 18 | let width = UIScreen.mainScreen().bounds.size.width; 19 | let height = width * 125.0/200.0; 20 | let frame = CGRectMake(0, 0, width, height) 21 | let banner = ParallexBanner(frame: frame) 22 | self.automaticallyAdjustsScrollViewInsets = false 23 | 24 | banner.delegate = self 25 | banner.dataSource = self 26 | banner.center = self.view.center 27 | self.view.addSubview(banner) 28 | // Do any additional setup after loading the view, typically from a nib. 29 | } 30 | 31 | func banner(banner: ParallexBanner, urlOrImageAtIndex index: NSInteger) -> AnyObject { 32 | let image = UIImage(named: "p\(index).jpg")! 33 | if index == 0{ 34 | return "http://img.blog.csdn.net/20160725214531068" 35 | }else{ 36 | return image 37 | } 38 | } 39 | func banner(banner: ParallexBanner, placeHolderForIndex index: NSInteger) -> UIImage? { 40 | return UIImage(named: "placeholder.jpg") 41 | } 42 | func numberOfBannersIn(bannner: ParallexBanner) -> NSInteger { 43 | return 3 44 | } 45 | func banner(banner: ParallexBanner, didClickAtIndex index: NSInteger) { 46 | print("Click \(index)") 47 | } 48 | func banner(banner: ParallexBanner, didScrollToIndex index: NSInteger) { 49 | print("Scroll to \(index)") 50 | } 51 | } -------------------------------------------------------------------------------- /Example/ParallexBanner/StoryboardViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryboardViewController.swift 3 | // ParallexBanner 4 | // 5 | // Created by huangwenchen on 16/7/26. 6 | // Copyright © 2016年 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import ParallexBanner 12 | 13 | func delay(delay:Double, closure:()->()) { 14 | dispatch_after( 15 | dispatch_time( 16 | DISPATCH_TIME_NOW, 17 | Int64(delay * Double(NSEC_PER_SEC)) 18 | ), 19 | dispatch_get_main_queue(), closure) 20 | } 21 | 22 | class StoryboadViewController: UIViewController,ParallexBannerDelegate,ParallexBannerDataSource { 23 | 24 | @IBOutlet weak var banner: ParallexBanner! 25 | var count = 0 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | self.navigationItem.title = "Auto reload after 3 seconds" 29 | self.view.backgroundColor = UIColor.whiteColor() 30 | self.automaticallyAdjustsScrollViewInsets = false 31 | 32 | banner.delegate = self 33 | banner.dataSource = self 34 | banner.autoScrollTimeInterval = 2.0 35 | // banner.enableScrollForSinglePage = true 36 | // banner.pageControl.hidesForSinglePage = false 37 | delay(3.0) { 38 | self.count = 3 39 | self.banner.reloadData() 40 | } 41 | // Do any additional setup after loading the view, typically from a nib. 42 | } 43 | 44 | func banner(banner: ParallexBanner, urlOrImageAtIndex index: NSInteger) -> AnyObject { 45 | let image = UIImage(named: "p\(index).jpg")! 46 | return image 47 | } 48 | func numberOfBannersIn(bannner: ParallexBanner) -> NSInteger { 49 | return count 50 | } 51 | func banner(banner: ParallexBanner, didClickAtIndex index: NSInteger) { 52 | print("Click \(index)") 53 | } 54 | func banner(banner: ParallexBanner, didScrollToIndex index: NSInteger) { 55 | print("Scroll to \(index)") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/ParallexBanner/TableviewHeaderViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableviewHeaderViewController.swift 3 | // ParallexBanner 4 | // 5 | // Created by huangwenchen on 16/7/26. 6 | // Copyright © 2016年 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import ParallexBanner 12 | 13 | class TableviewHeaderViewController: UITableViewController { 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | self.navigationItem.title = "As tableview header" 18 | self.view.backgroundColor = UIColor.whiteColor() 19 | let width = UIScreen.mainScreen().bounds.size.width; 20 | let height = width * 125.0/200.0; 21 | let frame = CGRectMake(0, 0, width, height) 22 | let banner = ParallexBanner(frame: frame) 23 | banner.delegate = self 24 | banner.dataSource = self 25 | self.tableView.tableHeaderView = banner 26 | // Do any additional setup after loading the view, typically from a nib. 27 | } 28 | // MARK: - TableView related - 29 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 30 | return 44.0; 31 | } 32 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 33 | return 20 34 | } 35 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 36 | var cell = tableView.dequeueReusableCellWithIdentifier("cell") 37 | if cell == nil { 38 | cell = UITableViewCell(style: .Default, reuseIdentifier: "cell") 39 | } 40 | cell?.textLabel?.text = "\(indexPath.row)" 41 | return cell! 42 | } 43 | 44 | } 45 | extension TableviewHeaderViewController:ParallexBannerDelegate,ParallexBannerDataSource{ 46 | // MARK: - Banner related - 47 | func banner(banner: ParallexBanner, urlOrImageAtIndex index: NSInteger) -> AnyObject { 48 | let image = UIImage(named: "p\(index).jpg")! 49 | if index == 0{ 50 | return "http://img.blog.csdn.net/20160725214531068" 51 | }else{ 52 | return image 53 | } 54 | } 55 | func numberOfBannersIn(bannner: ParallexBanner) -> NSInteger { 56 | return 3 57 | } 58 | func banner(banner: ParallexBanner, didClickAtIndex index: NSInteger) { 59 | print("Click \(index)") 60 | } 61 | func banner(banner: ParallexBanner, didScrollToIndex index: NSInteger) { 62 | print("Scroll to \(index)") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/ParallexBanner/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ParallexBanner 4 | // 5 | // Created by leo on 07/26/2016. 6 | // Copyright (c) 2016 leo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Example/ParallexBanner/p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoMobileDeveloper/ParallexBanner/ae2e48c8c94165ab4157ab8c7f3329e266ffd7ac/Example/ParallexBanner/p0.jpg -------------------------------------------------------------------------------- /Example/ParallexBanner/p1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoMobileDeveloper/ParallexBanner/ae2e48c8c94165ab4157ab8c7f3329e266ffd7ac/Example/ParallexBanner/p1.jpg -------------------------------------------------------------------------------- /Example/ParallexBanner/p2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoMobileDeveloper/ParallexBanner/ae2e48c8c94165ab4157ab8c7f3329e266ffd7ac/Example/ParallexBanner/p2.jpg -------------------------------------------------------------------------------- /Example/ParallexBanner/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoMobileDeveloper/ParallexBanner/ae2e48c8c94165ab4157ab8c7f3329e266ffd7ac/Example/ParallexBanner/placeholder.jpg -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'ParallexBanner_Example' do 4 | pod 'ParallexBanner', :path => '../' 5 | 6 | target 'ParallexBanner_Tests' do 7 | inherit! :search_paths 8 | 9 | 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Kingfisher (2.4.2) 3 | - ParallexBanner (0.1.0): 4 | - Kingfisher 5 | 6 | DEPENDENCIES: 7 | - ParallexBanner (from `../`) 8 | 9 | EXTERNAL SOURCES: 10 | ParallexBanner: 11 | :path: ../ 12 | 13 | SPEC CHECKSUMS: 14 | Kingfisher: 05bf8d04408aaa70fcd2c8c81b9f1d0f1ad313dd 15 | ParallexBanner: 7b39e5272f666fe9de0787b6c88dc888a66cc676 16 | 17 | PODFILE CHECKSUM: ac66f4d9cef0a35075dffaf388befd760a1173d5 18 | 19 | COCOAPODS: 1.0.0 20 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wei Wang 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 | 23 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Kingfisher 4 | 5 |

6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | codebeat 22 | 23 | 24 | 25 | 26 |

27 | 28 | 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. 29 | 30 | ## Features 31 | 32 | * Everything in Kingfisher is asynchronous, not only downloading, but also caching. That means you never need to worry about blocking your UI thread. 33 | * Multiple-layer cache. Downloaded images will be cached in both memory and disk. So there is no need to download again, this could boost your app's perceptual speed dramatically. 34 | * 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. 35 | * 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. 36 | * Cancelable processing task. You can cancel the downloading process if it is not needed anymore. 37 | * Prefetching. You can prefetch and cache the images which might soon appear in the page. It will bring your users great experience. 38 | * Independent components. You can use the downloader or caching system separately. Or even create your own cache based on Kingfisher's code. 39 | * Options to decompress the image in background before rendering it, which could improve the UI performance. 40 | * Categories over `UIImageView`, `NSImage` and `UIButton` for setting image from a URL directly. Use the same code across all Apple platforms. 41 | * Support GIF seamlessly. You could just download and set your GIF images as the same as you do for PNG/JPEG format using `AnimatedImageView`. 42 | * You could set `Activity Indicator` for your UIImageView or NSImageView to enable the indicator during loading image from web. 43 | 44 | ## Requirements 45 | 46 | * iOS 8.0+, tvOS 9.0+, watchOS 2.0+ or OS X 10.10+ 47 | * Xcode 7.3 or above 48 | 49 | If you are upgrading to Kingfisher 2.x from 1.x, please read the [Kingfisher 2.0 Migration Guide](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-2.0-Migration-Guide) for more information. 50 | 51 | Kingfisher is now supporting Swift 2.2. If you need to use Kingfisher in Swift 2.1, you need to pin the version to 2.1.0. 52 | 53 | ### Swift 3 54 | 55 | Kingfisher is now supporting Swift 3 in the [swift3](https://github.com/onevcat/Kingfisher/tree/swift3) branch. It is now under development and not be officially released yet. You could specify to that branch if you are working in a Swift 3 project. However, please reconsider if you want to use it in a releasing orientation product, since more breaking change would be applied later. 56 | 57 | ## Installation 58 | 59 | ### CocoaPods 60 | 61 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 62 | 63 | ``` bash 64 | $ gem install cocoapods 65 | ``` 66 | 67 | To integrate Kingfisher into your Xcode project using CocoaPods, specify it in your `Podfile`: 68 | 69 | ``` ruby 70 | source 'https://github.com/CocoaPods/Specs.git' 71 | platform :ios, '8.0' 72 | use_frameworks! 73 | 74 | pod 'Kingfisher', '~> 2.4' 75 | ``` 76 | 77 | Then, run the following command: 78 | 79 | ``` bash 80 | $ pod install 81 | ``` 82 | 83 | You should open the `{Project}.xcworkspace` instead of the `{Project}.xcodeproj` after you installed anything from CocoaPods. 84 | 85 | For more information about how to use CocoaPods, I suggest [this tutorial](http://www.raywenderlich.com/64546/introduction-to-cocoapods-2). 86 | 87 | ### Carthage 88 | 89 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager for Cocoa application. To install the carthage tool, you can use [Homebrew](http://brew.sh). 90 | 91 | ``` bash 92 | $ brew update 93 | $ brew install carthage 94 | ``` 95 | 96 | To integrate Kingfisher into your Xcode project using Carthage, specify it in your `Cartfile`: 97 | 98 | ``` ogdl 99 | github "onevcat/Kingfisher" ~> 2.4 100 | ``` 101 | 102 | Then, run the following command to build the Kingfisher framework: 103 | 104 | ``` bash 105 | $ carthage update 106 | 107 | ``` 108 | 109 | At last, you need to set up your Xcode project manually to add the Kingfisher framework. 110 | 111 | On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop each framework you want to use from the Carthage/Build folder on disk. 112 | 113 | On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script with the following content: 114 | 115 | ``` 116 | /usr/local/bin/carthage copy-frameworks 117 | ``` 118 | 119 | and add the paths to the frameworks you want to use under “Input Files”: 120 | 121 | ``` 122 | $(SRCROOT)/Carthage/Build/iOS/Kingfisher.framework 123 | ``` 124 | 125 | For more information about how to use Carthage, please see its [project page](https://github.com/Carthage/Carthage). 126 | 127 | ### Manually 128 | 129 | It is not recommended to install the framework manually, but if you prefer not to use either of the aforementioned dependency managers, you can integrate Kingfisher into your project manually. A regular way to use Kingfisher in your project would be using Embedded Framework. 130 | 131 | - Add Kingfisher as a [submodule](http://git-scm.com/docs/git-submodule). In your favorite terminal, `cd` into your top-level project directory, and entering the following command: 132 | 133 | ``` bash 134 | $ git submodule add https://github.com/onevcat/Kingfisher.git 135 | ``` 136 | 137 | - Open the `Kingfisher` folder, and drag `Kingfisher.xcodeproj` into the file navigator of your app project, under your app project. 138 | - In Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar. 139 | - In the tab bar at the top of that window, open the "Build Phases" panel. 140 | - Expand the "Target Dependencies" group, and add `Kingfisher.framework`. 141 | - Click on the `+` button at the top left of "Build Phases" panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add `Kingfisher.framework` of the platform you need. 142 | 143 | ## Usage 144 | 145 | You can find the full API documentation at [CocoaDocs](http://cocoadocs.org/docsets/Kingfisher/). 146 | 147 | ### UIImageView and NSImageView category 148 | 149 | Use Kingfisher in your project is as easy as a pie. You can use the `UIImageView` or `NSImageView` category and trust Kingfisher to manage downloading and cache images. 150 | 151 | #### Basic 152 | 153 | In your source files, add the following code: 154 | 155 | ``` swift 156 | import Kingfisher 157 | 158 | imageView.kf_setImageWithURL(NSURL(string: "http://your_image_url.png")!) 159 | ``` 160 | 161 | In most cases, Kingfisher is used in a reusable cell. Since the downloading process is asynchronous, the earlier image will be remained during the downloading of newer one. The placeholder version of this API could help: 162 | 163 | ``` swift 164 | imageView.kf_setImageWithURL(NSURL(string: "http://your_image_url.png")!, placeholderImage: nil) 165 | ``` 166 | 167 | By default, `Kingfisher` will use `absoluteString` of the URL as the key for cache. If you need another key instead of URL's `absoluteString`, there is another set of APIs accepting `Resource` as parameter: 168 | 169 | ``` swift 170 | let URL = NSURL(string: "http://your_image_url.png")! 171 | let resource = Resource(downloadURL: URL, cacheKey: "your_customized_key") 172 | 173 | imageView.kf_setImageWithResource(resource) 174 | ``` 175 | 176 | It will ask Kingfisher's manager to get the image for the "your_customized_key" from memory and disk first. If the manager does not find it, it will try to download the image at the URL, and store it with `cacheKey` ("your_customized_key" here) for next use. 177 | 178 | #### Options 179 | 180 | Kingfisher will search in cache (both memory and disk) first with the URL, if no image found, it will try to download and store the image in the cache. You can change this behavior by passing an option, to let it ignore the cache. 181 | 182 | ``` swift 183 | imageView.kf_setImageWithURL(NSURL(string: "your_image_url")!, 184 | placeholderImage: nil, 185 | optionsInfo: [.ForceRefresh]) 186 | ``` 187 | 188 | There are also other options to control the cache level, downloading priority, etc. Take some other examples: 189 | 190 | If you need to cache the downloaded image to a customized cache instead of the default one: 191 | 192 | ``` swift 193 | let myCache = ImageCache(name: "my_cache") 194 | 195 | imageView.kf_setImageWithURL(NSURL(string: "your_image_url")!, 196 | placeholderImage: nil, 197 | optionsInfo: [.TargetCache(myCache)]) 198 | ``` 199 | 200 | This is useful if you want to use a specified cache for some reasons. 201 | 202 | And if you need to fade in the image to image view during 1 second (image transition only works for iOS platform now): 203 | 204 | ``` 205 | imageView.kf_setImageWithURL(NSURL(string: "your_image_url")!, 206 | placeholderImage: nil, 207 | optionsInfo: [.Transition(ImageTransition.Fade(1))]) 208 | ``` 209 | 210 | You are also free to combine these options to customize the behavior: 211 | 212 | ```swift 213 | let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 214 | let optionInfo: KingfisherOptionsInfo = [ 215 | .ForceRefresh, 216 | .DownloadPriority(0.5), 217 | .CallbackDispatchQueue(queue), 218 | .Transition(ImageTransition.Fade(1)) 219 | ] 220 | ``` 221 | 222 | For more information about options, please see the `KingfisherOptionsInfo` in the [documentation](http://cocoadocs.org/docsets/Kingfisher/index.html). 223 | 224 | #### Callbacks 225 | 226 | You can get a chance during Kingfisher downloading images and when the process is done: 227 | 228 | ``` swift 229 | imageView.kf_setImageWithURL(NSURL(string: "your_image_url")!, 230 | placeholderImage: nil, 231 | optionsInfo: nil, 232 | progressBlock: { (receivedSize, totalSize) -> () in 233 | print("Download Progress: \(receivedSize)/\(totalSize)") 234 | }, 235 | completionHandler: { (image, error, cacheType, imageURL) -> () in 236 | print("Downloaded and set!") 237 | } 238 | ) 239 | ``` 240 | 241 | #### Cancel Task 242 | 243 | You can `cancel` the task if the images are not needed anymore. 244 | It could be useful when you use Kingfisher to set an image in a cell of table view or collection view, but users scroll the view and the cells disappeared before downloading finishing. 245 | 246 | ``` swift 247 | imageView.kf_setImageWithURL(NSURL(string: "http://your_image_url.png")!) 248 | 249 | // The image retrieving will stop. 250 | imageView.kf_cancelDownloadTask() 251 | ``` 252 | 253 | If you need more control and want to do some check before cancelling, all `kf_setImageWithURL` methods return a `RetrieveImageTask` object as well. You can also hold and manage it, then call cancel on the task: 254 | 255 | ``` swift 256 | let task = imageView.kf_setImageWithURL(NSURL(string: "http://your_image_url.png")!) 257 | 258 | let urlShouldNotBeCancelled: URL = ... 259 | 260 | if task.downloadTask?.URL != urlShouldNotBeCancelled { 261 | task.cancel() 262 | } 263 | ``` 264 | 265 | ### Downloader & Cache system 266 | 267 | Kingfisher will use the default downloader and cache if you do not specify them by yourself. You can access them by using `KingfisherManager.sharedManager.downloader` and `KingfisherManager.sharedManager.cache`. You can adjust some parameters to meet your demands: 268 | 269 | ``` swift 270 | let downloader = KingfisherManager.sharedManager.downloader 271 | 272 | // Download process will timeout after 5 seconds. Default is 15. 273 | downloader.downloadTimeout = 5 274 | 275 | // requestModifier will be called before image download request made. 276 | downloader.requestModifier = { 277 | (request: NSMutableURLRequest) in 278 | // Do what you need to modify the download request. Maybe add your HTTP basic authentication for example. 279 | } 280 | 281 | // Hosts in trustedHosts will be ignore the received challenge. 282 | // You can add the host of your self-signed site to it to bypass the SSL. 283 | // (Do not do it unless you know what you are doing) 284 | downloader.trustedHosts = Set(["your_self_signed_host"]) 285 | ``` 286 | 287 | ``` swift 288 | let cache = KingfisherManager.sharedManager.cache 289 | 290 | // Set max disk cache to 50 mb. Default is no limit. 291 | cache.maxDiskCacheSize = 50 * 1024 * 1024 292 | 293 | // Set max disk cache to duration to 3 days, Default is 1 week. 294 | cache.maxCachePeriodInSecond = 60 * 60 * 24 * 3 295 | 296 | // Get the disk size taken by the cache. 297 | cache.calculateDiskCacheSizeWithCompletionHandler { (size) -> () in 298 | print("disk size in bytes: \(size)") 299 | } 300 | ``` 301 | 302 | The memory cache will be purged whenever the app switched to background or receiving a memory warning. Disk cache will be cleaned when the conditions are met. You can also clear these caches manually: 303 | 304 | ``` swift 305 | // Clear memory cache right away. 306 | cache.clearMemoryCache() 307 | 308 | // Clear disk cache. This is an async operation. 309 | cache.clearDiskCache() 310 | 311 | // Clean expired or size exceeded disk cache. This is an async operation. 312 | cache.cleanExpiredDiskCache() 313 | ``` 314 | 315 | ### Prefetching 316 | 317 | 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. 318 | 319 | To prefetch some images, you could use the `ImagePrefetcher`: 320 | 321 | ```swift 322 | let urls = ["http://example.com/image1.jpg", "http://example.com/image2.jpg"].map { NSURL(string: $0)! } 323 | let prefetcher = ImagePrefetcher(urls: urls, optionsInfo: nil, progressBlock: nil, completionHandler: { 324 | (skippedResources, failedResources, completedResources) -> () in 325 | print("These resources are prefetched: \(completedResources)") 326 | }) 327 | prefetcher.start() 328 | ``` 329 | 330 | You can also stop a prefetch whenever you need: 331 | 332 | ```swift 333 | prefetcher.stop() 334 | ``` 335 | 336 | 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. 337 | 338 | ### Animated GIF 339 | 340 | You can load animated GIF by replacing `UIImageView` with `AnimatedImageView`, and then using the same API for a regular image view like this: 341 | 342 | ```swift 343 | let imageView = AnimatedImageView() 344 | imageView.kf_setImageWithURL(NSURL(string: "your_animated_gif_image_url")!) 345 | ``` 346 | 347 | `AnimatedImageView` will only decode some frames of your GIF image to get a smaller memory footprint. You can set the frame count you need to pre-load by setting the `framePreloadCount` property of an `AnimatedImageView` (default is 10). 348 | 349 | You can also load a GIF file by a regular `UIImageView`. However, all frames will be loaded and decoded into memory. It is probably not suitable if you are loading a large GIF image. For most cases, you may want to use `AnimatedImageView`. The GIF support in Kingfisher's `UIImageView` only fits the small files, and now is mostly serving for back compatibility. 350 | 351 | ## Future of Kingfisher 352 | 353 | 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. 354 | 355 | ## About the logo 356 | 357 | The logo of Kingfisher is inspired by [Tangram (七巧板)](http://en.wikipedia.org/wiki/Tangram), a dissection puzzle consisting of seven flat shapes from China. I believe she's a kingfisher bird instead of a swift, but someone insists that she is a pigeon. I guess I should give her a name. Hi, guys, do you have any suggestion? 358 | 359 | ## Contact 360 | 361 | Follow and contact me on [Twitter](http://twitter.com/onevcat) or [Sina Weibo](http://weibo.com/onevcat). If you find an issue, just [open a ticket](https://github.com/onevcat/Kingfisher/issues/new) on it. Pull requests are warmly welcome as well. 362 | 363 | ## License 364 | 365 | Kingfisher is released under the MIT license. See LICENSE for details. 366 | 367 | 368 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/AnimatedImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatableImageView.swift 3 | // Kingfisher 4 | // 5 | // Created by bl4ckra1sond3tre on 4/22/16. 6 | // 7 | // The AnimatableImageView, AnimatedFrame and Animator is a modified version of 8 | // some classes from kaishin's Gifu project (https://github.com/kaishin/Gifu) 9 | // 10 | // The MIT License (MIT) 11 | // 12 | // Copyright (c) 2014-2016 Reda Lemeden. 13 | // 14 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 15 | // this software and associated documentation files (the "Software"), to deal in 16 | // the Software without restriction, including without limitation the rights to 17 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 18 | // the Software, and to permit persons to whom the Software is furnished to do so, 19 | // subject to the following conditions: 20 | // 21 | // The above copyright notice and this permission notice shall be included in all 22 | // copies or substantial portions of the Software. 23 | // 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 26 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 27 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 28 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 29 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | // 31 | // The name and characters used in the demo of this software are property of their 32 | // respective owners. 33 | 34 | import UIKit 35 | import ImageIO 36 | 37 | /// `AnimatedImageView` is a subclass of `UIImageView` for displaying animated image. 38 | public class AnimatedImageView: UIImageView { 39 | 40 | /// Proxy object for prevending a reference cycle between the CADDisplayLink and AnimatedImageView. 41 | class TargetProxy { 42 | private weak var target: AnimatedImageView? 43 | 44 | init(target: AnimatedImageView) { 45 | self.target = target 46 | } 47 | 48 | @objc func onScreenUpdate() { 49 | target?.updateFrame() 50 | } 51 | } 52 | 53 | // MARK: - Public property 54 | /// Whether automatically play the animation when the view become visible. Default is true. 55 | public var autoPlayAnimatedImage = true 56 | 57 | /// The size of the frame cache. 58 | public var framePreloadCount = 10 59 | 60 | /// Specifies whether the GIF frames should be pre-scaled to save memory. Default is true. 61 | public var needsPrescaling = true 62 | 63 | /// The animation timer's run loop mode. Default is `NSRunLoopCommonModes`. Set this property to `NSDefaultRunLoopMode` will make the animation pause during UIScrollView scrolling. 64 | public var runLoopMode = NSRunLoopCommonModes { 65 | willSet { 66 | if runLoopMode == newValue { 67 | return 68 | } else { 69 | stopAnimating() 70 | displayLink.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: runLoopMode) 71 | displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: newValue) 72 | startAnimating() 73 | } 74 | } 75 | } 76 | 77 | // MARK: - Private property 78 | /// `Animator` instance that holds the frames of a specific image in memory. 79 | private var animator: Animator? 80 | 81 | /// A flag to avoid invalidating the displayLink on deinit if it was never created, because displayLink is so lazy. :D 82 | private var displayLinkInitialized: Bool = false 83 | 84 | /// A display link that keeps calling the `updateFrame` method on every screen refresh. 85 | private lazy var displayLink: CADisplayLink = { 86 | self.displayLinkInitialized = true 87 | let displayLink = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate)) 88 | displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: self.runLoopMode) 89 | displayLink.paused = true 90 | return displayLink 91 | }() 92 | 93 | // MARK: - Override 94 | override public var image: Image? { 95 | didSet { 96 | if image != oldValue { 97 | reset() 98 | } 99 | setNeedsDisplay() 100 | layer.setNeedsDisplay() 101 | } 102 | } 103 | 104 | deinit { 105 | if displayLinkInitialized { 106 | displayLink.invalidate() 107 | } 108 | } 109 | 110 | override public func isAnimating() -> Bool { 111 | if displayLinkInitialized { 112 | return !displayLink.paused 113 | } else { 114 | return super.isAnimating() 115 | } 116 | } 117 | 118 | /// Starts the animation. 119 | override public func startAnimating() { 120 | if self.isAnimating() { 121 | return 122 | } else { 123 | displayLink.paused = false 124 | } 125 | } 126 | 127 | /// Stops the animation. 128 | override public func stopAnimating() { 129 | super.stopAnimating() 130 | if displayLinkInitialized { 131 | displayLink.paused = true 132 | } 133 | } 134 | 135 | override public func displayLayer(layer: CALayer) { 136 | if let currentFrame = animator?.currentFrame { 137 | layer.contents = currentFrame.CGImage 138 | } else { 139 | layer.contents = image?.CGImage 140 | } 141 | } 142 | 143 | override public func didMoveToWindow() { 144 | super.didMoveToWindow() 145 | didMove() 146 | } 147 | 148 | override public func didMoveToSuperview() { 149 | super.didMoveToSuperview() 150 | didMove() 151 | } 152 | 153 | // This is for back compatibility that using regular UIImageView to show GIF. 154 | override func shouldPreloadAllGIF() -> Bool { 155 | return false 156 | } 157 | 158 | // MARK: - Private method 159 | /// Reset the animator. 160 | private func reset() { 161 | animator = nil 162 | if let imageSource = image?.kf_imageSource?.imageRef { 163 | animator = Animator(imageSource: imageSource, contentMode: contentMode, size: bounds.size, framePreloadCount: framePreloadCount) 164 | animator?.needsPrescaling = needsPrescaling 165 | animator?.prepareFrames() 166 | } 167 | didMove() 168 | } 169 | 170 | private func didMove() { 171 | if autoPlayAnimatedImage && animator != nil { 172 | if let _ = superview, _ = window { 173 | startAnimating() 174 | } else { 175 | stopAnimating() 176 | } 177 | } 178 | } 179 | 180 | /// Update the current frame with the displayLink duration. 181 | private func updateFrame() { 182 | if animator?.updateCurrentFrame(displayLink.duration) ?? false { 183 | layer.setNeedsDisplay() 184 | } 185 | } 186 | } 187 | 188 | /// Keeps a reference to an `Image` instance and its duration as a GIF frame. 189 | struct AnimatedFrame { 190 | var image: Image? 191 | let duration: NSTimeInterval 192 | 193 | static func null() -> AnimatedFrame { 194 | return AnimatedFrame(image: .None, duration: 0.0) 195 | } 196 | } 197 | 198 | // MARK: - Animator 199 | /// 200 | class Animator { 201 | // MARK: Private property 202 | private let size: CGSize 203 | private let maxFrameCount: Int 204 | private let imageSource: CGImageSourceRef 205 | 206 | private var animatedFrames = [AnimatedFrame]() 207 | private let maxTimeStep: NSTimeInterval = 1.0 208 | private var frameCount = 0 209 | private var currentFrameIndex = 0 210 | private var currentPreloadIndex = 0 211 | private var timeSinceLastFrameChange: NSTimeInterval = 0.0 212 | private var needsPrescaling = true 213 | 214 | /// Loop count of animatd image. 215 | private var loopCount = 0 216 | 217 | var currentFrame: UIImage? { 218 | return frameAtIndex(currentFrameIndex) 219 | } 220 | 221 | var contentMode: UIViewContentMode = .ScaleToFill 222 | 223 | /** 224 | Init an animator with image source reference. 225 | 226 | - parameter imageSource: The reference of animated image. 227 | 228 | - parameter contentMode: Content mode of AnimatedImageView. 229 | 230 | - parameter size: Size of AnimatedImageView. 231 | 232 | - framePreloadCount: Frame cache size. 233 | 234 | - returns: The animator object. 235 | */ 236 | init(imageSource src: CGImageSourceRef, contentMode mode: UIViewContentMode, size: CGSize, framePreloadCount: Int) { 237 | self.imageSource = src 238 | self.contentMode = mode 239 | self.size = size 240 | self.maxFrameCount = framePreloadCount 241 | } 242 | 243 | func frameAtIndex(index: Int) -> Image? { 244 | return animatedFrames[index].image 245 | } 246 | 247 | func prepareFrames() { 248 | frameCount = CGImageSourceGetCount(imageSource) 249 | 250 | if let properties = CGImageSourceCopyProperties(imageSource, nil), 251 | gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary, 252 | loopCount = gifInfo[kCGImagePropertyGIFLoopCount as String] as? Int { 253 | self.loopCount = loopCount 254 | } 255 | 256 | let frameToProcess = min(frameCount, maxFrameCount) 257 | animatedFrames.reserveCapacity(frameToProcess) 258 | animatedFrames = (0.. AnimatedFrame { 262 | guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else { 263 | return AnimatedFrame.null() 264 | } 265 | 266 | let frameDuration = imageSource.kf_GIFPropertiesAtIndex(index).flatMap { (gifInfo) -> Double? in 267 | let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as Double? 268 | let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as Double? 269 | let duration = unclampedDelayTime ?? delayTime 270 | /** 271 | http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp 272 | Many annoying ads specify a 0 duration to make an image flash as quickly as 273 | possible. We follow Safari and Firefox's behavior and use a duration of 100 ms 274 | for any frames that specify a duration of <= 10 ms. 275 | See and for more information. 276 | 277 | See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser. 278 | */ 279 | return duration > 0.011 ? duration : 0.100 280 | } 281 | 282 | let image = Image(CGImage: imageRef) 283 | let scaledImage: Image? 284 | 285 | if needsPrescaling { 286 | scaledImage = image.kf_resizeToSize(size, contentMode: contentMode) 287 | } else { 288 | scaledImage = image 289 | } 290 | 291 | return AnimatedFrame(image: scaledImage, duration: frameDuration ?? 0.0) 292 | } 293 | 294 | /** 295 | Updates the current frame if necessary using the frame timer and the duration of each frame in `animatedFrames`. 296 | */ 297 | func updateCurrentFrame(duration: CFTimeInterval) -> Bool { 298 | timeSinceLastFrameChange += min(maxTimeStep, duration) 299 | guard let frameDuration = animatedFrames[safe: currentFrameIndex]?.duration where frameDuration <= timeSinceLastFrameChange else { 300 | return false 301 | } 302 | 303 | timeSinceLastFrameChange -= frameDuration 304 | let lastFrameIndex = currentFrameIndex 305 | currentFrameIndex += 1 306 | currentFrameIndex = currentFrameIndex % animatedFrames.count 307 | 308 | if animatedFrames.count < frameCount { 309 | animatedFrames[lastFrameIndex] = prepareFrame(currentPreloadIndex) 310 | currentPreloadIndex += 1 311 | currentPreloadIndex = currentPreloadIndex % frameCount 312 | } 313 | return true 314 | } 315 | } 316 | 317 | // MARK: - Resize 318 | extension Image { 319 | func kf_resizeToSize(size: CGSize, contentMode: UIViewContentMode) -> Image { 320 | switch contentMode { 321 | case .ScaleAspectFit: 322 | let newSize = self.size.kf_sizeConstrainedSize(size) 323 | return kf_resizeToSize(newSize) 324 | case .ScaleAspectFill: 325 | let newSize = self.size.kf_sizeFillingSize(size) 326 | return kf_resizeToSize(newSize) 327 | default: 328 | return kf_resizeToSize(size) 329 | } 330 | } 331 | 332 | private func kf_resizeToSize(size: CGSize) -> Image { 333 | UIGraphicsBeginImageContextWithOptions(size, false, 0.0) 334 | drawInRect(CGRect(origin: CGPoint.zero, size: size)) 335 | let resizedImage = UIGraphicsGetImageFromCurrentImageContext() 336 | UIGraphicsEndImageContext() 337 | return resizedImage ?? self 338 | } 339 | } 340 | 341 | extension CGSize { 342 | func kf_sizeConstrainedSize(size: CGSize) -> CGSize { 343 | let aspectWidth = round(kf_aspectRatio * size.height) 344 | let aspectHeight = round(size.width / kf_aspectRatio) 345 | 346 | return aspectWidth > size.width ? CGSize(width: size.width, height: aspectHeight) : CGSize(width: aspectWidth, height: size.height) 347 | } 348 | 349 | func kf_sizeFillingSize(size: CGSize) -> CGSize { 350 | let aspectWidth = round(kf_aspectRatio * size.height) 351 | let aspectHeight = round(size.width / kf_aspectRatio) 352 | 353 | return aspectWidth < size.width ? CGSize(width: size.width, height: aspectHeight) : CGSize(width: aspectWidth, height: size.height) 354 | } 355 | private var kf_aspectRatio: CGFloat { 356 | return height == 0.0 ? 1.0 : width / height 357 | } 358 | } 359 | 360 | extension CGImageSourceRef { 361 | func kf_GIFPropertiesAtIndex(index: Int) -> [String: Double]? { 362 | let properties = CGImageSourceCopyPropertiesAtIndex(self, index, nil) as Dictionary? 363 | return properties?[kCGImagePropertyGIFDictionary as String] as? [String: Double] 364 | } 365 | } 366 | 367 | extension Array { 368 | subscript(safe index: Int) -> Element? { 369 | return indices ~= index ? self[index] : .None 370 | } 371 | } 372 | 373 | private func pure(value: T) -> [T] { 374 | return [value] 375 | } 376 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 16/1/6. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | 28 | #if os(OSX) 29 | import AppKit.NSImage 30 | public typealias Image = NSImage 31 | 32 | private var imagesKey: Void? 33 | private var durationKey: Void? 34 | #else 35 | import UIKit.UIImage 36 | import MobileCoreServices 37 | public typealias Image = UIImage 38 | 39 | private var imageSourceKey: Void? 40 | private var animatedImageDataKey: Void? 41 | #endif 42 | 43 | import ImageIO 44 | 45 | // MARK: - Image Properties 46 | extension Image { 47 | #if os(OSX) 48 | 49 | var CGImage: CGImageRef! { 50 | return CGImageForProposedRect(nil, context: nil, hints: nil) 51 | } 52 | 53 | var kf_scale: CGFloat { 54 | return 1.0 55 | } 56 | 57 | private(set) var kf_images: [Image]? { 58 | get { 59 | return objc_getAssociatedObject(self, &imagesKey) as? [Image] 60 | } 61 | set { 62 | objc_setAssociatedObject(self, &imagesKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 63 | } 64 | } 65 | 66 | private(set) var kf_duration: NSTimeInterval { 67 | get { 68 | return objc_getAssociatedObject(self, &durationKey) as? NSTimeInterval ?? 0.0 69 | } 70 | set { 71 | objc_setAssociatedObject(self, &durationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 72 | } 73 | } 74 | 75 | #else 76 | var kf_scale: CGFloat { 77 | return scale 78 | } 79 | 80 | var kf_images: [Image]? { 81 | return images 82 | } 83 | 84 | var kf_duration: NSTimeInterval { 85 | return duration 86 | } 87 | 88 | private(set) var kf_imageSource: ImageSource? { 89 | get { 90 | return objc_getAssociatedObject(self, &imageSourceKey) as? ImageSource 91 | } 92 | set { 93 | objc_setAssociatedObject(self, &imageSourceKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 94 | } 95 | } 96 | 97 | private(set) var kf_animatedImageData: NSData? { 98 | get { 99 | return objc_getAssociatedObject(self, &animatedImageDataKey) as? NSData 100 | } 101 | set { 102 | objc_setAssociatedObject(self, &animatedImageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 103 | } 104 | } 105 | #endif 106 | } 107 | 108 | // MARK: - Image Conversion 109 | extension Image { 110 | #if os(OSX) 111 | static func kf_imageWithCGImage(cgImage: CGImageRef, scale: CGFloat, refImage: Image?) -> Image { 112 | return Image(CGImage: cgImage, size: CGSize.zero) 113 | } 114 | 115 | /** 116 | Normalize the image. This method does nothing in OS X. 117 | 118 | - returns: The image itself. 119 | */ 120 | public func kf_normalizedImage() -> Image { 121 | return self 122 | } 123 | 124 | static func kf_animatedImageWithImages(images: [Image], duration: NSTimeInterval) -> Image? { 125 | return nil 126 | } 127 | #else 128 | static func kf_imageWithCGImage(cgImage: CGImageRef, scale: CGFloat, refImage: Image?) -> Image { 129 | if let refImage = refImage { 130 | return Image(CGImage: cgImage, scale: scale, orientation: refImage.imageOrientation) 131 | } else { 132 | return Image(CGImage: cgImage, scale: scale, orientation: .Up) 133 | } 134 | } 135 | 136 | /** 137 | Normalize the image. This method will try to redraw an image with orientation and scale considered. 138 | 139 | - returns: The normalized image with orientation set to up and correct scale. 140 | */ 141 | public func kf_normalizedImage() -> Image { 142 | // prevent animated image (GIF) lose it's images 143 | if images != nil { 144 | return self 145 | } 146 | 147 | if imageOrientation == .Up { 148 | return self 149 | } 150 | 151 | UIGraphicsBeginImageContextWithOptions(size, false, scale) 152 | drawInRect(CGRect(origin: CGPoint.zero, size: size)) 153 | let normalizedImage = UIGraphicsGetImageFromCurrentImageContext() 154 | UIGraphicsEndImageContext() 155 | 156 | return normalizedImage 157 | } 158 | 159 | static func kf_animatedImageWithImages(images: [Image], duration: NSTimeInterval) -> Image? { 160 | return Image.animatedImageWithImages(images, duration: duration) 161 | } 162 | #endif 163 | } 164 | 165 | 166 | // MARK: - PNG 167 | func ImagePNGRepresentation(image: Image) -> NSData? { 168 | #if os(OSX) 169 | if let cgimage = image.CGImage { 170 | let rep = NSBitmapImageRep(CGImage: cgimage) 171 | return rep.representationUsingType(.NSPNGFileType, properties:[:]) 172 | } 173 | return nil 174 | #else 175 | return UIImagePNGRepresentation(image) 176 | #endif 177 | } 178 | 179 | // MARK: - JPEG 180 | func ImageJPEGRepresentation(image: Image, _ compressionQuality: CGFloat) -> NSData? { 181 | #if os(OSX) 182 | let rep = NSBitmapImageRep(CGImage: image.CGImage) 183 | return rep.representationUsingType(.NSJPEGFileType, properties: [NSImageCompressionFactor: compressionQuality]) 184 | #else 185 | return UIImageJPEGRepresentation(image, compressionQuality) 186 | #endif 187 | } 188 | 189 | // MARK: - GIF 190 | func ImageGIFRepresentation(image: Image) -> NSData? { 191 | #if os(OSX) 192 | return ImageGIFRepresentation(image, duration: 0.0, repeatCount: 0) 193 | #else 194 | return image.kf_animatedImageData 195 | #endif 196 | } 197 | 198 | func ImageGIFRepresentation(image: Image, duration: NSTimeInterval, repeatCount: Int) -> NSData? { 199 | guard let images = image.kf_images else { 200 | return nil 201 | } 202 | 203 | let frameCount = images.count 204 | let gifDuration = duration <= 0.0 ? image.kf_duration / Double(frameCount) : duration / Double(frameCount) 205 | 206 | let frameProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: gifDuration]] 207 | let imageProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: repeatCount]] 208 | 209 | let data = NSMutableData() 210 | 211 | guard let destination = CGImageDestinationCreateWithData(data, kUTTypeGIF, frameCount, nil) else { 212 | return nil 213 | } 214 | CGImageDestinationSetProperties(destination, imageProperties) 215 | 216 | for image in images { 217 | CGImageDestinationAddImage(destination, image.CGImage!, frameProperties) 218 | } 219 | 220 | return CGImageDestinationFinalize(destination) ? NSData(data: data) : nil 221 | } 222 | 223 | func ImagesCountWithImageSource(ref: CGImageSourceRef) -> Int { 224 | return CGImageSourceGetCount(ref) 225 | } 226 | 227 | extension Image { 228 | static func kf_animatedImageWithGIFData(gifData data: NSData, preloadAll: Bool) -> Image? { 229 | return kf_animatedImageWithGIFData(gifData: data, scale: 1.0, duration: 0.0, preloadAll: preloadAll) 230 | } 231 | 232 | static func kf_animatedImageWithGIFData(gifData data: NSData, scale: CGFloat, duration: NSTimeInterval, preloadAll: Bool) -> Image? { 233 | 234 | func decodeFromSource(imageSource: CGImageSource, options: NSDictionary) -> ([Image], NSTimeInterval)? { 235 | 236 | let frameCount = CGImageSourceGetCount(imageSource) 237 | var images = [Image]() 238 | var gifDuration = 0.0 239 | for i in 0 ..< frameCount { 240 | 241 | guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, options) else { 242 | return nil 243 | } 244 | 245 | if frameCount == 1 { 246 | // Single frame 247 | gifDuration = Double.infinity 248 | } else { 249 | // Animated GIF 250 | guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil), 251 | gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary, 252 | frameDuration = (gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber) else 253 | { 254 | return nil 255 | } 256 | gifDuration += frameDuration.doubleValue 257 | } 258 | 259 | images.append(Image.kf_imageWithCGImage(imageRef, scale: scale, refImage: nil)) 260 | } 261 | 262 | return (images, gifDuration) 263 | } 264 | 265 | // Start of kf_animatedImageWithGIFData 266 | let options: NSDictionary = [kCGImageSourceShouldCache as String: NSNumber(bool: true), kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF] 267 | guard let imageSource = CGImageSourceCreateWithData(data, options) else { 268 | return nil 269 | } 270 | 271 | #if os(OSX) 272 | guard let (images, gifDuration) = decodeFromSource(imageSource, options: options) else { 273 | return nil 274 | } 275 | let image = Image(data: data) 276 | image?.kf_images = images 277 | image?.kf_duration = gifDuration 278 | 279 | return image 280 | #else 281 | 282 | if preloadAll { 283 | guard let (images, gifDuration) = decodeFromSource(imageSource, options: options) else { 284 | return nil 285 | } 286 | let image = Image.kf_animatedImageWithImages(images, duration: duration <= 0.0 ? gifDuration : duration) 287 | image?.kf_animatedImageData = data 288 | return image 289 | } else { 290 | let image = Image(data: data) 291 | image?.kf_animatedImageData = data 292 | image?.kf_imageSource = ImageSource(ref: imageSource) 293 | return image 294 | } 295 | #endif 296 | 297 | } 298 | } 299 | 300 | // MARK: - Create images from data 301 | extension Image { 302 | static func kf_imageWithData(data: NSData, scale: CGFloat, preloadAllGIFData: Bool) -> Image? { 303 | var image: Image? 304 | #if os(OSX) 305 | switch data.kf_imageFormat { 306 | case .JPEG: image = Image(data: data) 307 | case .PNG: image = Image(data: data) 308 | case .GIF: image = Image.kf_animatedImageWithGIFData(gifData: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData) 309 | case .Unknown: image = Image(data: data) 310 | } 311 | #else 312 | switch data.kf_imageFormat { 313 | case .JPEG: image = Image(data: data, scale: scale) 314 | case .PNG: image = Image(data: data, scale: scale) 315 | case .GIF: image = Image.kf_animatedImageWithGIFData(gifData: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData) 316 | case .Unknown: image = Image(data: data, scale: scale) 317 | } 318 | #endif 319 | 320 | return image 321 | } 322 | } 323 | 324 | // MARK: - Decode 325 | extension Image { 326 | func kf_decodedImage() -> Image? { 327 | return self.kf_decodedImage(scale: kf_scale) 328 | } 329 | 330 | func kf_decodedImage(scale scale: CGFloat) -> Image? { 331 | // prevent animated image (GIF) lose it's images 332 | #if os(iOS) 333 | if kf_imageSource != nil { 334 | return self 335 | } 336 | #else 337 | if kf_images != nil { 338 | return self 339 | } 340 | #endif 341 | 342 | let imageRef = self.CGImage 343 | let colorSpace = CGColorSpaceCreateDeviceRGB() 344 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue).rawValue 345 | 346 | let context = CGBitmapContextCreate(nil, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, 0, colorSpace, bitmapInfo) 347 | if let context = context { 348 | let rect = CGRect(x: 0, y: 0, width: CGImageGetWidth(imageRef), height: CGImageGetHeight(imageRef)) 349 | CGContextDrawImage(context, rect, imageRef) 350 | let decompressedImageRef = CGBitmapContextCreateImage(context) 351 | return Image.kf_imageWithCGImage(decompressedImageRef!, scale: scale, refImage: self) 352 | } else { 353 | return nil 354 | } 355 | } 356 | } 357 | 358 | /// Reference the source image reference 359 | class ImageSource { 360 | var imageRef: CGImageSourceRef? 361 | init(ref: CGImageSourceRef) { 362 | self.imageRef = ref 363 | } 364 | } 365 | 366 | // MARK: - Image format 367 | private let pngHeader: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] 368 | private let jpgHeaderSOI: [UInt8] = [0xFF, 0xD8] 369 | private let jpgHeaderIF: [UInt8] = [0xFF] 370 | private let gifHeader: [UInt8] = [0x47, 0x49, 0x46] 371 | 372 | enum ImageFormat { 373 | case Unknown, PNG, JPEG, GIF 374 | } 375 | 376 | extension NSData { 377 | var kf_imageFormat: ImageFormat { 378 | var buffer = [UInt8](count: 8, repeatedValue: 0) 379 | self.getBytes(&buffer, length: 8) 380 | if buffer == pngHeader { 381 | return .PNG 382 | } else if buffer[0] == jpgHeaderSOI[0] && 383 | buffer[1] == jpgHeaderSOI[1] && 384 | buffer[2] == jpgHeaderIF[0] 385 | { 386 | return .JPEG 387 | } else if buffer[0] == gifHeader[0] && 388 | buffer[1] == gifHeader[1] && 389 | buffer[2] == gifHeader[2] 390 | { 391 | return .GIF 392 | } 393 | 394 | return .Unknown 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/ImagePrefetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePrefetcher.swift 3 | // Kingfisher 4 | // 5 | // Created by Claire Knight on 24/02/2016 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | 28 | #if os(OSX) 29 | import AppKit 30 | #else 31 | import UIKit 32 | #endif 33 | 34 | 35 | /// Progress update block of prefetcher. 36 | /// 37 | /// - `skippedResources`: An array of resources that are already cached before the prefetching starting. 38 | /// - `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. 39 | /// - `completedResources`: An array of resources that are downloaded and cached successfully. 40 | public typealias PrefetcherProgressBlock = ((skippedResources: [Resource], failedResources: [Resource], completedResources: [Resource]) -> ()) 41 | 42 | /// Completion block of prefetcher. 43 | /// 44 | /// - `skippedResources`: An array of resources that are already cached before the prefetching starting. 45 | /// - `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. 46 | /// - `completedResources`: An array of resources that are downloaded and cached successfully. 47 | public typealias PrefetcherCompletionHandler = ((skippedResources: [Resource], failedResources: [Resource], completedResources: [Resource]) -> ()) 48 | 49 | /// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them. 50 | /// This is useful when you know a list of image resources and want to download them before showing. 51 | public class ImagePrefetcher { 52 | 53 | /// The maximum concurrent downloads to use when prefetching images. Default is 5. 54 | public var maxConcurrentDownloads = 5 55 | 56 | private let prefetchResources: [Resource] 57 | private let optionsInfo: KingfisherOptionsInfo 58 | private var progressBlock: PrefetcherProgressBlock? 59 | private var completionHandler: PrefetcherCompletionHandler? 60 | 61 | private var tasks = [NSURL: RetrieveImageDownloadTask]() 62 | 63 | private var skippedResources = [Resource]() 64 | private var completedResources = [Resource]() 65 | private var failedResources = [Resource]() 66 | 67 | private var requestedCount = 0 68 | private var stopped = false 69 | 70 | // The created manager used for prefetch. We will use the helper method in manager. 71 | private let manager: KingfisherManager 72 | 73 | private var finished: Bool { 74 | return failedResources.count + skippedResources.count + completedResources.count == prefetchResources.count 75 | } 76 | 77 | /** 78 | Init an image prefetcher with an array of URLs. 79 | 80 | The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable. 81 | After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process. 82 | The images already cached will be skipped without downloading again. 83 | 84 | - parameter urls: The URLs which should be prefetched. 85 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 86 | - parameter progressBlock: Called every time an resource is downloaded, skipped or cancelled. 87 | - parameter completionHandler: Called when the whole prefetching process finished. 88 | 89 | - returns: An `ImagePrefetcher` object. 90 | 91 | - Note: By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as 92 | the downloader and cache target respectively. You can specify another downloader or cache by using a customized `KingfisherOptionsInfo`. 93 | Both the progress and completion block will be invoked in main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method. 94 | */ 95 | public convenience init(urls: [NSURL], 96 | optionsInfo: KingfisherOptionsInfo? = nil, 97 | progressBlock: PrefetcherProgressBlock? = nil, 98 | completionHandler: PrefetcherCompletionHandler? = nil) 99 | { 100 | let resources = urls.map { Resource(downloadURL: $0) } 101 | self.init(resources: resources, optionsInfo: optionsInfo, progressBlock: progressBlock, completionHandler: completionHandler) 102 | } 103 | 104 | /** 105 | Init an image prefetcher with an array of resources. 106 | 107 | The prefetcher should be initiated with a list of prefetching targets. The resources list is immutable. 108 | After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process. 109 | The images already cached will be skipped without downloading again. 110 | 111 | - parameter resources: The resources which should be prefetched. See `Resource` type for more. 112 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 113 | - parameter progressBlock: Called every time an resource is downloaded, skipped or cancelled. 114 | - parameter completionHandler: Called when the whole prefetching process finished. 115 | 116 | - returns: An `ImagePrefetcher` object. 117 | 118 | - Note: By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as 119 | the downloader and cache target respectively. You can specify another downloader or cache by using a customized `KingfisherOptionsInfo`. 120 | Both the progress and completion block will be invoked in main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method. 121 | */ 122 | public init(resources: [Resource], 123 | optionsInfo: KingfisherOptionsInfo? = nil, 124 | progressBlock: PrefetcherProgressBlock? = nil, 125 | completionHandler: PrefetcherCompletionHandler? = nil) 126 | { 127 | prefetchResources = resources 128 | 129 | // We want all callbacks from main queue, so we ignore the call back queue in options 130 | let optionsInfoWithoutQueue = optionsInfo?.kf_removeAllMatchesIgnoringAssociatedValue(.CallbackDispatchQueue(nil)) 131 | self.optionsInfo = optionsInfoWithoutQueue ?? KingfisherEmptyOptionsInfo 132 | 133 | let cache = self.optionsInfo.targetCache ?? ImageCache.defaultCache 134 | let downloader = self.optionsInfo.downloader ?? ImageDownloader.defaultDownloader 135 | manager = KingfisherManager(downloader: downloader, cache: cache) 136 | 137 | self.progressBlock = progressBlock 138 | self.completionHandler = completionHandler 139 | } 140 | 141 | /** 142 | Start to download the resources and cache them. This can be useful for background downloading 143 | of assets that are required for later use in an app. This code will not try and update any UI 144 | with the results of the process. 145 | */ 146 | public func start() 147 | { 148 | // Since we want to handle the resources cancellation in main thread only. 149 | dispatch_async_safely_to_main_queue { () -> () in 150 | 151 | guard !self.stopped else { 152 | assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.") 153 | self.handleComplete() 154 | return 155 | } 156 | 157 | guard self.maxConcurrentDownloads > 0 else { 158 | assertionFailure("There should be concurrent downloads value should be at least 1.") 159 | self.handleComplete() 160 | return 161 | } 162 | 163 | guard self.prefetchResources.count > 0 else { 164 | self.handleComplete() 165 | return 166 | } 167 | 168 | let initialConcurentDownloads = min(self.prefetchResources.count, self.maxConcurrentDownloads) 169 | for i in 0 ..< initialConcurentDownloads { 170 | self.startPrefetchingResource(self.prefetchResources[i]) 171 | } 172 | } 173 | } 174 | 175 | 176 | /** 177 | Stop current downloading progress, and cancel any future prefetching activity that might be occuring. 178 | */ 179 | public func stop() { 180 | dispatch_async_safely_to_main_queue { 181 | 182 | if self.finished { 183 | return 184 | } 185 | 186 | self.stopped = true 187 | self.tasks.forEach { (_, task) -> () in 188 | task.cancel() 189 | } 190 | } 191 | } 192 | 193 | func downloadAndCacheResource(resource: Resource) { 194 | 195 | let task = RetrieveImageTask() 196 | let downloadTask = manager.downloadAndCacheImageWithURL( 197 | resource.downloadURL, 198 | forKey: resource.cacheKey, 199 | retrieveImageTask: task, 200 | progressBlock: nil, 201 | completionHandler: { 202 | (image, error, _, _) -> () in 203 | 204 | self.tasks.removeValueForKey(resource.downloadURL) 205 | 206 | if let _ = error { 207 | self.failedResources.append(resource) 208 | } else { 209 | self.completedResources.append(resource) 210 | } 211 | 212 | self.reportProgress() 213 | 214 | if self.stopped { 215 | if self.tasks.isEmpty { 216 | let pendingResources = self.prefetchResources[self.requestedCount.. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #if os(OSX) 28 | // Not implemented for OSX and watchOS yet. 29 | 30 | import AppKit 31 | 32 | public enum ImageTransition { 33 | case None 34 | var duration: NSTimeInterval { 35 | return 0 36 | } 37 | } 38 | 39 | #elseif os(watchOS) 40 | import UIKit 41 | public enum ImageTransition { 42 | case None 43 | var duration: NSTimeInterval { 44 | return 0 45 | } 46 | } 47 | #else 48 | import UIKit 49 | 50 | /** 51 | Transition effect to use when an image downloaded and set by `UIImageView` extension API in Kingfisher. 52 | You can assign an enum value with transition duration as an item in `KingfisherOptionsInfo` 53 | to enable the animation transition. 54 | 55 | Apple's UIViewAnimationOptions is used under the hood. 56 | For custom transition, you should specified your own transition options, animations and 57 | comletion handler as well. 58 | 59 | - None: No animation transistion. 60 | - Fade: Fade in the loaded image. 61 | - FlipFromLeft: Flip from left transition. 62 | - FlipFromRight: Flip from right transition. 63 | - FlipFromTop: Flip from top transition. 64 | - FlipFromBottom: Flip from bottom transition. 65 | - Custom: Custom transition. 66 | */ 67 | public enum ImageTransition { 68 | case None 69 | case Fade(NSTimeInterval) 70 | 71 | case FlipFromLeft(NSTimeInterval) 72 | case FlipFromRight(NSTimeInterval) 73 | case FlipFromTop(NSTimeInterval) 74 | case FlipFromBottom(NSTimeInterval) 75 | 76 | case Custom(duration: NSTimeInterval, 77 | options: UIViewAnimationOptions, 78 | animations: ((UIImageView, UIImage) -> Void)?, 79 | completion: ((Bool) -> Void)?) 80 | 81 | var duration: NSTimeInterval { 82 | switch self { 83 | case .None: return 0 84 | case .Fade(let duration): return duration 85 | 86 | case .FlipFromLeft(let duration): return duration 87 | case .FlipFromRight(let duration): return duration 88 | case .FlipFromTop(let duration): return duration 89 | case .FlipFromBottom(let duration): return duration 90 | 91 | case .Custom(let duration, _, _, _): return duration 92 | } 93 | } 94 | 95 | var animationOptions: UIViewAnimationOptions { 96 | switch self { 97 | case .None: return .TransitionNone 98 | case .Fade(_): return .TransitionCrossDissolve 99 | 100 | case .FlipFromLeft(_): return .TransitionFlipFromLeft 101 | case .FlipFromRight(_): return .TransitionFlipFromRight 102 | case .FlipFromTop(_): return .TransitionFlipFromTop 103 | case .FlipFromBottom(_): return .TransitionFlipFromBottom 104 | 105 | case .Custom(_, let options, _, _): return options 106 | } 107 | } 108 | 109 | var animations: ((UIImageView, UIImage) -> Void)? { 110 | switch self { 111 | case .Custom(_, _, let animations, _): return animations 112 | default: return {$0.image = $1} 113 | } 114 | } 115 | 116 | var completion: ((Bool) -> Void)? { 117 | switch self { 118 | case .Custom(_, _, _, let completion): return completion 119 | default: return nil 120 | } 121 | } 122 | } 123 | #endif 124 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/ImageView+Kingfisher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageView+Kingfisher.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | 28 | #if os(OSX) 29 | import AppKit 30 | typealias ImageView = NSImageView 31 | public typealias IndicatorView = NSProgressIndicator 32 | #else 33 | import UIKit 34 | typealias ImageView = UIImageView 35 | public typealias IndicatorView = UIActivityIndicatorView 36 | #endif 37 | 38 | // MARK: - Set Images 39 | /** 40 | * Set image to use from web. 41 | */ 42 | extension ImageView { 43 | 44 | /** 45 | Set an image with a URL, a placeholder image, options, progress handler and completion handler. 46 | 47 | - parameter URL: The URL of image. 48 | - parameter placeholderImage: A placeholder image when retrieving the image at URL. 49 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 50 | - parameter progressBlock: Called when the image downloading progress gets updated. 51 | - parameter completionHandler: Called when the image retrieved and set. 52 | 53 | - returns: A task represents the retrieving process. 54 | 55 | - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 56 | The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method. 57 | */ 58 | 59 | public func kf_setImageWithURL(URL: NSURL?, 60 | placeholderImage: Image? = nil, 61 | optionsInfo: KingfisherOptionsInfo? = nil, 62 | progressBlock: DownloadProgressBlock? = nil, 63 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask 64 | { 65 | let resource = URL.map { Resource(downloadURL: $0) } 66 | return kf_setImageWithResource(resource, 67 | placeholderImage: placeholderImage, 68 | optionsInfo: optionsInfo, 69 | progressBlock: progressBlock, 70 | completionHandler: completionHandler) 71 | } 72 | 73 | 74 | /** 75 | Set an image with a URL, a placeholder image, options, progress handler and completion handler. 76 | 77 | - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`. 78 | - parameter placeholderImage: A placeholder image when retrieving the image at URL. 79 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 80 | - parameter progressBlock: Called when the image downloading progress gets updated. 81 | - parameter completionHandler: Called when the image retrieved and set. 82 | 83 | - returns: A task represents the retrieving process. 84 | 85 | - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 86 | The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method. 87 | */ 88 | public func kf_setImageWithResource(resource: Resource?, 89 | placeholderImage: Image? = nil, 90 | optionsInfo: KingfisherOptionsInfo? = nil, 91 | progressBlock: DownloadProgressBlock? = nil, 92 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask 93 | { 94 | image = placeholderImage 95 | 96 | guard let resource = resource else { 97 | completionHandler?(image: nil, error: nil, cacheType: .None, imageURL: nil) 98 | return RetrieveImageTask.emptyTask 99 | } 100 | 101 | let showIndicatorWhenLoading = kf_showIndicatorWhenLoading 102 | var indicator: IndicatorView? = nil 103 | if showIndicatorWhenLoading { 104 | indicator = kf_indicator 105 | indicator?.hidden = false 106 | indicator?.kf_startAnimating() 107 | } 108 | 109 | kf_setWebURL(resource.downloadURL) 110 | 111 | var options = optionsInfo ?? [] 112 | if shouldPreloadAllGIF() { 113 | options.append(.PreloadAllGIFData) 114 | } 115 | 116 | let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: options, 117 | progressBlock: { receivedSize, totalSize in 118 | if let progressBlock = progressBlock { 119 | progressBlock(receivedSize: receivedSize, totalSize: totalSize) 120 | } 121 | }, 122 | completionHandler: {[weak self] image, error, cacheType, imageURL in 123 | 124 | dispatch_async_safely_to_main_queue { 125 | guard let sSelf = self where imageURL == sSelf.kf_webURL else { 126 | return 127 | } 128 | 129 | sSelf.kf_setImageTask(nil) 130 | 131 | guard let image = image else { 132 | indicator?.kf_stopAnimating() 133 | completionHandler?(image: nil, error: error, cacheType: cacheType, imageURL: imageURL) 134 | return 135 | } 136 | 137 | if let transitionItem = options.kf_firstMatchIgnoringAssociatedValue(.Transition(.None)), 138 | case .Transition(let transition) = transitionItem where ( options.forceTransition || cacheType == .None) { 139 | #if !os(OSX) 140 | UIView.transitionWithView(sSelf, duration: 0.0, options: [], 141 | animations: { 142 | indicator?.kf_stopAnimating() 143 | }, 144 | completion: { finished in 145 | UIView.transitionWithView(sSelf, duration: transition.duration, 146 | options: [transition.animationOptions, .AllowUserInteraction], 147 | animations: { 148 | // Set image property in the animation. 149 | transition.animations?(sSelf, image) 150 | }, 151 | completion: { finished in 152 | transition.completion?(finished) 153 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 154 | }) 155 | }) 156 | #endif 157 | } else { 158 | indicator?.kf_stopAnimating() 159 | sSelf.image = image 160 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 161 | } 162 | } 163 | }) 164 | 165 | kf_setImageTask(task) 166 | 167 | return task 168 | } 169 | } 170 | 171 | extension ImageView { 172 | func shouldPreloadAllGIF() -> Bool { 173 | return true 174 | } 175 | } 176 | 177 | extension ImageView { 178 | /** 179 | Cancel the image download task bounded to the image view if it is running. 180 | Nothing will happen if the downloading has already finished. 181 | */ 182 | public func kf_cancelDownloadTask() { 183 | kf_imageTask?.downloadTask?.cancel() 184 | } 185 | } 186 | 187 | // MARK: - Associated Object 188 | private var lastURLKey: Void? 189 | private var indicatorKey: Void? 190 | private var showIndicatorWhenLoadingKey: Void? 191 | private var imageTaskKey: Void? 192 | 193 | extension ImageView { 194 | /// Get the image URL binded to this image view. 195 | public var kf_webURL: NSURL? { 196 | return objc_getAssociatedObject(self, &lastURLKey) as? NSURL 197 | } 198 | 199 | private func kf_setWebURL(URL: NSURL) { 200 | objc_setAssociatedObject(self, &lastURLKey, URL, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 201 | } 202 | 203 | /// Whether show an animating indicator when the image view is loading an image or not. 204 | /// Default is false. 205 | public var kf_showIndicatorWhenLoading: Bool { 206 | get { 207 | if let result = objc_getAssociatedObject(self, &showIndicatorWhenLoadingKey) as? NSNumber { 208 | return result.boolValue 209 | } else { 210 | return false 211 | } 212 | } 213 | 214 | set { 215 | if kf_showIndicatorWhenLoading == newValue { 216 | return 217 | } else { 218 | if newValue { 219 | 220 | #if os(OSX) 221 | let indicator = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) 222 | indicator.controlSize = .SmallControlSize 223 | indicator.style = .SpinningStyle 224 | #else 225 | #if os(tvOS) 226 | let indicatorStyle = UIActivityIndicatorViewStyle.White 227 | #else 228 | let indicatorStyle = UIActivityIndicatorViewStyle.Gray 229 | #endif 230 | let indicator = UIActivityIndicatorView(activityIndicatorStyle:indicatorStyle) 231 | indicator.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleBottomMargin, .FlexibleTopMargin] 232 | #endif 233 | 234 | indicator.kf_center = CGPoint(x: CGRectGetMidX(bounds), y: CGRectGetMidY(bounds)) 235 | indicator.hidden = true 236 | 237 | self.addSubview(indicator) 238 | 239 | kf_setIndicator(indicator) 240 | } else { 241 | kf_indicator?.removeFromSuperview() 242 | kf_setIndicator(nil) 243 | } 244 | 245 | objc_setAssociatedObject(self, &showIndicatorWhenLoadingKey, NSNumber(bool: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 246 | } 247 | } 248 | } 249 | 250 | /// The indicator view showing when loading. This will be `nil` if `kf_showIndicatorWhenLoading` is false. 251 | /// You may want to use this to set the indicator style or color when you set `kf_showIndicatorWhenLoading` to true. 252 | public var kf_indicator: IndicatorView? { 253 | return objc_getAssociatedObject(self, &indicatorKey) as? IndicatorView 254 | } 255 | 256 | private func kf_setIndicator(indicator: IndicatorView?) { 257 | objc_setAssociatedObject(self, &indicatorKey, indicator, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 258 | } 259 | 260 | private var kf_imageTask: RetrieveImageTask? { 261 | return objc_getAssociatedObject(self, &imageTaskKey) as? RetrieveImageTask 262 | } 263 | 264 | private func kf_setImageTask(task: RetrieveImageTask?) { 265 | objc_setAssociatedObject(self, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 266 | } 267 | } 268 | 269 | 270 | extension IndicatorView { 271 | func kf_startAnimating() { 272 | #if os(OSX) 273 | startAnimation(nil) 274 | #else 275 | startAnimating() 276 | #endif 277 | hidden = false 278 | } 279 | 280 | func kf_stopAnimating() { 281 | #if os(OSX) 282 | stopAnimation(nil) 283 | #else 284 | stopAnimating() 285 | #endif 286 | hidden = true 287 | } 288 | 289 | #if os(OSX) 290 | var kf_center: CGPoint { 291 | get { 292 | return CGPoint(x: frame.origin.x + frame.size.width / 2.0, y: frame.origin.y + frame.size.height / 2.0 ) 293 | } 294 | set { 295 | let newFrame = CGRect(x: newValue.x - frame.size.width / 2.0, y: newValue.y - frame.size.height / 2.0, width: frame.size.width, height: frame.size.height) 296 | frame = newFrame 297 | } 298 | } 299 | #else 300 | var kf_center: CGPoint { 301 | get { 302 | return center 303 | } 304 | set { 305 | center = newValue 306 | } 307 | } 308 | #endif 309 | } 310 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/Kingfisher.h: -------------------------------------------------------------------------------- 1 | // 2 | // Kingfisher.h 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #import 28 | 29 | //! Project version number for Kingfisher. 30 | FOUNDATION_EXPORT double KingfisherVersionNumber; 31 | 32 | //! Project version string for Kingfisher. 33 | FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; 34 | 35 | // In this header, you should import all the public headers of your framework using statements like #import 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/KingfisherManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KingfisherManager.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #if os(OSX) 28 | import AppKit 29 | #else 30 | import UIKit 31 | #endif 32 | 33 | public typealias DownloadProgressBlock = ((receivedSize: Int64, totalSize: Int64) -> ()) 34 | public typealias CompletionHandler = ((image: Image?, error: NSError?, cacheType: CacheType, imageURL: NSURL?) -> ()) 35 | 36 | /// RetrieveImageTask represents a task of image retrieving process. 37 | /// It contains an async task of getting image from disk and from network. 38 | public class RetrieveImageTask { 39 | 40 | static let emptyTask = RetrieveImageTask() 41 | 42 | // If task is canceled before the download task started (which means the `downloadTask` is nil), 43 | // the download task should not begin. 44 | var cancelledBeforeDownloadStarting: Bool = false 45 | 46 | /// The disk retrieve task in this image task. Kingfisher will try to look up in cache first. This task represent the cache search task. 47 | public var diskRetrieveTask: RetrieveImageDiskTask? 48 | 49 | /// The network retrieve task in this image task. 50 | public var downloadTask: RetrieveImageDownloadTask? 51 | 52 | /** 53 | Cancel current task. If this task does not begin or already done, do nothing. 54 | */ 55 | public func cancel() { 56 | // From Xcode 7 beta 6, the `dispatch_block_cancel` will crash at runtime. 57 | // It fixed in Xcode 7.1. 58 | // See https://github.com/onevcat/Kingfisher/issues/99 for more. 59 | if let diskRetrieveTask = diskRetrieveTask { 60 | dispatch_block_cancel(diskRetrieveTask) 61 | } 62 | 63 | if let downloadTask = downloadTask { 64 | downloadTask.cancel() 65 | } else { 66 | cancelledBeforeDownloadStarting = true 67 | } 68 | } 69 | } 70 | 71 | /// Error domain of Kingfisher 72 | public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error" 73 | 74 | private let instance = KingfisherManager() 75 | 76 | /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache. 77 | /// You can use this class to retrieve an image via a specified URL from web or cache. 78 | public class KingfisherManager { 79 | 80 | /// Shared manager used by the extensions across Kingfisher. 81 | public class var sharedManager: KingfisherManager { 82 | return instance 83 | } 84 | 85 | /// Cache used by this manager 86 | public var cache: ImageCache 87 | 88 | /// Downloader used by this manager 89 | public var downloader: ImageDownloader 90 | 91 | /** 92 | Default init method 93 | 94 | - returns: A Kingfisher manager object with default cache, default downloader, and default prefetcher. 95 | */ 96 | public convenience init() { 97 | self.init(downloader: ImageDownloader.defaultDownloader, cache: ImageCache.defaultCache) 98 | } 99 | 100 | init(downloader: ImageDownloader, cache: ImageCache) { 101 | self.downloader = downloader 102 | self.cache = cache 103 | } 104 | 105 | /** 106 | Get an image with resource. 107 | If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first. 108 | If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`. 109 | These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more. 110 | 111 | - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`. 112 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 113 | - parameter progressBlock: Called every time downloaded data changed. This could be used as a progress UI. 114 | - parameter completionHandler: Called when the whole retrieving process finished. 115 | 116 | - returns: A `RetrieveImageTask` task object. You can use this object to cancel the task. 117 | */ 118 | public func retrieveImageWithResource(resource: Resource, 119 | optionsInfo: KingfisherOptionsInfo?, 120 | progressBlock: DownloadProgressBlock?, 121 | completionHandler: CompletionHandler?) -> RetrieveImageTask 122 | { 123 | let task = RetrieveImageTask() 124 | 125 | if let optionsInfo = optionsInfo where optionsInfo.forceRefresh { 126 | downloadAndCacheImageWithURL(resource.downloadURL, 127 | forKey: resource.cacheKey, 128 | retrieveImageTask: task, 129 | progressBlock: progressBlock, 130 | completionHandler: completionHandler, 131 | options: optionsInfo) 132 | } else { 133 | tryToRetrieveImageFromCacheForKey(resource.cacheKey, 134 | withURL: resource.downloadURL, 135 | retrieveImageTask: task, 136 | progressBlock: progressBlock, 137 | completionHandler: completionHandler, 138 | options: optionsInfo) 139 | } 140 | 141 | return task 142 | } 143 | 144 | /** 145 | Get an image with `URL.absoluteString` as the key. 146 | If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first. 147 | If not found, it will download the image at URL and cache it with `URL.absoluteString` value as its key. 148 | 149 | If you need to specify the key other than `URL.absoluteString`, please use resource version of this API with `resource.cacheKey` set to what you want. 150 | 151 | These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more. 152 | 153 | - parameter URL: The image URL. 154 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 155 | - parameter progressBlock: Called every time downloaded data changed. This could be used as a progress UI. 156 | - parameter completionHandler: Called when the whole retrieving process finished. 157 | 158 | - returns: A `RetrieveImageTask` task object. You can use this object to cancel the task. 159 | */ 160 | public func retrieveImageWithURL(URL: NSURL, 161 | optionsInfo: KingfisherOptionsInfo?, 162 | progressBlock: DownloadProgressBlock?, 163 | completionHandler: CompletionHandler?) -> RetrieveImageTask 164 | { 165 | return retrieveImageWithResource(Resource(downloadURL: URL), optionsInfo: optionsInfo, progressBlock: progressBlock, completionHandler: completionHandler) 166 | } 167 | 168 | func downloadAndCacheImageWithURL(URL: NSURL, 169 | forKey key: String, 170 | retrieveImageTask: RetrieveImageTask, 171 | progressBlock: DownloadProgressBlock?, 172 | completionHandler: CompletionHandler?, 173 | options: KingfisherOptionsInfo?) -> RetrieveImageDownloadTask? 174 | { 175 | let downloader = options?.downloader ?? self.downloader 176 | return downloader.downloadImageWithURL(URL, retrieveImageTask: retrieveImageTask, options: options, 177 | progressBlock: { receivedSize, totalSize in 178 | progressBlock?(receivedSize: receivedSize, totalSize: totalSize) 179 | }, 180 | completionHandler: { image, error, imageURL, originalData in 181 | 182 | let targetCache = options?.targetCache ?? self.cache 183 | if let error = error where error.code == KingfisherError.NotModified.rawValue { 184 | // Not modified. Try to find the image from cache. 185 | // (The image should be in cache. It should be guaranteed by the framework users.) 186 | targetCache.retrieveImageForKey(key, options: options, completionHandler: { (cacheImage, cacheType) -> () in 187 | completionHandler?(image: cacheImage, error: nil, cacheType: cacheType, imageURL: URL) 188 | 189 | }) 190 | return 191 | } 192 | 193 | if let image = image, originalData = originalData { 194 | targetCache.storeImage(image, originalData: originalData, forKey: key, toDisk: !(options?.cacheMemoryOnly ?? false), completionHandler: nil) 195 | } 196 | 197 | completionHandler?(image: image, error: error, cacheType: .None, imageURL: URL) 198 | 199 | }) 200 | } 201 | 202 | func tryToRetrieveImageFromCacheForKey(key: String, 203 | withURL URL: NSURL, 204 | retrieveImageTask: RetrieveImageTask, 205 | progressBlock: DownloadProgressBlock?, 206 | completionHandler: CompletionHandler?, 207 | options: KingfisherOptionsInfo?) 208 | { 209 | let diskTaskCompletionHandler: CompletionHandler = { (image, error, cacheType, imageURL) -> () in 210 | // Break retain cycle created inside diskTask closure below 211 | retrieveImageTask.diskRetrieveTask = nil 212 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 213 | } 214 | 215 | let targetCache = options?.targetCache ?? cache 216 | let diskTask = targetCache.retrieveImageForKey(key, options: options, 217 | completionHandler: { image, cacheType in 218 | if image != nil { 219 | diskTaskCompletionHandler(image: image, error: nil, cacheType:cacheType, imageURL: URL) 220 | } else { 221 | self.downloadAndCacheImageWithURL(URL, 222 | forKey: key, 223 | retrieveImageTask: retrieveImageTask, 224 | progressBlock: progressBlock, 225 | completionHandler: diskTaskCompletionHandler, 226 | options: options) 227 | } 228 | }) 229 | retrieveImageTask.diskRetrieveTask = diskTask 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/KingfisherOptionsInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KingfisherOptionsInfo.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/23. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #if os(OSX) 28 | import AppKit 29 | #else 30 | import UIKit 31 | #endif 32 | 33 | 34 | /** 35 | * KingfisherOptionsInfo is a typealias for [KingfisherOptionsInfoItem]. You can use the enum of option item with value to control some behaviors of Kingfisher. 36 | */ 37 | public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem] 38 | let KingfisherEmptyOptionsInfo = [KingfisherOptionsInfoItem]() 39 | 40 | /** 41 | Items could be added into KingfisherOptionsInfo. 42 | 43 | - TargetCache: The associated value of this member should be an ImageCache object. Kingfisher will use the specified cache object when handling related operations, including trying to retrieve the cached images and store the downloaded image to it. 44 | - Downloader: The associated value of this member should be an ImageDownloader object. Kingfisher will use this downloader to download the images. 45 | - Transition: Member for animation transition when using UIImageView. Kingfisher will use the `ImageTransition` of this enum to animate the image in if it is downloaded from web. The transition will not happen when the image is retrieved from either memory or disk cache by default. If you need to do the transition even when the image being retrieved from cache, set `ForceTransition` as well. 46 | - DownloadPriority: Associated `Float` value will be set as the priority of image download task. The value for it should be between 0.0~1.0. If this option not set, the default value (`NSURLSessionTaskPriorityDefault`) will be used. 47 | - ForceRefresh: If set, `Kingfisher` will ignore the cache and try to fire a download task for the resource. 48 | - ForceTransition: If set, setting the image to an image view will happen with transition even when retrieved from cache. See `Transition` option for more. 49 | - CacheMemoryOnly: If set, `Kingfisher` will only cache the value in memory but not in disk. 50 | - BackgroundDecode: Decode the image in background thread before using. 51 | - CallbackDispatchQueue: The associated value of this member will be used as the target queue of dispatch callbacks when retrieving images from cache. If not set, `Kingfisher` will use main quese for callbacks. 52 | - ScaleFactor: The associated value of this member will be used as the scale factor when converting retrieved data to an image. 53 | - PreloadAllGIFData: Whether all the GIF data should be preloaded. Default it false, which means following frames will be loaded on need. If true, all the GIF data will be loaded and decoded into memory. This option is mainly used for back compatibility internally. You should not set it directly. `AnimatedImageView` will not preload all data, while a normal image view (`UIImageView` or `NSImageView`) will load all data. Choose to use corresponding image view type instead of setting this option. 54 | */ 55 | public enum KingfisherOptionsInfoItem { 56 | case TargetCache(ImageCache?) 57 | case Downloader(ImageDownloader?) 58 | case Transition(ImageTransition) 59 | case DownloadPriority(Float) 60 | case ForceRefresh 61 | case ForceTransition 62 | case CacheMemoryOnly 63 | case BackgroundDecode 64 | case CallbackDispatchQueue(dispatch_queue_t?) 65 | case ScaleFactor(CGFloat) 66 | case PreloadAllGIFData 67 | } 68 | 69 | infix operator <== { 70 | associativity none 71 | precedence 160 72 | } 73 | 74 | // This operator returns true if two `KingfisherOptionsInfoItem` enum is the same, without considering the associated values. 75 | func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Bool { 76 | switch (lhs, rhs) { 77 | case (.TargetCache(_), .TargetCache(_)): fallthrough 78 | case (.Downloader(_), .Downloader(_)): fallthrough 79 | case (.Transition(_), .Transition(_)): fallthrough 80 | case (.DownloadPriority(_), .DownloadPriority(_)): fallthrough 81 | case (.ForceRefresh, .ForceRefresh): fallthrough 82 | case (.ForceTransition, .ForceTransition): fallthrough 83 | case (.CacheMemoryOnly, .CacheMemoryOnly): fallthrough 84 | case (.BackgroundDecode, .BackgroundDecode): fallthrough 85 | case (.CallbackDispatchQueue(_), .CallbackDispatchQueue(_)): fallthrough 86 | case (.ScaleFactor(_), .ScaleFactor(_)): fallthrough 87 | case (.PreloadAllGIFData, .PreloadAllGIFData): return true 88 | 89 | default: return false 90 | } 91 | } 92 | 93 | extension CollectionType where Generator.Element == KingfisherOptionsInfoItem { 94 | func kf_firstMatchIgnoringAssociatedValue(target: Generator.Element) -> Generator.Element? { 95 | return indexOf { $0 <== target }.flatMap { self[$0] } 96 | } 97 | 98 | func kf_removeAllMatchesIgnoringAssociatedValue(target: Generator.Element) -> [Generator.Element] { 99 | return self.filter { !($0 <== target) } 100 | } 101 | } 102 | 103 | extension CollectionType where Generator.Element == KingfisherOptionsInfoItem { 104 | var targetCache: ImageCache? { 105 | if let item = kf_firstMatchIgnoringAssociatedValue(.TargetCache(nil)), 106 | case .TargetCache(let cache) = item 107 | { 108 | return cache 109 | } 110 | return nil 111 | } 112 | 113 | var downloader: ImageDownloader? { 114 | if let item = kf_firstMatchIgnoringAssociatedValue(.Downloader(nil)), 115 | case .Downloader(let downloader) = item 116 | { 117 | return downloader 118 | } 119 | return nil 120 | } 121 | 122 | var transition: ImageTransition { 123 | if let item = kf_firstMatchIgnoringAssociatedValue(.Transition(.None)), 124 | case .Transition(let transition) = item 125 | { 126 | return transition 127 | } 128 | return ImageTransition.None 129 | } 130 | 131 | var downloadPriority: Float { 132 | if let item = kf_firstMatchIgnoringAssociatedValue(.DownloadPriority(0)), 133 | case .DownloadPriority(let priority) = item 134 | { 135 | return priority 136 | } 137 | return NSURLSessionTaskPriorityDefault 138 | } 139 | 140 | var forceRefresh: Bool { 141 | return contains{ $0 <== .ForceRefresh } 142 | } 143 | 144 | var forceTransition: Bool { 145 | return contains{ $0 <== .ForceTransition } 146 | } 147 | 148 | var cacheMemoryOnly: Bool { 149 | return contains{ $0 <== .CacheMemoryOnly } 150 | } 151 | 152 | var backgroundDecode: Bool { 153 | return contains{ $0 <== .BackgroundDecode } 154 | } 155 | 156 | var preloadAllGIFData: Bool { 157 | return contains { $0 <== .PreloadAllGIFData } 158 | } 159 | 160 | var callbackDispatchQueue: dispatch_queue_t { 161 | if let item = kf_firstMatchIgnoringAssociatedValue(.CallbackDispatchQueue(nil)), 162 | case .CallbackDispatchQueue(let queue) = item 163 | { 164 | return queue ?? dispatch_get_main_queue() 165 | } 166 | return dispatch_get_main_queue() 167 | } 168 | 169 | var scaleFactor: CGFloat { 170 | if let item = kf_firstMatchIgnoringAssociatedValue(.ScaleFactor(0)), 171 | case .ScaleFactor(let scale) = item 172 | { 173 | return scale 174 | } 175 | return 1.0 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/Resource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Resource.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | /** 30 | Resource is a simple combination of `downloadURL` and `cacheKey`. 31 | 32 | When passed to image view set methods, Kingfisher will try to download the target 33 | image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache. 34 | */ 35 | public struct Resource { 36 | /// The key used in cache. 37 | public let cacheKey: String 38 | 39 | /// The target image URL. 40 | public let downloadURL: NSURL 41 | 42 | /** 43 | Create a resource. 44 | 45 | - parameter downloadURL: The target image URL. 46 | - parameter cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key. 47 | 48 | - returns: A resource. 49 | */ 50 | public init(downloadURL: NSURL, cacheKey: String? = nil) { 51 | self.downloadURL = downloadURL 52 | self.cacheKey = cacheKey ?? downloadURL.absoluteString 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/String+MD5.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+MD5.swift 3 | // Kingfisher 4 | // 5 | // To date, adding CommonCrypto to a Swift framework is problematic. See: 6 | // http://stackoverflow.com/questions/25248598/importing-commoncrypto-in-a-swift-framework 7 | // We're using a subset and modified version of CryptoSwift as an alternative. 8 | // The following is an altered source version that only includes MD5. The original software can be found at: 9 | // https://github.com/krzyzanowskim/CryptoSwift 10 | // This is the original copyright notice: 11 | 12 | /* 13 | Copyright (C) 2014 Marcin Krzyżanowski 14 | This software is provided 'as-is', without any express or implied warranty. 15 | In no event will the authors be held liable for any damages arising from the use of this software. 16 | Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 17 | - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. 18 | - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 19 | - This notice may not be removed or altered from any source or binary distribution. 20 | */ 21 | 22 | import Foundation 23 | 24 | extension String { 25 | var kf_MD5: String { 26 | if let data = dataUsingEncoding(NSUTF8StringEncoding) { 27 | let MD5Calculator = MD5(Array(UnsafeBufferPointer(start: UnsafePointer(data.bytes), count: data.length))) 28 | let MD5Data = MD5Calculator.calculate() 29 | 30 | let MD5String = NSMutableString() 31 | for c in MD5Data { 32 | MD5String.appendFormat("%02x", c) 33 | } 34 | return MD5String as String 35 | 36 | } else { 37 | return self 38 | } 39 | } 40 | } 41 | 42 | /** array of bytes, little-endian representation */ 43 | func arrayOfBytes(value: T, length: Int? = nil) -> [UInt8] { 44 | let totalBytes = length ?? (sizeofValue(value) * 8) 45 | 46 | let valuePointer = UnsafeMutablePointer.alloc(1) 47 | valuePointer.memory = value 48 | 49 | let bytesPointer = UnsafeMutablePointer(valuePointer) 50 | var bytes = [UInt8](count: totalBytes, repeatedValue: 0) 51 | for j in 0.. [UInt8] { 64 | return arrayOfBytes(self, length: totalBytes) 65 | } 66 | 67 | } 68 | 69 | extension NSMutableData { 70 | 71 | /** Convenient way to append bytes */ 72 | func appendBytes(arrayOfBytes: [UInt8]) { 73 | appendBytes(arrayOfBytes, length: arrayOfBytes.count) 74 | } 75 | 76 | } 77 | 78 | protocol HashProtocol { 79 | var message: Array { get } 80 | 81 | /** Common part for hash calculation. Prepare header data. */ 82 | func prepare(len: Int) -> Array 83 | } 84 | 85 | extension HashProtocol { 86 | 87 | func prepare(len: Int) -> Array { 88 | var tmpMessage = message 89 | 90 | // Step 1. Append Padding Bits 91 | tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message 92 | 93 | // append "0" bit until message length in bits ≡ 448 (mod 512) 94 | var msgLength = tmpMessage.count 95 | var counter = 0 96 | 97 | while msgLength % len != (len - 8) { 98 | counter += 1 99 | msgLength += 1 100 | } 101 | 102 | tmpMessage += Array(count: counter, repeatedValue: 0) 103 | return tmpMessage 104 | } 105 | } 106 | 107 | func toUInt32Array(slice: ArraySlice) -> Array { 108 | var result = Array() 109 | result.reserveCapacity(16) 110 | 111 | for idx in slice.startIndex.stride(to: slice.endIndex, by: sizeof(UInt32)) { 112 | let d0 = UInt32(slice[idx.advancedBy(3)]) << 24 113 | let d1 = UInt32(slice[idx.advancedBy(2)]) << 16 114 | let d2 = UInt32(slice[idx.advancedBy(1)]) << 8 115 | let d3 = UInt32(slice[idx]) 116 | let val: UInt32 = d0 | d1 | d2 | d3 117 | 118 | result.append(val) 119 | } 120 | return result 121 | } 122 | 123 | struct BytesGenerator: GeneratorType { 124 | 125 | let chunkSize: Int 126 | let data: [UInt8] 127 | 128 | init(chunkSize: Int, data: [UInt8]) { 129 | self.chunkSize = chunkSize 130 | self.data = data 131 | } 132 | 133 | var offset = 0 134 | 135 | mutating func next() -> ArraySlice? { 136 | let end = min(chunkSize, data.count - offset) 137 | let result = data[offset.. 0 ? result : nil 140 | } 141 | } 142 | 143 | struct BytesSequence: SequenceType { 144 | let chunkSize: Int 145 | let data: [UInt8] 146 | 147 | func generate() -> BytesGenerator { 148 | return BytesGenerator(chunkSize: chunkSize, data: data) 149 | } 150 | } 151 | 152 | func rotateLeft(value: UInt32, bits: UInt32) -> UInt32 { 153 | return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits)) 154 | } 155 | 156 | class MD5: HashProtocol { 157 | 158 | static let size = 16 // 128 / 8 159 | let message: [UInt8] 160 | 161 | init (_ message: [UInt8]) { 162 | self.message = message 163 | } 164 | 165 | /** specifies the per-round shift amounts */ 166 | private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 167 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 168 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 169 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21] 170 | 171 | /** binary integer part of the sines of integers (Radians) */ 172 | private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 173 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 174 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 175 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 176 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 177 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 178 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 179 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 180 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 181 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 182 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 183 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 184 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 185 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 186 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 187 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391] 188 | 189 | private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] 190 | 191 | func calculate() -> [UInt8] { 192 | var tmpMessage = prepare(64) 193 | tmpMessage.reserveCapacity(tmpMessage.count + 4) 194 | 195 | // hash values 196 | var hh = hashes 197 | 198 | // Step 2. Append Length a 64-bit representation of lengthInBits 199 | let lengthInBits = (message.count * 8) 200 | let lengthBytes = lengthInBits.bytes(64 / 8) 201 | tmpMessage += lengthBytes.reverse() 202 | 203 | // Process the message in successive 512-bit chunks: 204 | let chunkSizeBytes = 512 / 8 // 64 205 | 206 | for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) { 207 | // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15 208 | var M = toUInt32Array(chunk) 209 | assert(M.count == 16, "Invalid array") 210 | 211 | // Initialize hash value for this chunk: 212 | var A: UInt32 = hh[0] 213 | var B: UInt32 = hh[1] 214 | var C: UInt32 = hh[2] 215 | var D: UInt32 = hh[3] 216 | 217 | var dTemp: UInt32 = 0 218 | 219 | // Main loop 220 | for j in 0 ..< sines.count { 221 | var g = 0 222 | var F: UInt32 = 0 223 | 224 | switch j { 225 | case 0...15: 226 | F = (B & C) | ((~B) & D) 227 | g = j 228 | break 229 | case 16...31: 230 | F = (D & B) | (~D & C) 231 | g = (5 * j + 1) % 16 232 | break 233 | case 32...47: 234 | F = B ^ C ^ D 235 | g = (3 * j + 5) % 16 236 | break 237 | case 48...63: 238 | F = C ^ (B | (~D)) 239 | g = (7 * j) % 16 240 | break 241 | default: 242 | break 243 | } 244 | dTemp = D 245 | D = C 246 | C = B 247 | B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j]) 248 | A = dTemp 249 | } 250 | 251 | hh[0] = hh[0] &+ A 252 | hh[1] = hh[1] &+ B 253 | hh[2] = hh[2] &+ C 254 | hh[3] = hh[3] &+ D 255 | } 256 | 257 | var result = [UInt8]() 258 | result.reserveCapacity(hh.count / 4) 259 | 260 | hh.forEach { 261 | let itemLE = $0.littleEndian 262 | result += [UInt8(itemLE & 0xff), UInt8((itemLE >> 8) & 0xff), UInt8((itemLE >> 16) & 0xff), UInt8((itemLE >> 24) & 0xff)] 263 | } 264 | return result 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/ThreadHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThreadHelper.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/10/9. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | func dispatch_async_safely_to_main_queue(block: ()->()) { 30 | dispatch_async_safely_to_queue(dispatch_get_main_queue(), block) 31 | } 32 | 33 | // This method will dispatch the `block` to a specified `queue`. 34 | // If the `queue` is the main queue, and current thread is main thread, the block 35 | // will be invoked immediately instead of being dispatched. 36 | func dispatch_async_safely_to_queue(queue: dispatch_queue_t, _ block: ()->()) { 37 | if queue === dispatch_get_main_queue() && NSThread.isMainThread() { 38 | block() 39 | } else { 40 | dispatch_async(queue) { 41 | block() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/Pods/Kingfisher/Sources/UIButton+Kingfisher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Kingfisher.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/13. 6 | // 7 | // Copyright (c) 2016 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import UIKit 28 | 29 | /** 30 | * Set image to use from web for a specified state. 31 | */ 32 | extension UIButton { 33 | /** 34 | Set an image to use for a specified state with a URL, a placeholder image, options, progress handler and completion handler. 35 | 36 | - parameter URL: The URL of image for specified state. 37 | - parameter state: The state that uses the specified image. 38 | - parameter placeholderImage: A placeholder image when retrieving the image at URL. 39 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 40 | - parameter progressBlock: Called when the image downloading progress gets updated. 41 | - parameter completionHandler: Called when the image retrieved and set. 42 | 43 | - returns: A task represents the retrieving process. 44 | 45 | - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 46 | The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method. 47 | */ 48 | public func kf_setImageWithURL(URL: NSURL?, 49 | forState state: UIControlState, 50 | placeholderImage: UIImage? = nil, 51 | optionsInfo: KingfisherOptionsInfo? = nil, 52 | progressBlock: DownloadProgressBlock? = nil, 53 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask 54 | { 55 | let resource = URL.map { Resource(downloadURL: $0) } 56 | return kf_setImageWithResource(resource, 57 | forState: state, 58 | placeholderImage: placeholderImage, 59 | optionsInfo: optionsInfo, 60 | progressBlock: progressBlock, 61 | completionHandler: completionHandler) 62 | } 63 | 64 | 65 | /** 66 | Set an image to use for a specified state with a resource, a placeholder image, options, progress handler and completion handler. 67 | 68 | - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`. 69 | - parameter state: The state that uses the specified image. 70 | - parameter placeholderImage: A placeholder image when retrieving the image at URL. 71 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 72 | - parameter progressBlock: Called when the image downloading progress gets updated. 73 | - parameter completionHandler: Called when the image retrieved and set. 74 | 75 | - returns: A task represents the retrieving process. 76 | 77 | - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 78 | The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method. 79 | */ 80 | public func kf_setImageWithResource(resource: Resource?, 81 | forState state: UIControlState, 82 | placeholderImage: UIImage? = nil, 83 | optionsInfo: KingfisherOptionsInfo? = nil, 84 | progressBlock: DownloadProgressBlock? = nil, 85 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask 86 | { 87 | setImage(placeholderImage, forState: state) 88 | 89 | guard let resource = resource else { 90 | completionHandler?(image: nil, error: nil, cacheType: .None, imageURL: nil) 91 | return RetrieveImageTask.emptyTask 92 | } 93 | 94 | kf_setWebURL(resource.downloadURL, forState: state) 95 | let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo, 96 | progressBlock: { receivedSize, totalSize in 97 | if let progressBlock = progressBlock { 98 | progressBlock(receivedSize: receivedSize, totalSize: totalSize) 99 | } 100 | }, 101 | completionHandler: {[weak self] image, error, cacheType, imageURL in 102 | dispatch_async_safely_to_main_queue { 103 | guard let sSelf = self where imageURL == sSelf.kf_webURLForState(state) else { 104 | return 105 | } 106 | 107 | sSelf.kf_setImageTask(nil) 108 | 109 | if image != nil { 110 | sSelf.setImage(image, forState: state) 111 | } 112 | 113 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 114 | } 115 | }) 116 | 117 | kf_setImageTask(task) 118 | return task 119 | } 120 | } 121 | 122 | private var lastURLKey: Void? 123 | private var imageTaskKey: Void? 124 | 125 | // MARK: - Runtime for UIButton image 126 | extension UIButton { 127 | /** 128 | Get the image URL binded to this button for a specified state. 129 | 130 | - parameter state: The state that uses the specified image. 131 | 132 | - returns: Current URL for image. 133 | */ 134 | public func kf_webURLForState(state: UIControlState) -> NSURL? { 135 | return kf_webURLs[NSNumber(unsignedLong:state.rawValue)] as? NSURL 136 | } 137 | 138 | private func kf_setWebURL(URL: NSURL, forState state: UIControlState) { 139 | kf_webURLs[NSNumber(unsignedLong:state.rawValue)] = URL 140 | } 141 | 142 | private var kf_webURLs: NSMutableDictionary { 143 | var dictionary = objc_getAssociatedObject(self, &lastURLKey) as? NSMutableDictionary 144 | if dictionary == nil { 145 | dictionary = NSMutableDictionary() 146 | kf_setWebURLs(dictionary!) 147 | } 148 | return dictionary! 149 | } 150 | 151 | private func kf_setWebURLs(URLs: NSMutableDictionary) { 152 | objc_setAssociatedObject(self, &lastURLKey, URLs, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 153 | } 154 | 155 | private var kf_imageTask: RetrieveImageTask? { 156 | return objc_getAssociatedObject(self, &imageTaskKey) as? RetrieveImageTask 157 | } 158 | 159 | private func kf_setImageTask(task: RetrieveImageTask?) { 160 | objc_setAssociatedObject(self, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 161 | } 162 | } 163 | 164 | /** 165 | * Set background image to use from web for a specified state. 166 | */ 167 | extension UIButton { 168 | /** 169 | Set the background image to use for a specified state with a URL, 170 | a placeholder image, options progress handler and completion handler. 171 | 172 | - parameter URL: The URL of image for specified state. 173 | - parameter state: The state that uses the specified image. 174 | - parameter placeholderImage: A placeholder image when retrieving the image at URL. 175 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 176 | - parameter progressBlock: Called when the image downloading progress gets updated. 177 | - parameter completionHandler: Called when the image retrieved and set. 178 | 179 | - returns: A task represents the retrieving process. 180 | 181 | - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 182 | The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method. 183 | */ 184 | public func kf_setBackgroundImageWithURL(URL: NSURL?, 185 | forState state: UIControlState, 186 | placeholderImage: UIImage? = nil, 187 | optionsInfo: KingfisherOptionsInfo? = nil, 188 | progressBlock: DownloadProgressBlock? = nil, 189 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask 190 | { 191 | let resource = URL.map { Resource(downloadURL: $0) } 192 | return kf_setBackgroundImageWithResource(resource, 193 | forState: state, 194 | placeholderImage: placeholderImage, 195 | optionsInfo: optionsInfo, 196 | progressBlock: progressBlock, 197 | completionHandler: completionHandler) 198 | } 199 | 200 | 201 | /** 202 | Set the background image to use for a specified state with a resource, 203 | a placeholder image, options progress handler and completion handler. 204 | 205 | - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`. 206 | - parameter state: The state that uses the specified image. 207 | - parameter placeholderImage: A placeholder image when retrieving the image at URL. 208 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 209 | - parameter progressBlock: Called when the image downloading progress gets updated. 210 | - parameter completionHandler: Called when the image retrieved and set. 211 | 212 | - returns: A task represents the retrieving process. 213 | 214 | - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 215 | The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method. 216 | */ 217 | public func kf_setBackgroundImageWithResource(resource: Resource?, 218 | forState state: UIControlState, 219 | placeholderImage: UIImage? = nil, 220 | optionsInfo: KingfisherOptionsInfo? = nil, 221 | progressBlock: DownloadProgressBlock? = nil, 222 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask 223 | { 224 | setBackgroundImage(placeholderImage, forState: state) 225 | 226 | guard let resource = resource else { 227 | completionHandler?(image: nil, error: nil, cacheType: .None, imageURL: nil) 228 | return RetrieveImageTask.emptyTask 229 | } 230 | 231 | kf_setBackgroundWebURL(resource.downloadURL, forState: state) 232 | let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo, 233 | progressBlock: { receivedSize, totalSize in 234 | if let progressBlock = progressBlock { 235 | progressBlock(receivedSize: receivedSize, totalSize: totalSize) 236 | } 237 | }, 238 | completionHandler: { [weak self] image, error, cacheType, imageURL in 239 | dispatch_async_safely_to_main_queue { 240 | guard let sSelf = self where imageURL == sSelf.kf_backgroundWebURLForState(state) else { 241 | return 242 | } 243 | 244 | sSelf.kf_setBackgroundImageTask(nil) 245 | 246 | if image != nil { 247 | sSelf.setBackgroundImage(image, forState: state) 248 | } 249 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 250 | } 251 | }) 252 | 253 | kf_setBackgroundImageTask(task) 254 | return task 255 | } 256 | } 257 | 258 | private var lastBackgroundURLKey: Void? 259 | private var backgroundImageTaskKey: Void? 260 | 261 | // MARK: - Runtime for UIButton background image 262 | extension UIButton { 263 | /** 264 | Get the background image URL binded to this button for a specified state. 265 | 266 | - parameter state: The state that uses the specified background image. 267 | 268 | - returns: Current URL for background image. 269 | */ 270 | public func kf_backgroundWebURLForState(state: UIControlState) -> NSURL? { 271 | return kf_backgroundWebURLs[NSNumber(unsignedLong:state.rawValue)] as? NSURL 272 | } 273 | 274 | private func kf_setBackgroundWebURL(URL: NSURL, forState state: UIControlState) { 275 | kf_backgroundWebURLs[NSNumber(unsignedLong:state.rawValue)] = URL 276 | } 277 | 278 | private var kf_backgroundWebURLs: NSMutableDictionary { 279 | var dictionary = objc_getAssociatedObject(self, &lastBackgroundURLKey) as? NSMutableDictionary 280 | if dictionary == nil { 281 | dictionary = NSMutableDictionary() 282 | kf_setBackgroundWebURLs(dictionary!) 283 | } 284 | return dictionary! 285 | } 286 | 287 | private func kf_setBackgroundWebURLs(URLs: NSMutableDictionary) { 288 | objc_setAssociatedObject(self, &lastBackgroundURLKey, URLs, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 289 | } 290 | 291 | private var kf_backgroundImageTask: RetrieveImageTask? { 292 | return objc_getAssociatedObject(self, &backgroundImageTaskKey) as? RetrieveImageTask 293 | } 294 | 295 | private func kf_setBackgroundImageTask(task: RetrieveImageTask?) { 296 | objc_setAssociatedObject(self, &backgroundImageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 297 | } 298 | } 299 | 300 | // MARK: - Cancel image download tasks. 301 | extension UIButton { 302 | /** 303 | Cancel the image download task bounded to the image view if it is running. 304 | Nothing will happen if the downloading has already finished. 305 | */ 306 | public func kf_cancelImageDownloadTask() { 307 | kf_imageTask?.downloadTask?.cancel() 308 | } 309 | 310 | /** 311 | Cancel the background image download task bounded to the image view if it is running. 312 | Nothing will happen if the downloading has already finished. 313 | */ 314 | public func kf_cancelBackgroundImageDownloadTask() { 315 | kf_backgroundImageTask?.downloadTask?.cancel() 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/ParallexBanner.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ParallexBanner", 3 | "version": "0.1.0", 4 | "summary": "A short description of ParallexBanner.", 5 | "description": "TODO: Add long description of the pod here.", 6 | "homepage": "https://github.com//ParallexBanner", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "leo": "leomobiledeveloper@gmail.com" 13 | }, 14 | "source": { 15 | "git": "https://github.com//ParallexBanner.git", 16 | "tag": "0.1.0" 17 | }, 18 | "platforms": { 19 | "ios": "8.0" 20 | }, 21 | "source_files": "ParallexBanner/Classes/**/*", 22 | "frameworks": "UIKit", 23 | "dependencies": { 24 | "Kingfisher": [ 25 | 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Kingfisher (2.4.2) 3 | - ParallexBanner (0.1.0): 4 | - Kingfisher 5 | 6 | DEPENDENCIES: 7 | - ParallexBanner (from `../`) 8 | 9 | EXTERNAL SOURCES: 10 | ParallexBanner: 11 | :path: ../ 12 | 13 | SPEC CHECKSUMS: 14 | Kingfisher: 05bf8d04408aaa70fcd2c8c81b9f1d0f1ad313dd 15 | ParallexBanner: 7b39e5272f666fe9de0787b6c88dc888a66cc676 16 | 17 | PODFILE CHECKSUM: ac66f4d9cef0a35075dffaf388befd760a1173d5 18 | 19 | COCOAPODS: 1.0.0 20 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Kingfisher/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 | 2.4.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Kingfisher : NSObject 3 | @end 4 | @implementation PodsDummy_Kingfisher 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Kingfisher/Kingfisher-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "Kingfisher.h" 4 | 5 | FOUNDATION_EXPORT double KingfisherVersionNumber; 6 | FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; 7 | 8 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Kingfisher/Kingfisher.modulemap: -------------------------------------------------------------------------------- 1 | framework module Kingfisher { 2 | umbrella header "Kingfisher-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Kingfisher/Kingfisher.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Kingfisher 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_LDFLAGS = -framework "CFNetwork" 5 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 6 | PODS_BUILD_DIR = $BUILD_DIR 7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/ParallexBanner/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 | 0.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/ParallexBanner/ParallexBanner-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_ParallexBanner : NSObject 3 | @end 4 | @implementation PodsDummy_ParallexBanner 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/ParallexBanner/ParallexBanner-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/ParallexBanner/ParallexBanner-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double ParallexBannerVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char ParallexBannerVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/ParallexBanner/ParallexBanner.modulemap: -------------------------------------------------------------------------------- 1 | framework module ParallexBanner { 2 | umbrella header "ParallexBanner-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/ParallexBanner/ParallexBanner.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/ParallexBanner 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 5 | OTHER_LDFLAGS = -framework "UIKit" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT} 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/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.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Kingfisher 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2015 Wei Wang 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | 29 | 30 | ## ParallexBanner 31 | 32 | Copyright (c) 2016 leo 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in 42 | all copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 50 | THE SOFTWARE. 51 | 52 | Generated by CocoaPods - https://cocoapods.org 53 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2015 Wei Wang 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | 40 | Title 41 | Kingfisher 42 | Type 43 | PSGroupSpecifier 44 | 45 | 46 | FooterText 47 | Copyright (c) 2016 leo <leomobiledeveloper@gmail.com> 48 | 49 | Permission is hereby granted, free of charge, to any person obtaining a copy 50 | of this software and associated documentation files (the "Software"), to deal 51 | in the Software without restriction, including without limitation the rights 52 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 53 | copies of the Software, and to permit persons to whom the Software is 54 | furnished to do so, subject to the following conditions: 55 | 56 | The above copyright notice and this permission notice shall be included in 57 | all copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 60 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 61 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 62 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 63 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 64 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 65 | THE SOFTWARE. 66 | 67 | Title 68 | ParallexBanner 69 | Type 70 | PSGroupSpecifier 71 | 72 | 73 | FooterText 74 | Generated by CocoaPods - https://cocoapods.org 75 | Title 76 | 77 | Type 78 | PSGroupSpecifier 79 | 80 | 81 | StringsTable 82 | Acknowledgements 83 | Title 84 | Acknowledgements 85 | 86 | 87 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_ParallexBanner_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_ParallexBanner_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\"" 63 | /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1" 64 | fi 65 | } 66 | 67 | # Strip invalid architectures 68 | strip_invalid_archs() { 69 | binary="$1" 70 | # Get architectures for current file 71 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 72 | stripped="" 73 | for arch in $archs; do 74 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 75 | # Strip non-valid architectures in-place 76 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 77 | stripped="$stripped $arch" 78 | fi 79 | done 80 | if [[ "$stripped" ]]; then 81 | echo "Stripped $binary of architectures:$stripped" 82 | fi 83 | } 84 | 85 | 86 | if [[ "$CONFIGURATION" == "Debug" ]]; then 87 | install_framework "$BUILT_PRODUCTS_DIR/Kingfisher/Kingfisher.framework" 88 | install_framework "$BUILT_PRODUCTS_DIR/ParallexBanner/ParallexBanner.framework" 89 | fi 90 | if [[ "$CONFIGURATION" == "Release" ]]; then 91 | install_framework "$BUILT_PRODUCTS_DIR/Kingfisher/Kingfisher.framework" 92 | install_framework "$BUILT_PRODUCTS_DIR/ParallexBanner/ParallexBanner.framework" 93 | fi 94 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | *) 22 | TARGET_DEVICE_ARGS="--target-device mac" 23 | ;; 24 | esac 25 | 26 | realpath() { 27 | DIRECTORY="$(cd "${1%/*}" && pwd)" 28 | FILENAME="${1##*/}" 29 | echo "$DIRECTORY/$FILENAME" 30 | } 31 | 32 | install_resource() 33 | { 34 | if [[ "$1" = /* ]] ; then 35 | RESOURCE_PATH="$1" 36 | else 37 | RESOURCE_PATH="${PODS_ROOT}/$1" 38 | fi 39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 40 | cat << EOM 41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 42 | EOM 43 | exit 1 44 | fi 45 | case $RESOURCE_PATH in 46 | *.storyboard) 47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 49 | ;; 50 | *.xib) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT}" 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" 53 | ;; 54 | *.framework) 55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 59 | ;; 60 | *.xcdatamodel) 61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 63 | ;; 64 | *.xcdatamodeld) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 67 | ;; 68 | *.xcmappingmodel) 69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 71 | ;; 72 | *.xcassets) 73 | ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH") 74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 75 | ;; 76 | *) 77 | echo "$RESOURCE_PATH" 78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 79 | ;; 80 | esac 81 | } 82 | 83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | fi 89 | rm -f "$RESOURCES_TO_COPY" 90 | 91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 92 | then 93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 95 | while read line; do 96 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then 97 | XCASSET_FILES+=("$line") 98 | fi 99 | done <<<"$OTHER_XCASSETS" 100 | 101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 102 | fi 103 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double Pods_ParallexBanner_ExampleVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char Pods_ParallexBanner_ExampleVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner/ParallexBanner.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Kingfisher" -framework "ParallexBanner" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_ParallexBanner_Example { 2 | umbrella header "Pods-ParallexBanner_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Example/Pods-ParallexBanner_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner/ParallexBanner.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Kingfisher" -framework "ParallexBanner" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/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.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_ParallexBanner_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_ParallexBanner_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\"" 63 | /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1" 64 | fi 65 | } 66 | 67 | # Strip invalid architectures 68 | strip_invalid_archs() { 69 | binary="$1" 70 | # Get architectures for current file 71 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 72 | stripped="" 73 | for arch in $archs; do 74 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 75 | # Strip non-valid architectures in-place 76 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 77 | stripped="$stripped $arch" 78 | fi 79 | done 80 | if [[ "$stripped" ]]; then 81 | echo "Stripped $binary of architectures:$stripped" 82 | fi 83 | } 84 | 85 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | *) 22 | TARGET_DEVICE_ARGS="--target-device mac" 23 | ;; 24 | esac 25 | 26 | realpath() { 27 | DIRECTORY="$(cd "${1%/*}" && pwd)" 28 | FILENAME="${1##*/}" 29 | echo "$DIRECTORY/$FILENAME" 30 | } 31 | 32 | install_resource() 33 | { 34 | if [[ "$1" = /* ]] ; then 35 | RESOURCE_PATH="$1" 36 | else 37 | RESOURCE_PATH="${PODS_ROOT}/$1" 38 | fi 39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 40 | cat << EOM 41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 42 | EOM 43 | exit 1 44 | fi 45 | case $RESOURCE_PATH in 46 | *.storyboard) 47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 49 | ;; 50 | *.xib) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT}" 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" 53 | ;; 54 | *.framework) 55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 59 | ;; 60 | *.xcdatamodel) 61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 63 | ;; 64 | *.xcdatamodeld) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 67 | ;; 68 | *.xcmappingmodel) 69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 71 | ;; 72 | *.xcassets) 73 | ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH") 74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 75 | ;; 76 | *) 77 | echo "$RESOURCE_PATH" 78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 79 | ;; 80 | esac 81 | } 82 | 83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | fi 89 | rm -f "$RESOURCES_TO_COPY" 90 | 91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 92 | then 93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 95 | while read line; do 96 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then 97 | XCASSET_FILES+=("$line") 98 | fi 99 | done <<<"$OTHER_XCASSETS" 100 | 101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 102 | fi 103 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double Pods_ParallexBanner_TestsVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char Pods_ParallexBanner_TestsVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner/ParallexBanner.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_ParallexBanner_Tests { 2 | umbrella header "Pods-ParallexBanner_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-ParallexBanner_Tests/Pods-ParallexBanner_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ParallexBanner/ParallexBanner.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 leo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /ParallexBanner.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint ParallexBanner.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'ParallexBanner' 11 | s.version = '0.1.0' 12 | s.summary = 'A banner to show images with parallex effect' 13 | s.description = <<-DESC 14 | This is a banner to show images with parallex effect.It support both local and Web Image. 15 | DESC 16 | 17 | s.homepage = 'https://github.com/LeoMobileDeveloper/ParallexBanner' 18 | s.license = { :type => 'MIT', :file => 'LICENSE' } 19 | s.author = { 'leo' => 'leomobiledeveloper@gmail.com' } 20 | s.source = { :git => 'https://github.com/LeoMobileDeveloper/ParallexBanner.git', :tag => s.version.to_s } 21 | s.ios.deployment_target = '8.0' 22 | s.source_files = 'ParallexBanner/Classes/**/*' 23 | s.frameworks = 'UIKit' 24 | s.dependency 'Kingfisher' 25 | end 26 | -------------------------------------------------------------------------------- /ParallexBanner/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoMobileDeveloper/ParallexBanner/ae2e48c8c94165ab4157ab8c7f3329e266ffd7ac/ParallexBanner/Assets/.gitkeep -------------------------------------------------------------------------------- /ParallexBanner/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoMobileDeveloper/ParallexBanner/ae2e48c8c94165ab4157ab8c7f3329e266ffd7ac/ParallexBanner/Classes/.gitkeep -------------------------------------------------------------------------------- /ParallexBanner/Classes/ParallexBanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParallexBanner.swift 3 | // Pods 4 | // 5 | // Created by huangwenchen on 16/7/23. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Kingfisher 12 | 13 | public enum ParallexBannerTransition{ 14 | case Normal 15 | case Parallex 16 | //Maybe I will add some transition in the future 17 | } 18 | @objc public protocol ParallexBannerDelegate { 19 | optional func banner(banner:ParallexBanner,didClickAtIndex index:NSInteger) 20 | optional func banner(banner:ParallexBanner,didScrollToIndex index:NSInteger) 21 | } 22 | 23 | @objc public protocol ParallexBannerDataSource{ 24 | func numberOfBannersIn(bannner:ParallexBanner)->NSInteger 25 | func banner(banner:ParallexBanner,urlOrImageAtIndex index:NSInteger)->AnyObject 26 | optional func banner(banner:ParallexBanner,placeHolderForIndex index:NSInteger)->UIImage? 27 | optional func banner(banner:ParallexBanner,contentModeAtIndex index:NSInteger)->UIViewContentMode 28 | } 29 | 30 | public class ParallexBanner: UIView { 31 | // MARK: - Propertys - 32 | public weak var dataSource:ParallexBannerDataSource?{ 33 | didSet{ 34 | reloadData() 35 | } 36 | } 37 | public weak var delegate:ParallexBannerDelegate? 38 | /// The transitionMode when scroll,default is .Parallex 39 | public var transitionMode:ParallexBannerTransition = ParallexBannerTransition.Parallex 40 | /// Weather to enable timer based scroll 41 | public var autoScroll:Bool = true { 42 | didSet{ 43 | if autoScroll{ 44 | restartTimerIfNeeded() 45 | }else{ 46 | stopTimerIfNecessory() 47 | } 48 | } 49 | } 50 | /// Weather to enable scroll if there is only one page 51 | public var enableScrollForSinglePage = false 52 | /// The speed of parallex scroll,it should between 0.1 to 0.8 53 | public var parllexSpeed:CGFloat = 0.4 54 | /// The duration between an auto scroll fire 55 | public var autoScrollTimeInterval:NSTimeInterval = 3.0 { 56 | didSet{ 57 | stopTimerIfNecessory() 58 | restartTimerIfNeeded() 59 | } 60 | } 61 | /// The page Control 62 | public let pageControl:UIPageControl = UIPageControl() 63 | /// The current index of page 64 | public var currentIndex:Int{ 65 | get{ 66 | if _currentIndex == 0 { 67 | return self.dataSource!.numberOfBannersIn(self) + 2 - 3 68 | }else if(_currentIndex == self.dataSource!.numberOfBannersIn(self) + 2 - 1){ 69 | return 0; 70 | }else{ 71 | return _currentIndex - 1 72 | } 73 | } 74 | }; 75 | 76 | private var _currentIndex = 1 77 | private var collectionView:UICollectionView! 78 | private var timer:NSTimer? 79 | private var flowLayout:UICollectionViewFlowLayout! 80 | 81 | // MARK: - Init - 82 | override public init(frame: CGRect) { 83 | super.init(frame: frame) 84 | commonInit() 85 | } 86 | public required init?(coder aDecoder: NSCoder) { 87 | super.init(coder: aDecoder) 88 | commonInit() 89 | } 90 | func commonInit(){ 91 | flowLayout = UICollectionViewFlowLayout() 92 | flowLayout.scrollDirection = .Horizontal 93 | flowLayout.minimumLineSpacing = 0.0 94 | collectionView = UICollectionView(frame:CGRectZero, collectionViewLayout: flowLayout) 95 | collectionView.dataSource = self 96 | collectionView.delegate = self 97 | collectionView.showsHorizontalScrollIndicator = false 98 | collectionView.showsVerticalScrollIndicator = false 99 | collectionView.pagingEnabled = true 100 | collectionView.backgroundColor = UIColor(white: 0.0, alpha: 0.1) 101 | pageControl.pageIndicatorTintColor = UIColor.whiteColor() 102 | pageControl.currentPageIndicatorTintColor = UIColor.darkGrayColor() 103 | pageControl.hidesForSinglePage = true 104 | pageControl.userInteractionEnabled = false 105 | addSubview(collectionView) 106 | addSubview(pageControl) 107 | collectionView.registerClass(BannerCell.self, forCellWithReuseIdentifier: "cell") 108 | } 109 | // MARK: - Layout - 110 | public override func layoutSubviews() { 111 | super.layoutSubviews() 112 | flowLayout.itemSize = self.frame.size 113 | if let ds = self.dataSource{ 114 | pageControl.numberOfPages = ds.numberOfBannersIn(self) 115 | }else{ 116 | pageControl.numberOfPages = 0 117 | } 118 | pageControl.sizeToFit() 119 | collectionView.frame = self.bounds 120 | pageControl.center = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds) - CGRectGetHeight(pageControl.bounds)/2) 121 | let originIndexPath = NSIndexPath(forItem: _currentIndex, inSection: 0) 122 | if let ds = dataSource{ 123 | let targetCount = ds.numberOfBannersIn(self); 124 | let needAdjust = targetCount > 1 || (targetCount == 1 && enableScrollForSinglePage) 125 | if needAdjust{ 126 | collectionView.scrollToItemAtIndexPath(originIndexPath, atScrollPosition: .None, animated: false) 127 | } 128 | } 129 | } 130 | // MARK: - API - 131 | public func reloadData(){ 132 | _currentIndex = 1; 133 | collectionView.reloadData() 134 | stopTimerIfNecessory() 135 | if let ds = self.dataSource{ 136 | let targetCount = ds.numberOfBannersIn(self); 137 | pageControl.numberOfPages = targetCount 138 | pageControl.currentPage = 0 139 | restartTimerIfNeeded() 140 | }else{ 141 | pageControl.numberOfPages = 0 142 | } 143 | setNeedsLayout() 144 | } 145 | // MARK: - Life Circle - 146 | public override func willMoveToSuperview(newSuperview: UIView?) { 147 | if newSuperview == nil{ 148 | stopTimerIfNecessory() 149 | } 150 | super.willMoveToSuperview(newSuperview) 151 | } 152 | deinit{ 153 | stopTimerIfNecessory() 154 | } 155 | // MARK: - Private - 156 | private func restartTimerIfNeeded(){ 157 | stopTimerIfNecessory() 158 | if autoScroll == false { 159 | return 160 | } 161 | let count = self.dataSource!.numberOfBannersIn(self) 162 | guard count != 0 else{ 163 | return 164 | } 165 | if count == 1 && enableScrollForSinglePage == false{ 166 | return 167 | } 168 | self.timer = NSTimer(timeInterval: self.autoScrollTimeInterval, target: self, selector: #selector(ParallexBanner.scrollToNext), userInfo: nil, repeats: true) 169 | NSRunLoop.mainRunLoop().addTimer(self.timer!, forMode: NSRunLoopCommonModes) 170 | } 171 | private func stopTimerIfNecessory(){ 172 | if self.timer != nil{ 173 | self.timer?.invalidate() 174 | self.timer = nil 175 | } 176 | } 177 | @objc private func scrollToNext(){ 178 | _currentIndex = (_currentIndex + 1) % (self.dataSource!.numberOfBannersIn(self) + 2); 179 | let nextIndx = NSIndexPath(forItem: _currentIndex, inSection: 0) 180 | collectionView.scrollToItemAtIndexPath(nextIndx, atScrollPosition: UICollectionViewScrollPosition.None, animated: true) 181 | } 182 | } 183 | 184 | //Handle dataSource/Delegate and scrollViewScroll 185 | extension ParallexBanner:UICollectionViewDataSource,UICollectionViewDelegate{ 186 | public func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 187 | return 1 188 | } 189 | public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 190 | guard self.dataSource != nil else{ 191 | return 0 192 | } 193 | let count = self.dataSource!.numberOfBannersIn(self); 194 | guard count != 0 else{ 195 | return 0 196 | } 197 | if count == 1 && enableScrollForSinglePage == false{ 198 | return count; 199 | } 200 | return count + 2; 201 | } 202 | public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 203 | assert(dataSource != nil) 204 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! BannerCell 205 | let index = indexPath.item; 206 | var adjustIndex = index 207 | if index == 0{ 208 | adjustIndex = self.dataSource!.numberOfBannersIn(self) + 2 - 3 209 | }else if index == self.dataSource!.numberOfBannersIn(self) + 2 - 1 { 210 | adjustIndex = 0; 211 | }else{ 212 | adjustIndex = index - 1 213 | } 214 | let imageOrURL = self.dataSource!.banner(self, urlOrImageAtIndex: adjustIndex) 215 | let placeHolder = self.dataSource!.banner?(self, placeHolderForIndex: adjustIndex) 216 | let contentMode = self.dataSource!.banner?(self, contentModeAtIndex: adjustIndex) 217 | if let mode = contentMode{ 218 | cell.imageView.contentMode = mode 219 | } 220 | if let url = imageOrURL as? String{ 221 | let url = NSURL(string: url) 222 | cell.imageView.kf_setImageWithURL(url, placeholderImage: placeHolder, optionsInfo: nil, progressBlock: nil, completionHandler: nil) 223 | } 224 | if let image = imageOrURL as? UIImage{ 225 | cell.imageView.image = image 226 | } 227 | handleEffect(cell) 228 | return cell 229 | } 230 | public func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 231 | collectionView.deselectItemAtIndexPath(indexPath, animated: true) 232 | self.delegate?.banner?(self, didClickAtIndex: currentIndex) 233 | } 234 | public func scrollViewWillBeginDragging(scrollView: UIScrollView) { 235 | guard autoScroll else{ 236 | return; 237 | } 238 | stopTimerIfNecessory() 239 | } 240 | public func scrollViewDidEndDecelerating(scrollView: UIScrollView) { 241 | guard autoScroll else{ 242 | return; 243 | } 244 | restartTimerIfNeeded() 245 | } 246 | public func scrollViewDidScroll(scrollView: UIScrollView) { 247 | var offSetX = scrollView.contentOffset.x 248 | let width = CGRectGetWidth(scrollView.bounds) 249 | guard width != 0 else{ 250 | return 251 | } 252 | if offSetX >= width * CGFloat(self.dataSource!.numberOfBannersIn(self) + 2 - 1){ 253 | offSetX = width; 254 | scrollView.contentOffset = CGPointMake(offSetX,0); 255 | }else if(offSetX < 0 ){ 256 | offSetX = width * CGFloat(self.dataSource!.numberOfBannersIn(self) + 2 - 2); 257 | scrollView.contentOffset = CGPointMake(offSetX,0); 258 | } 259 | _currentIndex = Int((offSetX + width / 2.0) / width); 260 | if pageControl.currentPage != currentIndex{ 261 | pageControl.currentPage = currentIndex 262 | self.delegate?.banner?(self, didScrollToIndex: currentIndex) 263 | 264 | } 265 | //Calculate offset for visiable cell 266 | collectionView.visibleCells().forEach { (cell) in 267 | if let bannerCell = cell as? BannerCell{ 268 | handleEffect(bannerCell) 269 | } 270 | } 271 | } 272 | private func handleEffect(cell:BannerCell){ 273 | switch transitionMode { 274 | case .Parallex: 275 | let minusX = self.collectionView.contentOffset.x - cell.frame.origin.x 276 | let imageOffsetX = -minusX * parllexSpeed; 277 | cell.scrollView.contentOffset = CGPointMake(imageOffsetX, 0) 278 | default: 279 | break 280 | } 281 | } 282 | } 283 | 284 | public class BannerCell:UICollectionViewCell{ 285 | let imageView = UIImageView() 286 | let scrollView = UIScrollView() 287 | override init(frame: CGRect) { 288 | super.init(frame: frame) 289 | commonInit() 290 | } 291 | private func commonInit(){ 292 | contentView.addSubview(scrollView) 293 | scrollView.scrollEnabled = false 294 | scrollView.userInteractionEnabled = false 295 | scrollView.addSubview(imageView) 296 | imageView.contentMode = UIViewContentMode.ScaleAspectFill; 297 | } 298 | required public init?(coder aDecoder: NSCoder) { 299 | fatalError("init(coder:) has not been implemented") 300 | } 301 | override public func layoutSubviews() { 302 | super.layoutSubviews() 303 | scrollView.contentSize = self.bounds.size; 304 | scrollView.frame = self.bounds 305 | imageView.frame = scrollView.bounds 306 | } 307 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ParallexBanner 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/ParallexBanner.svg?style=flat)](http://cocoapods.org/pods/ParallexBanner) [![Platform](http://img.shields.io/badge/platform-ios-blue.svg?style=flat 4 | )](https://developer.apple.com/iphone/index.action) 5 | [![Language](http://img.shields.io/badge/language-swift-brightgreen.svg?style=flat 6 | )](https://developer.apple.com/swift) 7 | [![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat 8 | )](http://mit-license.org) 9 | 10 | ParallexBanner is a banner with parallex scroll effect written by pure Swift. 11 | 12 | 13 | 14 | ## Features 15 | 16 | - [x] Storyboard and pure code layout 17 | - [x] Auto scroll 18 | - [x] Infinite scroll 19 | - [x] WebImage and local image (It use Kingfisher to load web image) 20 | - [x] Placeholder image 21 | 22 | ## Requirements 23 | 24 | - iOS 8 25 | - ARC 26 | 27 | ## Install 28 | 29 | Using CocoaPod 30 | 31 | ``` 32 | pod "ParallexBanner" 33 | ``` 34 | 35 | ## Getting start 36 | 37 | ParallexBanner use dataSource and delegate as main interface 38 | 39 | Data Source 40 | 41 | ``` 42 | //Number of banners 43 | func numberOfBannersIn(_ bannner:ParallexBanner)->NSInteger 44 | //You can return a ImageURL(String) or a UIImage object here 45 | func banner(_ banner:ParallexBanner,urlOrImageAtIndex index:NSInteger)->AnyObject 46 | //Return the place holder image here 47 | optional func banner(_ banner:ParallexBanner,placeHolderForIndex index:NSInteger)->UIImage? 48 | //Return the image contentMode here 49 | optional func banner(_ banner:ParallexBanner,contentModeAtIndex index:NSInteger)->UIViewContentMode 50 | 51 | ``` 52 | 53 | Delegate 54 | 55 | ``` 56 | //Click at index 57 | optional func banner(_ banner:ParallexBanner,didClickAtIndex index:NSInteger) 58 | //Scroll to index 59 | optional func banner(_ banner:ParallexBanner,didScrollToIndex index:NSInteger) 60 | ``` 61 | 62 | !!! Do not forget to call `banner.reloadData()` when model changed. 63 | 64 | ##Propertys 65 | 66 | - `autoScroll` enable timer based scroll 67 | - `autoScrollTimeInterval` scroll interval 68 | - `enableScrollForSinglePage` enable scroll if there is only single page 69 | - `parllexSpeed` the speed of parallex scroll.Better to between 0.1 and 0.8 70 | - `currentIndex` current page index.Readonly 71 | - `pageControl` the pageControl object 72 | - `transitionMode` set it to .Normal if you do not want parallex scroll. 73 | 74 | 75 | ## Author 76 | 77 | leo, leomobiledeveloper@gmail.com 78 | 79 | ## License 80 | 81 | ParallexBanner is available under the MIT license. See the LICENSE file for more info. 82 | -------------------------------------------------------------------------------- /ScreenShots/gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoMobileDeveloper/ParallexBanner/ae2e48c8c94165ab4157ab8c7f3329e266ffd7ac/ScreenShots/gif.gif -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------