├── Example ├── AppDelegate.swift └── ViewController.swift ├── ImageBrowser.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ ├── jasnig.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ ├── ImageBrowser.xcscheme │ │ └── xcschememanagement.plist │ └── zeroj.xcuserdatad │ └── xcschemes │ ├── ImageBrowser.xcscheme │ └── xcschememanagement.plist ├── ImageBrowser.xcworkspace ├── contents.xcworkspacedata └── xcuserdata │ └── jasnig.xcuserdatad │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist ├── ImageBrowser ├── Assets.xcassets │ ├── 1.imageset │ │ ├── Contents.json │ │ └── ef725e23-5167-49c7-af3b-d9dedf3c9ad1.jpg │ ├── 2.imageset │ │ ├── Contents.json │ │ └── Snip20160420_15.png │ ├── 3.imageset │ │ ├── Contents.json │ │ └── Snip20160504_1.png │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── download.imageset │ │ ├── Contents.json │ │ ├── member_skin_button_download@2x.png │ │ └── member_skin_button_download@3x.png │ ├── feed_video_icon_download_white.imageset │ │ ├── Contents.json │ │ ├── feed_video_icon_download_white@2x.png │ │ └── feed_video_icon_download_white@3x.png │ └── more.imageset │ │ ├── Contents.json │ │ ├── alipay_msp_more@2x.png │ │ └── alipay_msp_more@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Example │ ├── AppDelegate.swift │ ├── NoSourceImageViewController.swift │ ├── ViewController.swift │ ├── 本地图片 │ │ └── LocalController.swift │ ├── 网络图片(使用代理) │ │ └── NetworkWithDelegateController.swift │ └── 网络图片(未使用代理) │ │ └── NetworkWithoutDelegateController.swift ├── Info.plist └── PhotoBrowser │ ├── PhotoBrowser.swift │ ├── PhotoToolBar.swift │ ├── PhotoViewCell.swift │ ├── SimpleHUD.swift │ └── UIViewExtension.swift ├── ImageBrowserTests ├── ImageBrowserTests.swift └── Info.plist ├── ImageBrowserUITests ├── ImageBrowserUITests.swift └── Info.plist ├── LICENSE ├── PhotoBrowser.podspec ├── 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 ├── Manifest.lock ├── Pods.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ │ └── zeroj.xcuserdatad │ │ └── xcschemes │ │ ├── Kingfisher.xcscheme │ │ ├── Pods-ImageBrowser.xcscheme │ │ └── xcschememanagement.plist └── Target Support Files │ ├── Kingfisher │ ├── Info.plist │ ├── Kingfisher-dummy.m │ ├── Kingfisher-prefix.pch │ ├── Kingfisher-umbrella.h │ ├── Kingfisher.modulemap │ └── Kingfisher.xcconfig │ └── Pods-ImageBrowser │ ├── Info.plist │ ├── Pods-ImageBrowser-acknowledgements.markdown │ ├── Pods-ImageBrowser-acknowledgements.plist │ ├── Pods-ImageBrowser-dummy.m │ ├── Pods-ImageBrowser-frameworks.sh │ ├── Pods-ImageBrowser-resources.sh │ ├── Pods-ImageBrowser-umbrella.h │ ├── Pods-ImageBrowser.debug.xcconfig │ ├── Pods-ImageBrowser.modulemap │ └── Pods-ImageBrowser.release.xcconfig ├── README.md └── ZJPhotoBrowser.podspec /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/3. 6 | // Copyright © 2016年 ZeroJ. 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/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/3. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | class TestCell: UICollectionViewCell { 13 | @IBOutlet weak var imageView: UIImageView! 14 | } 15 | 16 | 17 | // MARK: - UICollectionViewDelegate, UICollectionViewDataSource 18 | extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource { 19 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 20 | return 1 21 | } 22 | 23 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 24 | return photoModels.count 25 | } 26 | 27 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 28 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(String(TestCell), forIndexPath: indexPath) as! TestCell 29 | let model = photoModels[indexPath.row] 30 | cell.imageView.kf_setImageWithURL(NSURL(string: model.imageUrlString!)!, placeholderImage: images[0]) 31 | 32 | 33 | return cell 34 | } 35 | 36 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 37 | 38 | func setPhoto() -> [PhotoModel] { 39 | var photos: [PhotoModel] = [] 40 | for (_, photo) in photoModels.enumerate() { 41 | // 这个方法只能返回可见的cell, 如果不可见, 返回值为nil 42 | // let cell = collectionView.cellForItemAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as! TestCell 43 | // let sourceView = cell.imageView 44 | // let photoModel = PhotoModel(localImage: image, sourceImageView: sourceView, description: nil) 45 | let photoModel = PhotoModel(imageUrlString: photo.imageUrlString, sourceImageView: nil, description: nil) 46 | photos.append(photoModel) 47 | } 48 | return photos 49 | } 50 | 51 | 52 | let testVc = PhotoBrowser(photoModels: setPhoto()) 53 | testVc.delegate = self 54 | testVc.showWithBeginPage(indexPath.row) 55 | } 56 | 57 | } 58 | 59 | extension ViewController: PhotoBrowserDelegate { 60 | 61 | 62 | // 将要退出浏览模式 63 | func photoBrowserWillEndDisplay(endPage: Int) { 64 | let currentIndexPath = NSIndexPath(forRow: endPage, inSection: 0) 65 | 66 | let cell = collectionView.cellForItemAtIndexPath(currentIndexPath) as? TestCell 67 | cell?.alpha = 0.0 68 | } 69 | // 已经退出浏览模式 70 | func photoBrowserDidEndDisplay(endPage: Int) { 71 | let currentIndexPath = NSIndexPath(forRow: endPage, inSection: 0) 72 | 73 | let cell = collectionView.cellForItemAtIndexPath(currentIndexPath) as? TestCell 74 | cell?.alpha = 1.0 75 | } 76 | // 正在展示的页, 更新collectionView 77 | func photoBrowserDidDisplayPage(currentPage: Int, totalPages: Int) { 78 | let visibleIndexPaths = collectionView.indexPathsForVisibleItems() 79 | 80 | let currentIndexPath = NSIndexPath(forRow: currentPage, inSection: 0) 81 | if !visibleIndexPaths.contains(currentIndexPath) { 82 | collectionView.scrollToItemAtIndexPath(currentIndexPath, atScrollPosition: .Top, animated: false) 83 | } 84 | } 85 | // 获取动态改变的sourceImageView 86 | func sourceImageViewForCurrentIndex(index: Int) -> UIImageView? { 87 | 88 | let currentIndexPath = NSIndexPath(forRow: index, inSection: 0) 89 | 90 | let cell = collectionView.cellForItemAtIndexPath(currentIndexPath) as? TestCell 91 | let sourceView = cell?.imageView 92 | return sourceView 93 | } 94 | } 95 | 96 | class ViewController: UIViewController { 97 | 98 | @IBOutlet weak var imageView1: UIImageView! 99 | 100 | @IBOutlet weak var imageView2: UIImageView! 101 | 102 | @IBOutlet weak var collectionView: UICollectionView! 103 | 104 | var images: [UIImage] = [ 105 | UIImage(named: "1")!, 106 | UIImage(named: "2")!, 107 | UIImage(named: "3")!, 108 | UIImage(named: "1")!, 109 | UIImage(named: "2")!, 110 | UIImage(named: "3")! 111 | ] 112 | 113 | var photoModels: [PhotoModel] = [] 114 | @IBAction func slider(sender: UISlider) { 115 | 116 | } 117 | @IBAction func btnOnClick(sender: UIButton) { 118 | 119 | 120 | } 121 | 122 | 123 | func setupPhoto() { 124 | 125 | photoModels = [ 126 | PhotoModel(imageUrlString: "http://image.tianjimedia.com/uploadImages/2013/134/001GKNRJ7FCO_1440x900.jpg", sourceImageView: imageView1, description: nil), 127 | PhotoModel(imageUrlString: "http://pic4.nipic.com/20091215/2396136_140959028451_2.jpg", sourceImageView: imageView1, description: nil), 128 | PhotoModel(imageUrlString: "http://img.taopic.com/uploads/allimg/140326/235113-1403260I33562.jpg", sourceImageView: imageView1, description: nil), 129 | PhotoModel(imageUrlString: "http://pic1.ooopic.com/uploadfilepic/sheying/2009-01-04/OOOPIC_z19870212_20090104b18ef5b046904933.jpg", sourceImageView: imageView2, description: nil), 130 | PhotoModel(imageUrlString: "http://tupian.enterdesk.com/2013/mxy/12/10/15/3.jpg", sourceImageView: imageView2, description: nil), 131 | PhotoModel(imageUrlString: "http://image.tianjimedia.com/uploadImages/2011/286/8X76S7XD89VU.jpg", sourceImageView: imageView2, description: nil), 132 | PhotoModel(imageUrlString: "http://5.26923.com/download/pic/000/245/718dfc8322abe39627591e4a495767af.jpg", sourceImageView: imageView2, description: nil), 133 | PhotoModel(imageUrlString: "http://image.tuwang.com/Nature/FengGuang-1600-1200/FengGuang_pic_abx@DaTuKu.org.jpg", sourceImageView: imageView2, description: nil), 134 | PhotoModel(imageUrlString: "http://www.deskcar.com/desktop/fengjing/200895150214/21.jpg", sourceImageView: imageView2, description: nil), 135 | PhotoModel(imageUrlString: "http://pic25.nipic.com/20121203/213291_135120242136_2.jpg", sourceImageView: imageView2, description: nil), 136 | PhotoModel(imageUrlString: "http://image.tianjimedia.com/uploadImages/2012/011/R5J8A0HYL5YV.jpg", sourceImageView: imageView2, description: nil) 137 | ] 138 | 139 | 140 | } 141 | 142 | override func viewDidLoad() { 143 | super.viewDidLoad() 144 | setupPhoto() 145 | 146 | 147 | } 148 | 149 | override func didReceiveMemoryWarning() { 150 | super.didReceiveMemoryWarning() 151 | // Dispose of any resources that can be recreated. 152 | } 153 | 154 | 155 | } 156 | 157 | -------------------------------------------------------------------------------- /ImageBrowser.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ImageBrowser.xcodeproj/xcuserdata/jasnig.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /ImageBrowser.xcodeproj/xcuserdata/jasnig.xcuserdatad/xcschemes/ImageBrowser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /ImageBrowser.xcodeproj/xcuserdata/jasnig.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ImageBrowser.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4BB7D8491CD8A1C20003A082 16 | 17 | primary 18 | 19 | 20 | 4BB7D85D1CD8A1C30003A082 21 | 22 | primary 23 | 24 | 25 | 4BB7D8681CD8A1C30003A082 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ImageBrowser.xcodeproj/xcuserdata/zeroj.xcuserdatad/xcschemes/ImageBrowser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /ImageBrowser.xcodeproj/xcuserdata/zeroj.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ImageBrowser.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4BB7D8491CD8A1C20003A082 16 | 17 | primary 18 | 19 | 20 | 4BB7D85D1CD8A1C30003A082 21 | 22 | primary 23 | 24 | 25 | 4BB7D8681CD8A1C30003A082 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ImageBrowser.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ImageBrowser.xcworkspace/xcuserdata/jasnig.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 23 | 24 | 25 | 26 | 27 | 29 | 35 | 36 | 44 | 45 | 46 | 47 | 48 | 50 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ef725e23-5167-49c7-af3b-d9dedf3c9ad1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/1.imageset/ef725e23-5167-49c7-af3b-d9dedf3c9ad1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/1.imageset/ef725e23-5167-49c7-af3b-d9dedf3c9ad1.jpg -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Snip20160420_15.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/2.imageset/Snip20160420_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/2.imageset/Snip20160420_15.png -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Snip20160504_1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/3.imageset/Snip20160504_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/3.imageset/Snip20160504_1.png -------------------------------------------------------------------------------- /ImageBrowser/Assets.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 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/download.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "member_skin_button_download@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "member_skin_button_download@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/download.imageset/member_skin_button_download@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/download.imageset/member_skin_button_download@2x.png -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/download.imageset/member_skin_button_download@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/download.imageset/member_skin_button_download@3x.png -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/feed_video_icon_download_white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "feed_video_icon_download_white@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "feed_video_icon_download_white@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/feed_video_icon_download_white.imageset/feed_video_icon_download_white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/feed_video_icon_download_white.imageset/feed_video_icon_download_white@2x.png -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/feed_video_icon_download_white.imageset/feed_video_icon_download_white@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/feed_video_icon_download_white.imageset/feed_video_icon_download_white@3x.png -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/more.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "alipay_msp_more@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "alipay_msp_more@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/more.imageset/alipay_msp_more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/more.imageset/alipay_msp_more@2x.png -------------------------------------------------------------------------------- /ImageBrowser/Assets.xcassets/more.imageset/alipay_msp_more@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasnig/PhotoBrowser/3b5bd6b232390d1de5cb150ff70ffea1c006af32/ImageBrowser/Assets.xcassets/more.imageset/alipay_msp_more@3x.png -------------------------------------------------------------------------------- /ImageBrowser/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ImageBrowser/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/3. 6 | // Copyright © 2016年 ZeroJ. 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 | -------------------------------------------------------------------------------- /ImageBrowser/Example/NoSourceImageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoSourceImageViewController.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/25. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/users/fb31a3d1ec30/latest_articles 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | // 30 | 31 | import UIKit 32 | 33 | class NoSourceImageViewController: UIViewController { 34 | private lazy var collectionView: UICollectionView = {[unowned self] in 35 | let flowLayout = UICollectionViewFlowLayout() 36 | flowLayout.scrollDirection = .Vertical 37 | 38 | 39 | flowLayout.itemSize = CGSize(width: (self.view.bounds.size.width - 3*LocalController.margin) / 2, height: (self.view.bounds.size.height - 64.0 - 6*LocalController.margin) / 5) 40 | flowLayout.minimumLineSpacing = LocalController.margin 41 | flowLayout.minimumInteritemSpacing = LocalController.margin 42 | flowLayout.sectionInset = UIEdgeInsetsZero 43 | 44 | 45 | let collectionView = UICollectionView(frame: CGRect(x: 0.0, y: 64.0, width: self.view.bounds.size.width, height: self.view.bounds.size.height - 64.0), collectionViewLayout: flowLayout) 46 | collectionView.backgroundColor = UIColor.whiteColor() 47 | collectionView.delegate = self 48 | collectionView.dataSource = self 49 | collectionView.pagingEnabled = false 50 | collectionView.registerClass(TestCell.self, forCellWithReuseIdentifier: String(TestCell)) 51 | 52 | return collectionView 53 | }() 54 | let photosURLString: [String] = [ 55 | "http://a.33iq.com/upload/15/08/12/images/14393706299128.jpg", 56 | "http://pic1a.nipic.com/2008-09-19/200891903253318_2.jpg", 57 | "http://online.sccnn.com/desk2/1314/1920car_35003.jpg", 58 | "http://img1qn.moko.cc/2016-04-21/a37f9d54-493f-4352-8b40-f17cb8570e67.jpg", 59 | "http://image.tianjimedia.com/uploadImages/2015/204/14/736OGPLY9H62_1000x600.jpg", 60 | "http://a.hiphotos.baidu.com/image/pic/item/f9dcd100baa1cd11daf25f19bc12c8fcc3ce2d46.jpg", 61 | "http://image.tianjimedia.com/uploadImages/2013/134/001GKNRJ7FCO_1440x900.jpg", 62 | "http://pic4.nipic.com/20091215/2396136_140959028451_2.jpg", 63 | "http://www.05927.com/UploadFiles/pic_200910271459213674.jpg", 64 | "http://a.hiphotos.baidu.com/image/pic/item/43a7d933c895d1438d0b16fc77f082025baf07eb.jpg", 65 | "http://c.hiphotos.baidu.com/image/pic/item/8b13632762d0f703f3b967030cfa513d2797c5e2.jpg", 66 | "http://f.hiphotos.baidu.com/image/pic/item/91529822720e0cf3a2cdbc240e46f21fbe09aa33.jpg", 67 | "http://pic.58pic.com/58pic/11/52/20/45s58PICVat.jpg" 68 | 69 | 70 | ] 71 | override func viewDidLoad() { 72 | super.viewDidLoad() 73 | 74 | view.addSubview(collectionView) 75 | } 76 | 77 | override func didReceiveMemoryWarning() { 78 | super.didReceiveMemoryWarning() 79 | // Dispose of any resources that can be recreated. 80 | } 81 | 82 | } 83 | 84 | 85 | 86 | // MARK: - UICollectionViewDelegate, UICollectionViewDataSource 87 | extension NoSourceImageViewController: UICollectionViewDelegate, UICollectionViewDataSource { 88 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 89 | return 1 90 | } 91 | 92 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 93 | return photosURLString.count 94 | } 95 | 96 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 97 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(String(TestCell), forIndexPath: indexPath) as! TestCell 98 | let photoUrlString = photosURLString[indexPath.row] 99 | cell.imageView.kf_setImageWithURL(NSURL(string: photoUrlString)!, placeholderImage: UIImage(named: "1")) 100 | 101 | 102 | return cell 103 | } 104 | 105 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 106 | 107 | func setPhoto() -> [PhotoModel] { 108 | var photos: [PhotoModel] = [] 109 | for photoURLString in photosURLString { 110 | // 初始化不设置sourceImageView,也可以设置, 如果sourceImageView是不需要动态改变的, 那么推荐不需要代理设置sourceImageView 111 | // 而在代理方法中动态更新,将会覆盖原来设置的sourceImageView 112 | let photoModel = PhotoModel(imageUrlString: photoURLString, sourceImageView: nil) 113 | photos.append(photoModel) 114 | } 115 | return photos 116 | } 117 | 118 | let photoBrowser = PhotoBrowser(photoModels: setPhoto()) 119 | photoBrowser.show(inVc: self, beginPage: indexPath.row) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /ImageBrowser/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/3. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Kingfisher 11 | 12 | class TestCell: UICollectionViewCell { 13 | lazy var imageView: AnimatedImageView = AnimatedImageView(frame: self.bounds) 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | imageView.contentMode = .ScaleToFill 18 | 19 | imageView.autoPlayAnimatedImage = false 20 | addSubview(imageView) 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | } 27 | 28 | class ViewController: UIViewController { 29 | 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | } 35 | 36 | override func didReceiveMemoryWarning() { 37 | super.didReceiveMemoryWarning() 38 | // Dispose of any resources that can be recreated. 39 | } 40 | 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /ImageBrowser/Example/本地图片/LocalController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalController.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/16. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/users/fb31a3d1ec30/latest_articles 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | // 30 | 31 | import UIKit 32 | 33 | class LocalController: UIViewController { 34 | static let margin: CGFloat = 10.0 35 | 36 | private lazy var collectionView: UICollectionView = {[unowned self] in 37 | let flowLayout = UICollectionViewFlowLayout() 38 | flowLayout.scrollDirection = .Vertical 39 | 40 | 41 | flowLayout.itemSize = CGSize(width: (self.view.bounds.size.width - 3*LocalController.margin) / 2, height: (self.view.bounds.size.height - 64.0 - 6*LocalController.margin) / 5) 42 | flowLayout.minimumLineSpacing = LocalController.margin 43 | flowLayout.minimumInteritemSpacing = LocalController.margin 44 | flowLayout.sectionInset = UIEdgeInsetsZero 45 | 46 | 47 | let collectionView = UICollectionView(frame: CGRect(x: 0.0, y: 64.0, width: self.view.bounds.size.width, height: self.view.bounds.size.height - 64.0), collectionViewLayout: flowLayout) 48 | collectionView.backgroundColor = UIColor.whiteColor() 49 | collectionView.delegate = self 50 | collectionView.dataSource = self 51 | collectionView.pagingEnabled = false 52 | collectionView.registerClass(TestCell.self, forCellWithReuseIdentifier: String(TestCell)) 53 | 54 | return collectionView 55 | }() 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | 59 | view.addSubview(collectionView) 60 | } 61 | 62 | override func didReceiveMemoryWarning() { 63 | super.didReceiveMemoryWarning() 64 | // Dispose of any resources that can be recreated. 65 | } 66 | 67 | 68 | var images: [UIImage] = [ 69 | UIImage(named: "1")!, 70 | UIImage(named: "2")!, 71 | UIImage(named: "3")!, 72 | UIImage(named: "1")!, 73 | UIImage(named: "2")!, 74 | UIImage(named: "3")! 75 | ] 76 | } 77 | 78 | // MARK: - UICollectionViewDelegate, UICollectionViewDataSource 79 | extension LocalController: UICollectionViewDelegate, UICollectionViewDataSource { 80 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 81 | return 1 82 | } 83 | 84 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 85 | return images.count 86 | } 87 | 88 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 89 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(String(TestCell), forIndexPath: indexPath) as! TestCell 90 | cell.imageView.image = images[indexPath.row] 91 | 92 | return cell 93 | } 94 | 95 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 96 | 97 | func setPhoto() -> [PhotoModel] { 98 | var photos: [PhotoModel] = [] 99 | for (index, image) in images.enumerate() { 100 | // 这个方法只能返回可见的cell, 如果不可见, 返回值为nil 101 | let cell = collectionView.cellForItemAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TestCell 102 | let sourceView = cell?.imageView 103 | 104 | let photoModel = PhotoModel(localImage: image, sourceImageView: sourceView) 105 | photos.append(photoModel) 106 | } 107 | return photos 108 | } 109 | 110 | 111 | let photoBrowser = PhotoBrowser(photoModels: setPhoto()) 112 | 113 | photoBrowser.show(inVc: self, beginPage: indexPath.row) 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /ImageBrowser/Example/网络图片(使用代理)/NetworkWithDelegateController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkWithDelegateController.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/16. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/users/fb31a3d1ec30/latest_articles 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | // 30 | 31 | import UIKit 32 | 33 | class NetworkWithDelegateController: UIViewController { 34 | private lazy var collectionView: UICollectionView = {[unowned self] in 35 | let flowLayout = UICollectionViewFlowLayout() 36 | flowLayout.scrollDirection = .Vertical 37 | 38 | 39 | flowLayout.itemSize = CGSize(width: (self.view.bounds.size.width - 3*LocalController.margin) / 2, height: (self.view.bounds.size.height - 64.0 - 6*LocalController.margin) / 5) 40 | flowLayout.minimumLineSpacing = LocalController.margin 41 | flowLayout.minimumInteritemSpacing = LocalController.margin 42 | flowLayout.sectionInset = UIEdgeInsetsZero 43 | 44 | 45 | let collectionView = UICollectionView(frame: CGRect(x: 0.0, y: 64.0, width: self.view.bounds.size.width, height: self.view.bounds.size.height - 64.0), collectionViewLayout: flowLayout) 46 | collectionView.backgroundColor = UIColor.whiteColor() 47 | collectionView.delegate = self 48 | collectionView.dataSource = self 49 | collectionView.pagingEnabled = false 50 | collectionView.registerClass(TestCell.self, forCellWithReuseIdentifier: String(TestCell)) 51 | 52 | return collectionView 53 | }() 54 | let photosURLString: [String] = [ 55 | "http://a.33iq.com/upload/15/08/12/images/14393706299128.jpg", 56 | "http://pic1a.nipic.com/2008-09-19/200891903253318_2.jpg", 57 | "http://online.sccnn.com/desk2/1314/1920car_35003.jpg", 58 | "http://img1qn.moko.cc/2016-04-21/a37f9d54-493f-4352-8b40-f17cb8570e67.jpg", 59 | "http://image.tianjimedia.com/uploadImages/2015/204/14/736OGPLY9H62_1000x600.jpg", 60 | "http://a.hiphotos.baidu.com/image/pic/item/f9dcd100baa1cd11daf25f19bc12c8fcc3ce2d46.jpg", 61 | "http://image.tianjimedia.com/uploadImages/2013/134/001GKNRJ7FCO_1440x900.jpg", 62 | "http://pic4.nipic.com/20091215/2396136_140959028451_2.jpg", 63 | "http://www.05927.com/UploadFiles/pic_200910271459213674.jpg", 64 | "http://a.hiphotos.baidu.com/image/pic/item/43a7d933c895d1438d0b16fc77f082025baf07eb.jpg", 65 | "http://c.hiphotos.baidu.com/image/pic/item/8b13632762d0f703f3b967030cfa513d2797c5e2.jpg", 66 | "http://f.hiphotos.baidu.com/image/pic/item/91529822720e0cf3a2cdbc240e46f21fbe09aa33.jpg", 67 | "http://pic.58pic.com/58pic/11/52/20/45s58PICVat.jpg" 68 | 69 | 70 | ] 71 | override func viewDidLoad() { 72 | super.viewDidLoad() 73 | 74 | view.addSubview(collectionView) 75 | } 76 | 77 | override func didReceiveMemoryWarning() { 78 | super.didReceiveMemoryWarning() 79 | // Dispose of any resources that can be recreated. 80 | } 81 | 82 | } 83 | 84 | 85 | 86 | // MARK: - UICollectionViewDelegate, UICollectionViewDataSource 87 | extension NetworkWithDelegateController: UICollectionViewDelegate, UICollectionViewDataSource { 88 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 89 | return 1 90 | } 91 | 92 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 93 | return photosURLString.count 94 | } 95 | 96 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 97 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(String(TestCell), forIndexPath: indexPath) as! TestCell 98 | let photoUrlString = photosURLString[indexPath.row] 99 | cell.imageView.kf_setImageWithURL(NSURL(string: photoUrlString)!, placeholderImage: UIImage(named: "1")) 100 | 101 | 102 | return cell 103 | } 104 | 105 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 106 | 107 | func setPhoto() -> [PhotoModel] { 108 | var photos: [PhotoModel] = [] 109 | for photoURLString in photosURLString { 110 | // 初始化不设置sourceImageView,也可以设置, 如果sourceImageView是不需要动态改变的, 那么推荐不需要代理设置sourceImageView 111 | // 而在代理方法中动态更新,将会覆盖原来设置的sourceImageView 112 | let photoModel = PhotoModel(imageUrlString: photoURLString, sourceImageView: nil) 113 | photos.append(photoModel) 114 | } 115 | return photos 116 | } 117 | 118 | let photoBrowser = PhotoBrowser(photoModels: setPhoto()) 119 | // 指定代理 120 | photoBrowser.delegate = self 121 | photoBrowser.show(inVc: self, beginPage: indexPath.row) 122 | } 123 | } 124 | 125 | 126 | extension NetworkWithDelegateController: PhotoBrowserDelegate { 127 | 128 | /** 在将要退出浏览模式,和已经退出浏览模式中获取到的endPage是相同的, 所以可以在这两个方法的短暂时间中处理一些动画, 比如, 这里示例处理了原来的cell的显示和隐藏*/ 129 | // 将要退出浏览模式 130 | func photoBrowserWillEndDisplay(endPage: Int) { 131 | let currentIndexPath = NSIndexPath(forRow: endPage, inSection: 0) 132 | 133 | let cell = collectionView.cellForItemAtIndexPath(currentIndexPath) as? TestCell 134 | cell?.alpha = 0.0 135 | } 136 | // 已经退出浏览模式 137 | func photoBrowserDidEndDisplay(endPage: Int) { 138 | let currentIndexPath = NSIndexPath(forRow: endPage, inSection: 0) 139 | 140 | let cell = collectionView.cellForItemAtIndexPath(currentIndexPath) as? TestCell 141 | cell?.alpha = 1.0 142 | } 143 | 144 | // 正在展示的页 145 | // 因为通过 collectionView.cellForItemAtIndexPath(currentIndexPath) 146 | // 这个方法只能获取到当前显示在collectionView中的cell, 否则返回nil, 147 | // 所以如果是在浏览图片的时候的页数对应的currentIndexPath已经不在collectionView的可见cell范围内 148 | // 返回的将是nil, 就不能准确的获取到 sourceImageView 149 | // 故在每次显示图片的时候判断如果对应的sourceImageView cell已经不可见 150 | // 那么就更新collectionView的位置, 滚动到相应的cell 151 | // 当然更新collectionView位置的方法很多, 这里只是示例 152 | func photoBrowserDidDisplayPage(currentPage: Int, totalPages: Int) { 153 | let visibleIndexPaths = collectionView.indexPathsForVisibleItems() 154 | 155 | let currentIndexPath = NSIndexPath(forRow: currentPage, inSection: 0) 156 | if !visibleIndexPaths.contains(currentIndexPath) { 157 | collectionView.scrollToItemAtIndexPath(currentIndexPath, atScrollPosition: .Top, animated: false) 158 | collectionView.layoutIfNeeded() 159 | } 160 | } 161 | // 获取动态改变的sourceImageView 162 | func sourceImageViewForCurrentIndex(index: Int) -> UIImageView? { 163 | 164 | let currentIndexPath = NSIndexPath(forRow: index, inSection: 0) 165 | 166 | let cell = collectionView.cellForItemAtIndexPath(currentIndexPath) as? TestCell 167 | let sourceView = cell?.imageView 168 | return sourceView 169 | } 170 | 171 | } -------------------------------------------------------------------------------- /ImageBrowser/Example/网络图片(未使用代理)/NetworkWithoutDelegateController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkWithoutDelegateController.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/16. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/users/fb31a3d1ec30/latest_articles 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | // 30 | 31 | import UIKit 32 | 33 | class NetworkWithoutDelegateController: UIViewController { 34 | private lazy var collectionView: UICollectionView = {[unowned self] in 35 | let flowLayout = UICollectionViewFlowLayout() 36 | flowLayout.scrollDirection = .Vertical 37 | 38 | 39 | flowLayout.itemSize = CGSize(width: (self.view.bounds.size.width - 3*LocalController.margin) / 2, height: (self.view.bounds.size.height - 64.0 - 6*LocalController.margin) / 5) 40 | flowLayout.minimumLineSpacing = LocalController.margin 41 | flowLayout.minimumInteritemSpacing = LocalController.margin 42 | flowLayout.sectionInset = UIEdgeInsetsZero 43 | 44 | 45 | let collectionView = UICollectionView(frame: CGRect(x: 0.0, y: 64.0, width: self.view.bounds.size.width, height: self.view.bounds.size.height - 64.0), collectionViewLayout: flowLayout) 46 | collectionView.backgroundColor = UIColor.whiteColor() 47 | collectionView.delegate = self 48 | collectionView.dataSource = self 49 | collectionView.pagingEnabled = false 50 | collectionView.registerClass(TestCell.self, forCellWithReuseIdentifier: String(TestCell)) 51 | 52 | return collectionView 53 | }() 54 | let photosURLString: [String] = [ 55 | "http://img1.mydrivers.com/img/20140701/25dd8788193d44f3afbadca4bc21b3a7.gif", 56 | "http://img.zcool.cn/community/01f08155b607c032f875251fc81d45.gif", 57 | "http://img.taopic.com/uploads/allimg/140326/235113-1403260I33562.jpg", 58 | "http://img.taopic.com/uploads/allimg/140326/235113-1403260I33562.jpg", 59 | "http://tupian.enterdesk.com/2013/mxy/12/10/15/3.jpg", 60 | "http://5.26923.com/download/pic/000/245/718dfc8322abe39627591e4a495767af.jpg", 61 | "http://pic25.nipic.com/20121203/213291_135120242136_2.jpg" 62 | ] 63 | 64 | 65 | override func viewDidLoad() { 66 | super.viewDidLoad() 67 | 68 | view.addSubview(collectionView) 69 | } 70 | 71 | override func didReceiveMemoryWarning() { 72 | super.didReceiveMemoryWarning() 73 | // Dispose of any resources that can be recreated. 74 | } 75 | 76 | 77 | 78 | } 79 | 80 | 81 | // MARK: - UICollectionViewDelegate, UICollectionViewDataSource 82 | extension NetworkWithoutDelegateController: UICollectionViewDelegate, UICollectionViewDataSource { 83 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 84 | return 1 85 | } 86 | 87 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 88 | return photosURLString.count 89 | } 90 | 91 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 92 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(String(TestCell), forIndexPath: indexPath) as! TestCell 93 | let photoUrlString = photosURLString[indexPath.row] 94 | cell.imageView.kf_setImageWithURL(NSURL(string: photoUrlString)!, placeholderImage: UIImage(named: "1")) 95 | 96 | 97 | return cell 98 | } 99 | 100 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 101 | 102 | func setPhoto() -> [PhotoModel] { 103 | var photos: [PhotoModel] = [] 104 | for (index, photoURLString) in photosURLString.enumerate() { 105 | // 这个方法只能返回可见的cell, 如果不可见, 返回值为nil 106 | let cell = collectionView.cellForItemAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TestCell 107 | let sourceView = cell?.imageView 108 | let photoModel = PhotoModel(imageUrlString: photoURLString, sourceImageView: sourceView) 109 | photos.append(photoModel) 110 | } 111 | return photos 112 | } 113 | 114 | let photoBrowser = PhotoBrowser(photoModels: setPhoto()) {[weak self] (extraBtn) in 115 | if let sSelf = self { 116 | let hud = SimpleHUD(frame:CGRect(x: 0.0, y: (sSelf.view.zj_height - 80)*0.5, width: sSelf.view.zj_width, height: 80.0)) 117 | sSelf.view.addSubview(hud) 118 | hud.showHUD("点击了附加的按钮", autoHide: true, afterTime: 2.0) 119 | } 120 | 121 | } 122 | // 指定代理 123 | 124 | photoBrowser.show(inVc: self, beginPage: indexPath.row) 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /ImageBrowser/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 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ImageBrowser/PhotoBrowser/PhotoToolBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoToolBar.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/9. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/users/fb31a3d1ec30/latest_articles 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | 30 | import UIKit 31 | 32 | /// /// @author ZeroJ, 16-05-24 23:05:11 33 | /// 34 | //TODO: 还未使用 (it is not useful now) 35 | struct ToolBarStyle { 36 | enum ToolBarPosition { 37 | case Up 38 | case Down 39 | } 40 | /// 是否显示保存按钮 41 | var showSaveBtn = true 42 | /// 是否显示附加按钮 43 | var showExtraBtn = true 44 | /// toolBar位置 45 | var toolbarPosition = ToolBarPosition.Down 46 | 47 | } 48 | 49 | class PhotoToolBar: UIView { 50 | typealias BtnAction = (btn: UIButton) -> Void 51 | var saveBtnOnClick: BtnAction? 52 | var extraBtnOnClick: BtnAction? 53 | 54 | var indexText: String = " " { 55 | didSet { 56 | indexLabel.text = indexText 57 | } 58 | } 59 | 60 | var toolBarStyle: ToolBarStyle! 61 | 62 | /// 保存图片按钮 63 | private lazy var saveBtn: UIButton = { 64 | 65 | let saveBtn = UIButton() 66 | saveBtn.setTitleColor(UIColor.whiteColor(), forState: .Normal) 67 | saveBtn.backgroundColor = UIColor.clearColor() 68 | saveBtn.setImage(UIImage(named: "feed_video_icon_download_white"), forState: .Normal) 69 | saveBtn.addTarget(self, action: #selector(self.saveBtnOnClick(_:)), forControlEvents: .TouchUpInside) 70 | 71 | // saveBtn.hidden = self.toolBarStyle.showSaveBtn 72 | return saveBtn 73 | }() 74 | /// 附加的按钮 75 | private lazy var extraBtn: UIButton = { 76 | let extraBtn = UIButton() 77 | extraBtn.setTitleColor(UIColor.whiteColor(), forState: .Normal) 78 | extraBtn.backgroundColor = UIColor.clearColor() 79 | extraBtn.setImage(UIImage(named: "more"), forState: .Normal) 80 | extraBtn.addTarget(self, action: #selector(self.extraBtnOnClick(_:)), forControlEvents: .TouchUpInside) 81 | // extraBtn.hidden = self.toolBarStyle.showExtraBtn 82 | 83 | return extraBtn 84 | }() 85 | /// 显示页码 86 | private lazy var indexLabel:UILabel = { 87 | let indexLabel = UILabel() 88 | indexLabel.textColor = UIColor.whiteColor() 89 | indexLabel.backgroundColor = UIColor.clearColor() 90 | indexLabel.textAlignment = NSTextAlignment.Center 91 | indexLabel.font = UIFont.boldSystemFontOfSize(20.0) 92 | return indexLabel 93 | }() 94 | 95 | init(frame: CGRect, toolBarStyle: ToolBarStyle) { 96 | super.init(frame: frame) 97 | self.toolBarStyle = toolBarStyle 98 | commonInit() 99 | } 100 | 101 | required init?(coder aDecoder: NSCoder) { 102 | super.init(coder: aDecoder) 103 | } 104 | 105 | func commonInit() { 106 | addSubview(saveBtn) 107 | addSubview(indexLabel) 108 | addSubview(extraBtn) 109 | } 110 | 111 | func saveBtnOnClick(btn: UIButton) { 112 | saveBtnOnClick?(btn:btn) 113 | } 114 | func extraBtnOnClick(btn: UIButton) { 115 | extraBtnOnClick?(btn: btn) 116 | } 117 | 118 | 119 | override func layoutSubviews() { 120 | super.layoutSubviews() 121 | 122 | let margin: CGFloat = 30.0 123 | let btnW: CGFloat = 60.0 124 | 125 | let saveBtnX = NSLayoutConstraint(item: saveBtn, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1.0, constant: margin) 126 | let saveBtnY = NSLayoutConstraint(item: saveBtn, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0.0) 127 | let saveBtnW = NSLayoutConstraint(item: saveBtn, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: btnW) 128 | let saveBtnH = NSLayoutConstraint(item: saveBtn, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 1.0, constant: 0.0) 129 | 130 | saveBtn.translatesAutoresizingMaskIntoConstraints = false 131 | addConstraints([saveBtnX, saveBtnY, saveBtnW, saveBtnH]) 132 | 133 | let extraBtnX = NSLayoutConstraint(item: extraBtn, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1.0, constant: -margin) 134 | let extraBtnY = NSLayoutConstraint(item: extraBtn, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0.0) 135 | let extraBtnW = NSLayoutConstraint(item: extraBtn, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: btnW) 136 | let extraBtnH = NSLayoutConstraint(item: extraBtn, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 1.0, constant: 0.0) 137 | 138 | extraBtn.translatesAutoresizingMaskIntoConstraints = false 139 | addConstraints([extraBtnX, extraBtnY, extraBtnW, extraBtnH]) 140 | 141 | 142 | let indexLabelLeft = NSLayoutConstraint(item: indexLabel, attribute: .Leading, relatedBy: .Equal, toItem: saveBtn, attribute: .Trailing, multiplier: 1.0, constant: 0.0) 143 | let indexLabelY = NSLayoutConstraint(item: indexLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0.0) 144 | let indexLabelRight = NSLayoutConstraint(item: indexLabel, attribute: .Trailing, relatedBy: .Equal, toItem: extraBtn, attribute: .Leading, multiplier: 1.0, constant: 0.0) 145 | let indexLabelH = NSLayoutConstraint(item: indexLabel, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 1.0, constant: 0.0) 146 | 147 | indexLabel.translatesAutoresizingMaskIntoConstraints = false 148 | addConstraints([indexLabelLeft, indexLabelY, indexLabelRight, indexLabelH]) 149 | 150 | 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /ImageBrowser/PhotoBrowser/PhotoViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoViewCell.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/15. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/users/fb31a3d1ec30/latest_articles 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | 30 | import UIKit 31 | import Kingfisher 32 | 33 | public class PhotoModel { 34 | public var description: String? 35 | // 网络图片的URL 36 | public var imageUrlString: String? 37 | // 本地图片 或者已经下载好的图片 38 | public var localImage: UIImage? 39 | // 用来设置默认图片 和执行动画 如果没有提供,将没有加载时的默认图片, 并且没有动画 40 | public var sourceImageView: UIImageView? 41 | // 本地图片 42 | public init(localImage: UIImage?, sourceImageView: UIImageView?) { 43 | self.localImage = localImage 44 | self.sourceImageView = sourceImageView 45 | // self.description = description 46 | } 47 | // 网络图片 48 | public init(imageUrlString: String?, sourceImageView: UIImageView?) { 49 | self.imageUrlString = imageUrlString 50 | self.sourceImageView = sourceImageView 51 | // self.description = description 52 | } 53 | } 54 | 55 | class PhotoViewCell: UICollectionViewCell { 56 | /// 单击响应 57 | var singleTapAction: ((gesture: UITapGestureRecognizer) -> Void)? 58 | /// 图片模型设置 59 | var photoModel: PhotoModel! = nil { 60 | didSet { 61 | setupImage() 62 | } 63 | } 64 | // 是否横屏 65 | private var isLandscap: Bool { 66 | let screenSize = UIScreen.mainScreen().bounds.size 67 | return screenSize.width >= screenSize.height 68 | } 69 | 70 | private var downloadTask: RetrieveImageTask? 71 | /// 懒加载 对外可读 72 | private(set) lazy var imageView: AnimatedImageView = {[unowned self] in 73 | let imageView = AnimatedImageView() 74 | imageView.contentMode = .ScaleAspectFit 75 | imageView.userInteractionEnabled = true 76 | imageView.backgroundColor = UIColor.blackColor() 77 | return imageView 78 | }() 79 | /// 懒加载 80 | private(set) lazy var scrollView: UIScrollView = { 81 | let scrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: self.contentView.zj_width - PhotoBrowser.contentMargin, height: self.contentView.zj_height)) 82 | scrollView.showsVerticalScrollIndicator = true 83 | scrollView.showsHorizontalScrollIndicator = true 84 | scrollView.clipsToBounds = true 85 | // pagingEnabled = false 86 | // 预设定 87 | scrollView.maximumZoomScale = 2.0 88 | scrollView.minimumZoomScale = 1.0 89 | scrollView.backgroundColor = UIColor.blackColor() 90 | scrollView.delegate = self 91 | return scrollView 92 | }() 93 | /// 进度显示 94 | private lazy var hud: SimpleHUD? = {[unowned self] in 95 | 96 | let hud = SimpleHUD(frame:CGRect(x: 0.0, y: (self.zj_height - 80)*0.5, width: self.zj_width, height: 80.0)) 97 | return hud 98 | }() 99 | 100 | /// 设置图片 101 | var image: UIImage? = nil { 102 | didSet { 103 | setupImageViewFrame() 104 | } 105 | } 106 | //MARK:- life cycle 107 | override init(frame: CGRect) { 108 | super.init(frame: frame) 109 | commonInit() 110 | } 111 | 112 | required init?(coder aDecoder: NSCoder) { 113 | super.init(coder: aDecoder) 114 | commonInit() 115 | } 116 | 117 | deinit { 118 | downloadTask?.cancel() 119 | downloadTask = nil 120 | print("\(self.debugDescription) --- 销毁") 121 | } 122 | //MARK:- private 初始设置 123 | private func commonInit() { 124 | setupScrollView() 125 | addGestures() 126 | } 127 | 128 | private func addGestures() { 129 | let singleTap = UITapGestureRecognizer(target: self, action: #selector(self.handleSingleTap(_:))) 130 | singleTap.numberOfTapsRequired = 1 131 | singleTap.numberOfTouchesRequired = 1 132 | 133 | let doubleTap = UITapGestureRecognizer(target: self, action: #selector(self.handleDoubleTap(_:))) 134 | doubleTap.numberOfTapsRequired = 2 135 | doubleTap.numberOfTouchesRequired = 1 136 | 137 | // 允许优先执行doubleTap, 在doubleTap执行失败的时候执行singleTap 138 | // 如果没有设置这个, 那么将只会执行singleTap 不会执行doubleTap 139 | singleTap.requireGestureRecognizerToFail(doubleTap) 140 | 141 | addGestureRecognizer(singleTap) 142 | addGestureRecognizer(doubleTap) 143 | } 144 | 145 | private func setupScrollView() { 146 | scrollView.addSubview(imageView) 147 | contentView.addSubview(scrollView) 148 | } 149 | 150 | //MARK:- 点击处理 151 | 152 | // 单击手势, 给外界处理 153 | func handleSingleTap(ges: UITapGestureRecognizer) { 154 | // 首先缩放到最小倍数, 以便于执行退出动画时frame可以同步改变 155 | if scrollView.zoomScale != scrollView.minimumZoomScale { 156 | scrollView.setZoomScale(scrollView.minimumZoomScale, animated: false) 157 | } 158 | singleTapAction?(gesture: ges) 159 | } 160 | 161 | // 双击放大至最大 或者 缩小至最小 162 | func handleDoubleTap(ges: UITapGestureRecognizer) { 163 | // print("double---------") 164 | if imageView.image == nil { return } 165 | 166 | if scrollView.zoomScale <= scrollView.minimumZoomScale { // 放大 167 | 168 | let location = ges.locationInView(scrollView) 169 | // 放大scrollView.maximumZoomScale倍, 将它的宽高缩小 170 | let width = scrollView.zj_width/scrollView.maximumZoomScale 171 | let height = scrollView.zj_height/scrollView.maximumZoomScale 172 | 173 | let rect = CGRect(x: location.x * (1 - 1/scrollView.maximumZoomScale), y: location.y * (1 - 1/scrollView.maximumZoomScale), width: width, height: height) 174 | scrollView.zoomToRect(rect, animated: true) 175 | 176 | } else {// 缩小 177 | scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true) 178 | 179 | } 180 | 181 | } 182 | 183 | // 处理屏幕旋转 184 | override func layoutSubviews() { 185 | super.layoutSubviews() 186 | scrollView.frame = CGRect(x: 0.0, y: 0.0, width: self.contentView.zj_width - PhotoBrowser.contentMargin, height: self.contentView.zj_height) 187 | setupImageViewFrame() 188 | } 189 | 190 | /// 重置UI状态 191 | func resetUI() { 192 | scrollView.zoomScale = scrollView.minimumZoomScale 193 | imageView.image = nil 194 | singleTapAction = nil 195 | hud?.hideHUD() 196 | } 197 | } 198 | 199 | // MARK: - private helper --- 加载图片设置 200 | extension PhotoViewCell { 201 | private func setupImage() { 202 | 203 | hud?.hideHUD() 204 | guard let photo = photoModel else { 205 | assert(false, "设置的图片模型不正确") 206 | return 207 | } 208 | 209 | // 如果是加载本地的图片, 直接设置图片即可, 注意这里是photoBrowser需要提升的地方 210 | // 因为对于本地图片的加载没有做处理, 所以当直接使用 UIImage(named"")的形式加载图片的时候, 会消耗大量的内存 211 | // 不过鉴于参考了其他的图片浏览器框架, 大家对本地图片都没有处理, 因为这个确实用的很少, 毕竟都是用来加载网络图片的情况比较多 212 | // 如果发现确实需要处理后面会努力处理这个问题 213 | if photo.localImage != nil { 214 | image = photo.localImage 215 | return 216 | } 217 | 218 | // 加载网路图片 219 | guard let urlString = photo.imageUrlString, let url = NSURL(string: urlString) else { 220 | assert(false, "设置的url不合法") 221 | return 222 | } 223 | // 添加提示框 224 | if let HUD = hud { 225 | addSubview(HUD) 226 | } 227 | // 添加进度显示 228 | hud?.addLoadingView() 229 | // 设置默认图片 230 | if let sourceImageView = photo.sourceImageView { 231 | image = sourceImageView.image 232 | } 233 | 234 | image = image ?? UIImage(named: "2") 235 | downloadTask = imageView.kf_setImageWithURL(url, placeholderImage: image, optionsInfo: nil, progressBlock: {[weak self] (receivedSize, totalSize) in 236 | let progress = Double(receivedSize) / Double(totalSize) 237 | // print(progress) 238 | if let sSelf = self { 239 | 240 | sSelf.hud?.progress = progress 241 | } 242 | 243 | }) {[weak self] (image, error, cacheType, imageURL) in 244 | // 加载完成 245 | // 注意: 因为这个闭包是多线程调用的 所以可能存在 没有显示完图片,就点击了返回 246 | // 这个时候self已经被销毁了 所以使用[unonwed self] 将会导致"野指针"的问题 247 | // 使用 [weak self] 保证安全访问self 248 | // 但是这也不是绝对安全的, 比如在 self 销毁之前, 进入了这个闭包 那么strongSelf 有值 进入 249 | // 如果在这时恰好 self 销毁了,那么之后调用strongSelf 都将会出错crash 250 | 251 | // withExtendedLifetime(self, { () -> self in 252 | // 253 | // }) 254 | let strongSelf = self 255 | guard let `self` = strongSelf else { return } 256 | 257 | 258 | self.image = image 259 | self.hud?.hideLoadingView() 260 | 261 | if let _ = image {//加载成功 262 | self.hud?.hideHUD() 263 | return 264 | } 265 | 266 | // 提示加载错误 267 | self.hud?.showHUD("加载失败", autoHide: false, afterTime: 0.0) 268 | 269 | 270 | } 271 | 272 | } 273 | 274 | private func setupImageViewFrame() { 275 | // 考虑长图, 考虑旋转屏幕 276 | if let imageV = image { 277 | 278 | // 设置为最小倍数 279 | scrollView.setZoomScale(scrollView.minimumZoomScale, animated: false) 280 | imageView.image = image 281 | 282 | // 按照图片比例设置imageView的frame 283 | let width = imageV.size.width < scrollView.zj_width ? imageV.size.width : scrollView.zj_width 284 | let height = imageV.size.height * (width / imageV.size.width) 285 | 286 | // 长图 287 | if height > scrollView.zj_height { 288 | imageView.frame = CGRect(x: (scrollView.zj_width - width) / 2, y: 0.0, width: width, height: height) 289 | scrollView.contentSize = imageView.bounds.size 290 | scrollView.contentOffset = CGPointZero 291 | // scrollView.zoomToRect(CGRect(x: scrollView.zj_centerX, y: scrollView.zj_centerY, width: scrollView.zj_width/2, height: scrollView.zj_height/2), animated: false) 292 | } else { 293 | // 居中显示 294 | imageView.frame = CGRect(x: (scrollView.zj_width - width) / 2, y: (scrollView.zj_height - height) / 2, width: width, height: height) 295 | 296 | } 297 | // 使得最大缩放时(双击或者放大)图片高度 = 屏幕高度 + 1.0倍图片高度 298 | scrollView.maximumZoomScale = scrollView.zj_height / height + 1.0 299 | } 300 | } 301 | } 302 | 303 | // MARK: - UIScrollViewDelegate 304 | extension PhotoViewCell: UIScrollViewDelegate { 305 | func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { 306 | return imageView 307 | } 308 | 309 | func scrollViewDidZoom(scrollView: UIScrollView) { 310 | // 居中显示图片 311 | setImageViewToTheCenter() 312 | } 313 | 314 | 315 | // 居中显示图片 316 | func setImageViewToTheCenter() { 317 | let offsetX = (scrollView.zj_width > scrollView.contentSize.width) ? (scrollView.zj_width - scrollView.contentSize.width)*0.5 : 0.0 318 | let offsetY = (scrollView.zj_height > scrollView.contentSize.height) ? (scrollView.zj_height - scrollView.contentSize.height)*0.5 : 0.0 319 | 320 | imageView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX, y: scrollView.contentSize.height * 0.5 + offsetY) 321 | 322 | } 323 | 324 | } 325 | -------------------------------------------------------------------------------- /ImageBrowser/PhotoBrowser/SimpleHUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleHUD.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/15. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/users/fb31a3d1ec30/latest_articles 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | 30 | import UIKit 31 | 32 | class SimpleHUD: UIView { 33 | 34 | class LoadingView: UIView { 35 | 36 | private let lineWidth: CGFloat = 8.0 37 | private var radius: CGFloat { 38 | return min(zj_width, zj_height) * 0.5 39 | } 40 | 41 | private var progressText: String = "0%" { 42 | didSet { 43 | addSubview(progressLabel) 44 | progressLabel.text = progressText 45 | } 46 | } 47 | 48 | private lazy var progressLabel: UILabel = { 49 | let label = UILabel(frame: CGRect(x: 10.0, y: (self.zj_height - 30.0) * 0.5, width: self.zj_width - 20.0, height: 30.0)) 50 | label.center = self.center 51 | label.font = UIFont.boldSystemFontOfSize(16.0) 52 | label.textColor = UIColor.whiteColor() 53 | label.textAlignment = .Center 54 | 55 | return label 56 | }() 57 | 58 | 59 | var progress: Double = 0.0 { 60 | didSet { 61 | // print(progress) 62 | if progress >= 1.0 { // 加载完成 63 | removeFromSuperview() 64 | } 65 | progressText = "\(String(format: "%.0f", progress * 100))%" 66 | 67 | // 调用这个方法会执行drawRect方法 68 | setNeedsDisplay() 69 | } 70 | } 71 | 72 | override init(frame: CGRect) { 73 | super.init(frame: frame) 74 | backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3) 75 | layer.cornerRadius = frame.size.width * 0.5 76 | layer.masksToBounds = true 77 | } 78 | 79 | required init?(coder aDecoder: NSCoder) { 80 | fatalError("init(coder:) has not been implemented") 81 | } 82 | 83 | 84 | override func drawRect(rect: CGRect) { 85 | 86 | 87 | let context = UIGraphicsGetCurrentContext() 88 | 89 | // 画圆环 90 | CGContextSetLineWidth(context, lineWidth) 91 | 92 | CGContextSetLineCap(context, .Round) 93 | let endAngle = CGFloat(progress * M_PI * 2 - M_PI_2 + 0.01) 94 | 95 | CGContextAddArc(context, rect.size.width/2, rect.size.height/2, radius, -CGFloat(M_PI_2), endAngle, 0) 96 | 97 | CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor) 98 | CGContextStrokePath(context) 99 | 100 | } 101 | 102 | } 103 | 104 | /// 加载错误提示 105 | private lazy var messageLabel: UILabel = { 106 | let label = UILabel(frame: CGRect(x: (self.bounds.size.width - 80.0) * 0.5, y:0.0, width: 80.0 , height: 30.0)) 107 | 108 | label.font = UIFont.boldSystemFontOfSize(16.0) 109 | label.backgroundColor = UIColor.blackColor() 110 | label.layer.cornerRadius = label.bounds.size.height / 2 111 | label.layer.masksToBounds = true 112 | 113 | label.textColor = UIColor.whiteColor() 114 | label.textAlignment = .Center 115 | 116 | return label 117 | }() 118 | 119 | var progress: Double = 0.01 { 120 | willSet { 121 | loadingView.progress = newValue 122 | } 123 | } 124 | 125 | private lazy var loadingView: LoadingView = { 126 | let loadingView = LoadingView(frame: CGRect(x: (self.bounds.width - 80)*0.5, y: (self.bounds.height - 80)*0.5, width: 80.0, height: 80.0)) 127 | return loadingView 128 | }() 129 | 130 | override init(frame: CGRect) { 131 | super.init(frame: frame) 132 | backgroundColor = UIColor.clearColor() 133 | } 134 | 135 | required init?(coder aDecoder: NSCoder) { 136 | fatalError("init(coder:) has not been implemented") 137 | } 138 | func addLoadingView() { 139 | messageLabel.removeFromSuperview() 140 | addSubview(loadingView) 141 | 142 | } 143 | 144 | /// 145 | /// 146 | /// - parameter autoHide: 是否自动隐藏 147 | /// - parameter time: 自动隐藏的时间 只有当autoHide = true的时候有效 148 | func showHUD(text: String, autoHide: Bool, afterTime time: Double) { 149 | 150 | loadingView.removeFromSuperview() 151 | addSubview(messageLabel) 152 | messageLabel.text = text 153 | 154 | let textSize = (text as NSString).boundingRectWithSize(CGSizeMake(CGFloat(MAXFLOAT), 0.0), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: messageLabel.font], context: nil) 155 | messageLabel.frame = CGRect(x: (self.bounds.width - textSize.width - 16)*0.5, y: (self.bounds.height - 40.0)*0.5, width: textSize.width + 16, height: 40.0) 156 | 157 | messageLabel.layer.cornerRadius = messageLabel.bounds.height / 2 158 | 159 | 160 | if autoHide { 161 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(time * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {[unowned self] in 162 | self.hideHUD() 163 | 164 | }) 165 | } 166 | 167 | } 168 | func hideLoadingView() { 169 | loadingView.removeFromSuperview() 170 | } 171 | func hideHUD() { 172 | self.removeFromSuperview() 173 | } 174 | } -------------------------------------------------------------------------------- /ImageBrowser/PhotoBrowser/UIViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewExtensionFrame.swift 3 | // ImageBrowser 4 | // 5 | // Created by jasnig on 16/5/15. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // github: https://github.com/jasnig 8 | // 简书: http://www.jianshu.com/p/b84f4dd96d0c 9 | 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import UIKit 30 | //MARK: - frame extension 31 | extension UIView { 32 | /// x 33 | public final var zj_x: CGFloat { 34 | set(x) { 35 | frame.origin.x = x 36 | } 37 | 38 | get { 39 | return frame.origin.x 40 | } 41 | } 42 | /// y 43 | public final var zj_y:CGFloat { 44 | set(y) { 45 | frame.origin.y = y 46 | } 47 | 48 | get { 49 | return frame.origin.y 50 | } 51 | } 52 | /// centerX 53 | public final var zj_centerX: CGFloat { 54 | set(centerX) { 55 | center.x = centerX 56 | } 57 | 58 | get { 59 | return center.x 60 | } 61 | } 62 | /// centerY 63 | public final var zj_centerY: CGFloat { 64 | set(centerY) { 65 | center.y = centerY 66 | } 67 | 68 | get { 69 | return center.y 70 | } 71 | } 72 | /// width 73 | public final var zj_width: CGFloat { 74 | set(width) { 75 | frame.size.width = width 76 | } 77 | 78 | get { 79 | return bounds.size.width 80 | } 81 | } 82 | /// height 83 | public final var zj_height: CGFloat { 84 | set(height) { 85 | frame.size.height = height 86 | } 87 | 88 | get { 89 | return bounds.size.height 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /ImageBrowserTests/ImageBrowserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageBrowserTests.swift 3 | // ImageBrowserTests 4 | // 5 | // Created by jasnig on 16/5/3. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ImageBrowser 11 | 12 | class ImageBrowserTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ImageBrowserTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ImageBrowserUITests/ImageBrowserUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageBrowserUITests.swift 3 | // ImageBrowserUITests 4 | // 5 | // Created by jasnig on 16/5/3. 6 | // Copyright © 2016年 ZeroJ. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ImageBrowserUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ImageBrowserUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 ZeroJ 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 | -------------------------------------------------------------------------------- /PhotoBrowser.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "PhotoBrowser" 3 | s.version = "0.0.2" 4 | s.summary = "PhotoBrowser provides an easy way to reach the effect that looking through photos likes the system photo app" 5 | s.homepage = "https://github.com/jasnig/PhotoBrowser" 6 | s.license = { :type => "MIT" } 7 | s.authors = { "ZeroJ" => "854136959@qq.com" } 8 | 9 | s.requires_arc = true 10 | s.platform = :ios 11 | s.platform = :ios, "8.0" 12 | s.source = { :git => "https://github.com/jasnig/PhotoBrowser.git", :tag => s.version } 13 | s.framework = "UIKit" 14 | s.dependency 'Kingfisher' 15 | s.source_files = "ImageBrowser/PhotoBrowser/*.swift" 16 | end -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '8.0' 3 | use_frameworks! 4 | target 'ImageBrowser' do 5 | pod 'Kingfisher' 6 | end 7 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Kingfisher (2.4.1) 3 | 4 | DEPENDENCIES: 5 | - Kingfisher 6 | 7 | SPEC CHECKSUMS: 8 | Kingfisher: 41aed5b6c1ed8fa98cf6d75283ce89cf7890f517 9 | 10 | PODFILE CHECKSUM: 1774de14a9dba58ff0d5da6928454319945db864 11 | 12 | COCOAPODS: 1.0.1 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | return kf_setImageWithResource(Resource(downloadURL: URL), 66 | placeholderImage: placeholderImage, 67 | optionsInfo: optionsInfo, 68 | progressBlock: progressBlock, 69 | completionHandler: completionHandler) 70 | } 71 | 72 | 73 | /** 74 | Set an image with a URL, a placeholder image, options, progress handler and completion handler. 75 | 76 | - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`. 77 | - parameter placeholderImage: A placeholder image when retrieving the image at URL. 78 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 79 | - parameter progressBlock: Called when the image downloading progress gets updated. 80 | - parameter completionHandler: Called when the image retrieved and set. 81 | 82 | - returns: A task represents the retrieving process. 83 | 84 | - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 85 | The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method. 86 | */ 87 | public func kf_setImageWithResource(resource: Resource, 88 | placeholderImage: Image? = nil, 89 | optionsInfo: KingfisherOptionsInfo? = nil, 90 | progressBlock: DownloadProgressBlock? = nil, 91 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask 92 | { 93 | let showIndicatorWhenLoading = kf_showIndicatorWhenLoading 94 | var indicator: IndicatorView? = nil 95 | if showIndicatorWhenLoading { 96 | indicator = kf_indicator 97 | indicator?.hidden = false 98 | indicator?.kf_startAnimating() 99 | } 100 | 101 | image = placeholderImage 102 | 103 | kf_setWebURL(resource.downloadURL) 104 | 105 | var options = optionsInfo ?? [] 106 | if shouldPreloadAllGIF() { 107 | options.append(.PreloadAllGIFData) 108 | } 109 | 110 | let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: options, 111 | progressBlock: { receivedSize, totalSize in 112 | if let progressBlock = progressBlock { 113 | progressBlock(receivedSize: receivedSize, totalSize: totalSize) 114 | } 115 | }, 116 | completionHandler: {[weak self] image, error, cacheType, imageURL in 117 | 118 | dispatch_async_safely_to_main_queue { 119 | guard let sSelf = self where imageURL == sSelf.kf_webURL else { 120 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 121 | return 122 | } 123 | 124 | sSelf.kf_setImageTask(nil) 125 | 126 | guard let image = image else { 127 | indicator?.kf_stopAnimating() 128 | completionHandler?(image: nil, error: error, cacheType: cacheType, imageURL: imageURL) 129 | return 130 | } 131 | 132 | if let transitionItem = options.kf_firstMatchIgnoringAssociatedValue(.Transition(.None)), 133 | case .Transition(let transition) = transitionItem where ( options.forceTransition || cacheType == .None) { 134 | #if !os(OSX) 135 | UIView.transitionWithView(sSelf, duration: 0.0, options: [], 136 | animations: { 137 | indicator?.kf_stopAnimating() 138 | }, 139 | completion: { finished in 140 | UIView.transitionWithView(sSelf, duration: transition.duration, 141 | options: [transition.animationOptions, .AllowUserInteraction], 142 | animations: { 143 | // Set image property in the animation. 144 | transition.animations?(sSelf, image) 145 | }, 146 | completion: { finished in 147 | transition.completion?(finished) 148 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 149 | }) 150 | }) 151 | #endif 152 | } else { 153 | indicator?.kf_stopAnimating() 154 | sSelf.image = image 155 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 156 | } 157 | } 158 | }) 159 | 160 | kf_setImageTask(task) 161 | 162 | return task 163 | } 164 | } 165 | 166 | extension ImageView { 167 | func shouldPreloadAllGIF() -> Bool { 168 | return true 169 | } 170 | } 171 | 172 | extension ImageView { 173 | /** 174 | Cancel the image download task bounded to the image view if it is running. 175 | Nothing will happen if the downloading has already finished. 176 | */ 177 | public func kf_cancelDownloadTask() { 178 | kf_imageTask?.downloadTask?.cancel() 179 | } 180 | } 181 | 182 | // MARK: - Associated Object 183 | private var lastURLKey: Void? 184 | private var indicatorKey: Void? 185 | private var showIndicatorWhenLoadingKey: Void? 186 | private var imageTaskKey: Void? 187 | 188 | extension ImageView { 189 | /// Get the image URL binded to this image view. 190 | public var kf_webURL: NSURL? { 191 | return objc_getAssociatedObject(self, &lastURLKey) as? NSURL 192 | } 193 | 194 | private func kf_setWebURL(URL: NSURL) { 195 | objc_setAssociatedObject(self, &lastURLKey, URL, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 196 | } 197 | 198 | /// Whether show an animating indicator when the image view is loading an image or not. 199 | /// Default is false. 200 | public var kf_showIndicatorWhenLoading: Bool { 201 | get { 202 | if let result = objc_getAssociatedObject(self, &showIndicatorWhenLoadingKey) as? NSNumber { 203 | return result.boolValue 204 | } else { 205 | return false 206 | } 207 | } 208 | 209 | set { 210 | if kf_showIndicatorWhenLoading == newValue { 211 | return 212 | } else { 213 | if newValue { 214 | 215 | #if os(OSX) 216 | let indicator = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) 217 | indicator.controlSize = .SmallControlSize 218 | indicator.style = .SpinningStyle 219 | #else 220 | #if os(tvOS) 221 | let indicatorStyle = UIActivityIndicatorViewStyle.White 222 | #else 223 | let indicatorStyle = UIActivityIndicatorViewStyle.Gray 224 | #endif 225 | let indicator = UIActivityIndicatorView(activityIndicatorStyle:indicatorStyle) 226 | indicator.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleBottomMargin, .FlexibleTopMargin] 227 | #endif 228 | 229 | indicator.kf_center = CGPoint(x: CGRectGetMidX(bounds), y: CGRectGetMidY(bounds)) 230 | indicator.hidden = true 231 | 232 | self.addSubview(indicator) 233 | 234 | kf_setIndicator(indicator) 235 | } else { 236 | kf_indicator?.removeFromSuperview() 237 | kf_setIndicator(nil) 238 | } 239 | 240 | objc_setAssociatedObject(self, &showIndicatorWhenLoadingKey, NSNumber(bool: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 241 | } 242 | } 243 | } 244 | 245 | /// The indicator view showing when loading. This will be `nil` if `kf_showIndicatorWhenLoading` is false. 246 | /// You may want to use this to set the indicator style or color when you set `kf_showIndicatorWhenLoading` to true. 247 | public var kf_indicator: IndicatorView? { 248 | return objc_getAssociatedObject(self, &indicatorKey) as? IndicatorView 249 | } 250 | 251 | private func kf_setIndicator(indicator: IndicatorView?) { 252 | objc_setAssociatedObject(self, &indicatorKey, indicator, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 253 | } 254 | 255 | private var kf_imageTask: RetrieveImageTask? { 256 | return objc_getAssociatedObject(self, &imageTaskKey) as? RetrieveImageTask 257 | } 258 | 259 | private func kf_setImageTask(task: RetrieveImageTask?) { 260 | objc_setAssociatedObject(self, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 261 | } 262 | } 263 | 264 | 265 | extension IndicatorView { 266 | func kf_startAnimating() { 267 | #if os(OSX) 268 | startAnimation(nil) 269 | #else 270 | startAnimating() 271 | #endif 272 | hidden = false 273 | } 274 | 275 | func kf_stopAnimating() { 276 | #if os(OSX) 277 | stopAnimation(nil) 278 | #else 279 | stopAnimating() 280 | #endif 281 | hidden = true 282 | } 283 | 284 | #if os(OSX) 285 | var kf_center: CGPoint { 286 | get { 287 | return CGPoint(x: frame.origin.x + frame.size.width / 2.0, y: frame.origin.y + frame.size.height / 2.0 ) 288 | } 289 | set { 290 | 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) 291 | frame = newFrame 292 | } 293 | } 294 | #else 295 | var kf_center: CGPoint { 296 | get { 297 | return center 298 | } 299 | set { 300 | center = newValue 301 | } 302 | } 303 | #endif 304 | } 305 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // If task is canceled before the download task started (which means the `downloadTask` is nil), 41 | // the download task should not begin. 42 | var cancelledBeforeDownlodStarting: Bool = false 43 | 44 | /// The disk retrieve task in this image task. Kingfisher will try to look up in cache first. This task represent the cache search task. 45 | public var diskRetrieveTask: RetrieveImageDiskTask? 46 | 47 | /// The network retrieve task in this image task. 48 | public var downloadTask: RetrieveImageDownloadTask? 49 | 50 | /** 51 | Cancel current task. If this task does not begin or already done, do nothing. 52 | */ 53 | public func cancel() { 54 | // From Xcode 7 beta 6, the `dispatch_block_cancel` will crash at runtime. 55 | // It fixed in Xcode 7.1. 56 | // See https://github.com/onevcat/Kingfisher/issues/99 for more. 57 | if let diskRetrieveTask = diskRetrieveTask { 58 | dispatch_block_cancel(diskRetrieveTask) 59 | } 60 | 61 | if let downloadTask = downloadTask { 62 | downloadTask.cancel() 63 | } else { 64 | cancelledBeforeDownlodStarting = true 65 | } 66 | } 67 | } 68 | 69 | /// Error domain of Kingfisher 70 | public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error" 71 | 72 | private let instance = KingfisherManager() 73 | 74 | /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache. 75 | /// You can use this class to retrieve an image via a specified URL from web or cache. 76 | public class KingfisherManager { 77 | 78 | /// Shared manager used by the extensions across Kingfisher. 79 | public class var sharedManager: KingfisherManager { 80 | return instance 81 | } 82 | 83 | /// Cache used by this manager 84 | public var cache: ImageCache 85 | 86 | /// Downloader used by this manager 87 | public var downloader: ImageDownloader 88 | 89 | /** 90 | Default init method 91 | 92 | - returns: A Kingfisher manager object with default cache, default downloader, and default prefetcher. 93 | */ 94 | public convenience init() { 95 | self.init(downloader: ImageDownloader.defaultDownloader, cache: ImageCache.defaultCache) 96 | } 97 | 98 | init(downloader: ImageDownloader, cache: ImageCache) { 99 | self.downloader = downloader 100 | self.cache = cache 101 | } 102 | 103 | /** 104 | Get an image with resource. 105 | If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first. 106 | If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`. 107 | These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more. 108 | 109 | - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`. 110 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 111 | - parameter progressBlock: Called every time downloaded data changed. This could be used as a progress UI. 112 | - parameter completionHandler: Called when the whole retrieving process finished. 113 | 114 | - returns: A `RetrieveImageTask` task object. You can use this object to cancel the task. 115 | */ 116 | public func retrieveImageWithResource(resource: Resource, 117 | optionsInfo: KingfisherOptionsInfo?, 118 | progressBlock: DownloadProgressBlock?, 119 | completionHandler: CompletionHandler?) -> RetrieveImageTask 120 | { 121 | let task = RetrieveImageTask() 122 | 123 | if let optionsInfo = optionsInfo where optionsInfo.forceRefresh { 124 | downloadAndCacheImageWithURL(resource.downloadURL, 125 | forKey: resource.cacheKey, 126 | retrieveImageTask: task, 127 | progressBlock: progressBlock, 128 | completionHandler: completionHandler, 129 | options: optionsInfo) 130 | } else { 131 | tryToRetrieveImageFromCacheForKey(resource.cacheKey, 132 | withURL: resource.downloadURL, 133 | retrieveImageTask: task, 134 | progressBlock: progressBlock, 135 | completionHandler: completionHandler, 136 | options: optionsInfo) 137 | } 138 | 139 | return task 140 | } 141 | 142 | /** 143 | Get an image with `URL.absoluteString` as the key. 144 | If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first. 145 | If not found, it will download the image at URL and cache it with `URL.absoluteString` value as its key. 146 | 147 | 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. 148 | 149 | These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more. 150 | 151 | - parameter URL: The image URL. 152 | - parameter optionsInfo: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more. 153 | - parameter progressBlock: Called every time downloaded data changed. This could be used as a progress UI. 154 | - parameter completionHandler: Called when the whole retrieving process finished. 155 | 156 | - returns: A `RetrieveImageTask` task object. You can use this object to cancel the task. 157 | */ 158 | public func retrieveImageWithURL(URL: NSURL, 159 | optionsInfo: KingfisherOptionsInfo?, 160 | progressBlock: DownloadProgressBlock?, 161 | completionHandler: CompletionHandler?) -> RetrieveImageTask 162 | { 163 | return retrieveImageWithResource(Resource(downloadURL: URL), optionsInfo: optionsInfo, progressBlock: progressBlock, completionHandler: completionHandler) 164 | } 165 | 166 | func downloadAndCacheImageWithURL(URL: NSURL, 167 | forKey key: String, 168 | retrieveImageTask: RetrieveImageTask, 169 | progressBlock: DownloadProgressBlock?, 170 | completionHandler: CompletionHandler?, 171 | options: KingfisherOptionsInfo?) -> RetrieveImageDownloadTask? 172 | { 173 | let downloader = options?.downloader ?? self.downloader 174 | return downloader.downloadImageWithURL(URL, retrieveImageTask: retrieveImageTask, options: options, 175 | progressBlock: { receivedSize, totalSize in 176 | progressBlock?(receivedSize: receivedSize, totalSize: totalSize) 177 | }, 178 | completionHandler: { image, error, imageURL, originalData in 179 | 180 | let targetCache = options?.targetCache ?? self.cache 181 | if let error = error where error.code == KingfisherError.NotModified.rawValue { 182 | // Not modified. Try to find the image from cache. 183 | // (The image should be in cache. It should be guaranteed by the framework users.) 184 | targetCache.retrieveImageForKey(key, options: options, completionHandler: { (cacheImage, cacheType) -> () in 185 | completionHandler?(image: cacheImage, error: nil, cacheType: cacheType, imageURL: URL) 186 | 187 | }) 188 | return 189 | } 190 | 191 | if let image = image, originalData = originalData { 192 | targetCache.storeImage(image, originalData: originalData, forKey: key, toDisk: !(options?.cacheMemoryOnly ?? false), completionHandler: nil) 193 | } 194 | 195 | completionHandler?(image: image, error: error, cacheType: .None, imageURL: URL) 196 | 197 | }) 198 | } 199 | 200 | func tryToRetrieveImageFromCacheForKey(key: String, 201 | withURL URL: NSURL, 202 | retrieveImageTask: RetrieveImageTask, 203 | progressBlock: DownloadProgressBlock?, 204 | completionHandler: CompletionHandler?, 205 | options: KingfisherOptionsInfo?) 206 | { 207 | let diskTaskCompletionHandler: CompletionHandler = { (image, error, cacheType, imageURL) -> () in 208 | // Break retain cycle created inside diskTask closure below 209 | retrieveImageTask.diskRetrieveTask = nil 210 | completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL) 211 | } 212 | 213 | let targetCache = options?.targetCache ?? cache 214 | let diskTask = targetCache.retrieveImageForKey(key, options: options, 215 | completionHandler: { image, cacheType in 216 | if image != nil { 217 | diskTaskCompletionHandler(image: image, error: nil, cacheType:cacheType, imageURL: URL) 218 | } else { 219 | self.downloadAndCacheImageWithURL(URL, 220 | forKey: key, 221 | retrieveImageTask: retrieveImageTask, 222 | progressBlock: progressBlock, 223 | completionHandler: diskTaskCompletionHandler, 224 | options: options) 225 | } 226 | }) 227 | retrieveImageTask.diskRetrieveTask = diskTask 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Kingfisher (2.4.1) 3 | 4 | DEPENDENCIES: 5 | - Kingfisher 6 | 7 | SPEC CHECKSUMS: 8 | Kingfisher: 41aed5b6c1ed8fa98cf6d75283ce89cf7890f517 9 | 10 | PODFILE CHECKSUM: 1774de14a9dba58ff0d5da6928454319945db864 11 | 12 | COCOAPODS: 1.0.1 13 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/zeroj.xcuserdatad/xcschemes/Kingfisher.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/zeroj.xcuserdatad/xcschemes/Pods-ImageBrowser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/zeroj.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Kingfisher.xcscheme 8 | 9 | isShown 10 | 11 | 12 | Pods-ImageBrowser.xcscheme 13 | 14 | isShown 15 | 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | E96E13C4D3B5B5FC0651440734C2BF4D 21 | 22 | primary 23 | 24 | 25 | FEE48F66B0213627698074E57F2EE697 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /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.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Kingfisher : NSObject 3 | @end 4 | @implementation PodsDummy_Kingfisher 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/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 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser-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 | Generated by CocoaPods - https://cocoapods.org 30 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser-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 | Generated by CocoaPods - https://cocoapods.org 48 | Title 49 | 50 | Type 51 | PSGroupSpecifier 52 | 53 | 54 | StringsTable 55 | Acknowledgements 56 | Title 57 | Acknowledgements 58 | 59 | 60 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_ImageBrowser : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_ImageBrowser 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser-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 | fi 89 | if [[ "$CONFIGURATION" == "Release" ]]; then 90 | install_framework "$BUILT_PRODUCTS_DIR/Kingfisher/Kingfisher.framework" 91 | fi 92 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser-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} ${TARGET_DEVICE_ARGS}" 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}" ${TARGET_DEVICE_ARGS} 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 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double Pods_ImageBrowserVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char Pods_ImageBrowserVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser.debug.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" 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" 6 | OTHER_LDFLAGS = $(inherited) -framework "Kingfisher" 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 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_ImageBrowser { 2 | umbrella header "Pods-ImageBrowser-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ImageBrowser/Pods-ImageBrowser.release.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" 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" 6 | OTHER_LDFLAGS = $(inherited) -framework "Kingfisher" 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhotoBrowser 2 | --- 3 | #####providing an easy way to reach the effect that looking through photos likes the system photo app(快速集成图片浏览器, 支持网络, 本地,以及各种手势) 4 | 5 | ---- 6 | 7 | ##使用示例效果 8 | 9 | ![网络图片.gif](http://upload-images.jianshu.io/upload_images/1271831-a188a56a9ac4e4be.gif?imageMogr2/auto-orient/strip) 10 | ![最终效果.gif](http://upload-images.jianshu.io/upload_images/1271831-f9a69daadeef8e08.gif?imageMogr2/auto-orient/strip) 11 | 12 | 13 | ----- 14 | 15 | ### 可以简单快速灵活的实现上图中的效果 16 | ### 注意, 代码依赖 Kingfisher, 请先在您的项目中添加Kingfisher框架 17 | ### attention please PhotoBrowser relys on 'Kingfisher' 18 | 19 | --- 20 | 21 | ## Feature 22 | * providing an easy way to reach the effect that looking through photos likes the system photo app 23 | * support rotation 24 | 25 | ### 书写思路移步 26 | ###[简书1](http://www.jianshu.com/p/331c24bd263e) 27 | 28 | --- 29 | 30 | ## Requirements 31 | 32 | * iOS 8.0+ 33 | * Xcode 7.3 or above 34 | 35 | ## Installation 36 | 37 | * CocoaPods 38 | add these lines to your project's file 'Podfile' 39 | 40 | source 'https://github.com/CocoaPods/Specs.git' 41 | 42 | platform :ios, '8.0' 43 | use_frameworks! 44 | 45 | pod 'ZJPhotoBrowser', '~> 0.1.0' 46 | 47 | * 直接下载将下载文件的PhotoBrowser文件夹下的文件拖进您的项目中就可以使用了 48 | * or drag the downloaded file 'PhotoBrowser' to your project 49 | 50 | ###Usage 51 | --- 52 | 一. 本地图片使用示例(load local images' example) 53 | 54 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 55 | 1. 设置photoModels 56 | func setPhoto() -> [PhotoModel] { 57 | var photos: [PhotoModel] = [] 58 | for (index, image) in images.enumerate() { 59 | // 这个方法只能返回可见的cell, 如果不可见, 返回值为nil 60 | let cell = collectionView.cellForItemAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TestCell 61 | let sourceView = cell?.imageView 62 | // 在这里设置本地图片的photoModel, 注意指定的sourceImageView是图片的imageView,用来执行动画和设置初始图片 63 | let photoModel = PhotoModel(localImage: image, sourceImageView: sourceView, description: nil) 64 | photos.append(photoModel) 65 | } 66 | return photos 67 | } 68 | 69 | // 2. 初始化PhotoBrowser 70 | let photoBrowser = PhotoBrowser(photoModels: setPhoto()) 71 | // 3. 设置初始的页数 72 | photoBrowser.showWithBeginPage(indexPath.row) 73 | } 74 | 75 | ---- 76 | 77 | 二. 网络图片使用(load network images' example) 78 | 79 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 80 | 1. 设置photoModels 81 | func setPhoto() -> [PhotoModel] { 82 | var photos: [PhotoModel] = [] 83 | for (index, photoURLString) in photosURLString.enumerate() { 84 | // 这个方法只能返回可见的cell, 如果不可见, 返回值为nil 85 | let cell = collectionView.cellForItemAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TestCell 86 | let sourceView = cell?.imageView 87 | 88 | // 这里设置网络图片的photoModel 注意指定的sourceImageView是图片的imageView,用来执行动画和设置初始图片 89 | let photoModel = PhotoModel(imageUrlString: photoURLString, sourceImageView: sourceView, description: nil) 90 | photos.append(photoModel) 91 | } 92 | return photos 93 | } 94 | 95 | // 2. 初始化PhotoBrowser 96 | let photoBrowser = PhotoBrowser(photoModels: setPhoto()) 97 | // 3. 设置初始的页数 98 | photoBrowser.showWithBeginPage(indexPath.row) 99 | } 100 | 101 | --- 102 | 103 | 三. 使用代理,动态更新sourceImageView和进行更多的操作(using delegate to do more useful work) 104 | 105 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 106 | 1. 设置photoModels 107 | func setPhoto() -> [PhotoModel] { 108 | var photos: [PhotoModel] = [] 109 | for photoURLString in photosURLString { 110 | // 初始化不设置sourceImageView,也可以设置, 如果sourceImageView是不需要动态改变的, 那么推荐不需要代理设置sourceImageView 111 | // 而在代理方法中动态更新,将会覆盖原来设置的sourceImageView 112 | let photoModel = PhotoModel(imageUrlString: photoURLString, sourceImageView: nil, description: nil) 113 | photos.append(photoModel) 114 | } 115 | return photos 116 | } 117 | // 2. 初始化PhotoBrowser 118 | let photoBrowser = PhotoBrowser(photoModels: setPhoto()) 119 | // 3. 指定代理 120 | photoBrowser.delegate = self 121 | // 4. 设置初始的页数 122 | photoBrowser.showWithBeginPage(indexPath.row) 123 | } 124 | 125 | 126 | 代理使用示例(the delegate's method use example) 127 | --- 128 | // 正在展示的页 129 | // 因为通过 collectionView.cellForItemAtIndexPath(currentIndexPath) 130 | // 这个方法只能获取到当前显示在collectionView中的cell, 否则返回nil, 131 | // 所以如果是在浏览图片的时候的页数对应的currentIndexPath已经不在collectionView的可见cell范围内 132 | // 返回的将是nil, 就不能准确的获取到 sourceImageView 133 | // 故在每次显示图片的时候判断如果对应的sourceImageView cell已经不可见 134 | // 那么就更新collectionView的位置, 滚动到相应的cell 135 | // 当然更新collectionView位置的方法很多, 这里只是示例 136 | func photoBrowserDidDisplayPage(currentPage: Int, totalPages: Int) { 137 | let visibleIndexPaths = collectionView.indexPathsForVisibleItems() 138 | 139 | let currentIndexPath = NSIndexPath(forRow: currentPage, inSection: 0) 140 | if !visibleIndexPaths.contains(currentIndexPath) { 141 | collectionView.scrollToItemAtIndexPath(currentIndexPath, atScrollPosition: .Top, animated: false) 142 | } 143 | } 144 | // 获取动态改变的sourceImageView 145 | func sourceImageViewForCurrentIndex(index: Int) -> UIImageView? { 146 | 147 | let currentIndexPath = NSIndexPath(forRow: index, inSection: 0) 148 | 149 | let cell = collectionView.cellForItemAtIndexPath(currentIndexPath) as? TestCell 150 | let sourceView = cell?.imageView 151 | return sourceView 152 | } 153 | 154 | 155 | 156 | 157 | 158 | #### 如果你在使用中遇到问题: 可以通过[简书](http://www.jianshu.com/users/fb31a3d1ec30/latest_articles)私信给我 159 | 160 | ## License 161 | 162 | PhotoBrowser is released under the MIT license. See LICENSE for details. -------------------------------------------------------------------------------- /ZJPhotoBrowser.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ZJPhotoBrowser" 3 | s.version = "0.1.0" 4 | s.summary = "PhotoBrowser provides an easy way to reach the effect that looking through photos likes the system photo app and also supports rotation" 5 | s.homepage = "https://github.com/jasnig/PhotoBrowser" 6 | s.license = { :type => "MIT" } 7 | s.authors = { "ZeroJ" => "854136959@qq.com" } 8 | 9 | s.requires_arc = true 10 | s.platform = :ios 11 | s.platform = :ios, "8.0" 12 | s.source = { :git => "https://github.com/jasnig/PhotoBrowser.git", :tag => s.version } 13 | s.framework = "UIKit" 14 | s.dependency 'Kingfisher' 15 | s.source_files = "ImageBrowser/PhotoBrowser/*.swift" 16 | end --------------------------------------------------------------------------------