├── 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 | 
10 | 
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
--------------------------------------------------------------------------------