├── AAImagePickerController
├── check.png
├── take_photo.png
└── AAImagePickerController.swift
├── AAImagePickerControllerDemo.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── .gitignore
├── AAImagePickerControllerDemoTests
├── Info.plist
└── AAImagePickerControllerDemoTests.swift
├── AAImagePickerControllerDemo
├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── ViewController.swift
├── AppDelegate.swift
└── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── README.md
└── AKPickerView.swift
/AAImagePickerController/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anas10/AAImagePickerController/HEAD/AAImagePickerController/check.png
--------------------------------------------------------------------------------
/AAImagePickerController/take_photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anas10/AAImagePickerController/HEAD/AAImagePickerController/take_photo.png
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Swift ###
2 | # Xcode
3 | #
4 | build/
5 | *.pbxuser
6 | !default.pbxuser
7 | *.mode1v3
8 | !default.mode1v3
9 | *.mode2v3
10 | !default.mode2v3
11 | *.perspectivev3
12 | !default.perspectivev3
13 | xcuserdata
14 | *.xccheckout
15 | *.moved-aside
16 | DerivedData
17 | *.hmap
18 | *.ipa
19 | *.xcuserstate
20 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemoTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | co.aitali.$(PRODUCT_NAME:rfc1034identifier)
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 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemoTests/AAImagePickerControllerDemoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AAImagePickerControllerDemoTests.swift
3 | // AAImagePickerControllerDemoTests
4 | //
5 | // Created by Anas perso on 16/05/15.
6 | // Copyright (c) 2015 Anas. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 |
12 | class AAImagePickerControllerDemoTests: 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 | XCTAssert(true, "Pass")
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 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "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 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | co.aitali.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // AAImagePickerControllerDemo
4 | //
5 | // Created by Anas perso on 16/05/15.
6 | // Copyright (c) 2015 Anas. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet weak var scrollView: UIScrollView!
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 | // Do any additional setup after loading the view, typically from a nib.
18 | }
19 |
20 | override func didReceiveMemoryWarning() {
21 | super.didReceiveMemoryWarning()
22 | // Dispose of any resources that can be recreated.
23 | }
24 |
25 | @IBAction func showPicker(sender: AnyObject) {
26 | let pc = AAImagePickerController()
27 | pc.pickerDelegate = self
28 | pc.allowsMultipleSelection = true
29 | pc.maximumNumberOfSelection = 6
30 | pc.numberOfColumnInPortrait = 4
31 | pc.numberOfColumnInLandscape = 7
32 | pc.showTakePhoto = true
33 | self.presentViewController(pc, animated: true, completion: nil)
34 | }
35 | }
36 |
37 | extension ViewController : AAImagePickerControllerDelegate {
38 | func imagePickerControllerDidCancel() {
39 | self.dismissViewControllerAnimated(true, completion: nil)
40 | }
41 |
42 | func imagePickerControllerDidFinishSelection(images: [ImageItem]) {
43 | self.dismissViewControllerAnimated(true, completion: nil)
44 | println("\(images.count) selected items")
45 | scrollView.subviews.map({ $0.removeFromSuperview() })
46 | for (index, item) in enumerate(images) {
47 | let imageHeight: CGFloat = scrollView.bounds.height / 2
48 | let imageView = UIImageView(image: item.image)
49 | imageView.contentMode = UIViewContentMode.ScaleAspectFit
50 | imageView.frame = CGRect(x: 0, y: CGFloat(index) * imageHeight, width: scrollView.bounds.width, height: imageHeight)
51 | scrollView.addSubview(imageView)
52 |
53 | }
54 | scrollView.contentSize.height = CGRectGetMaxY((scrollView.subviews.last as! UIView).frame)
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // AAImagePickerControllerDemo
4 | //
5 | // Created by Anas perso on 16/05/15.
6 | // Copyright (c) 2015 Anas. 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 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AAImagePickerController
2 | Custom ImagePickerController with camera option, multiselect and quick album selection
3 |
4 | *Screenshots comming soon*
5 |
6 |
7 | ## Features
8 |
9 | - Allows multiple selection of photos
10 | - Allows to take a picture
11 | - Allow easy selection of an album
12 | - Customizable (number of columns, maximumSelection, etc.)
13 | - Supports both portrait mode and landscape mode
14 | - Fast and memory-efficient scrolling
15 | - Compatible with all iPhone and iPad (Tested from iOS 7.0)
16 |
17 | ## Example
18 |
19 | let pc = AAImagePickerController()
20 | pc.pickerDelegate = self
21 | pc.allowsMultipleSelection = true // Default value is true
22 | pc.maximumNumberOfSelection = 6 // Default value is 0 (unlimited)
23 | pc.numberOfColumnInPortrait = 5 // Default value is 4
24 | pc.numberOfColumnInLandscape = 10 // Default value is 7
25 |
26 | self.presentViewController(pc, animated: true, completion: nil)
27 |
28 |
29 | ## Installation
30 |
31 | ### CocoaPods
32 |
33 | *Comming soon*
34 |
35 | ### Carthage
36 |
37 | *Comming soon*
38 |
39 | ### Manual
40 |
41 | 1. Add `AAImagePickerController.swift`, `check.png`, `take_photo.png` and `AKPickerView.swift` to your project
42 | 2. That's all you can use it!
43 |
44 | ## Usage
45 |
46 | ### Basic
47 |
48 | 1. Implement `AAImagePickerControllerDelegate` methods
49 | 2. Initialize a new `AAImagePickerController` object
50 | 3. Set `self` to the `delegate` property
51 | 4. Show the picker by using `presentViewController:animated:completion:`
52 | ```
53 | let pc = AAImagePickerController()
54 | pc.pickerDelegate = self
55 |
56 | self.presentViewController(pc, animated: true, completion: nil)
57 | ```
58 |
59 | ### Delegate Methods
60 |
61 | #### Getting the selected items
62 |
63 | Implement `imagePickerControllerDidFinishSelection` to get the items selected by the user.
64 | This method will be called when the user finishes selecting images.
65 |
66 | func imagePickerControllerDidFinishSelection(images: [ImageItem]) {
67 | for (index, item) in enumerate(images) {
68 | let imageView = UIImageView(image: item.image)
69 | ...
70 | }
71 | self.dismissViewControllerAnimated(true, completion: nil)
72 | }
73 |
74 |
75 | #### Getting notified when the user cancels
76 |
77 | Implement `imagePickerControllerDidCancel` to get notified when the user hits "Cancel" button.
78 |
79 | func imagePickerControllerDidCancel() {
80 | self.dismissViewControllerAnimated(true, completion: nil)
81 | }
82 |
83 | ### Customization
84 |
85 | #### Selection mode
86 |
87 | When `allowsMultipleSelection` is `true`, the user can select multiple photos.
88 | The default value is `true`.
89 |
90 | pc.allowsMultipleSelection = true
91 |
92 | You can limit the number of selection `maximumNumberOfSelection` property.
93 | The default value is `0`, which means the number of selection is unlimited.
94 |
95 | pc.maximumNumberOfSelection = 6
96 |
97 | #### Number of columns
98 |
99 | Use `numberOfColumnInPortrait` and `numberOfColumnInLandscape` to change the number of columns in the specified orientation.
100 | The code below shows the default value.
101 |
102 | pc.numberOfColumnInPortrait = 4
103 | pc.numberOfColumnInLandscape = 7
104 |
105 |
106 | ## License
107 |
108 | The MIT License (MIT)
109 |
110 | Copyright (c) 2015 Anas AIT-ALI
111 |
112 | Permission is hereby granted, free of charge, to any person obtaining a copy
113 | of this software and associated documentation files (the "Software"), to deal
114 | in the Software without restriction, including without limitation the rights
115 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
116 | copies of the Software, and to permit persons to whom the Software is
117 | furnished to do so, subject to the following conditions:
118 |
119 | The above copyright notice and this permission notice shall be included in all
120 | copies or substantial portions of the Software.
121 |
122 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
123 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
124 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
125 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
126 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
127 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
128 | SOFTWARE.
129 |
--------------------------------------------------------------------------------
/AAImagePickerController/AAImagePickerController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AAImagePickerController.swift
3 | // AAImagePickerControllerDemo
4 | //
5 | // Created by Anas perso on 16/05/15.
6 | // Copyright (c) 2015 Anas. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AssetsLibrary
11 |
12 | // MARK: - Constants
13 | let AAImageCellIdentifier = "AAImageCellIdentifier"
14 | let AATakePhotoCellIdentifier = "AATakePhotoCellIdentifier"
15 |
16 | // MARK: - AAImagePickerControllerDelegate
17 | protocol AAImagePickerControllerDelegate : NSObjectProtocol {
18 | func imagePickerControllerDidFinishSelection(images: [ImageItem])
19 | func imagePickerControllerDidCancel()
20 | }
21 |
22 | // MARK: - Models
23 | class ImageGroup {
24 | var name: String!
25 | var group: ALAssetsGroup!
26 | }
27 |
28 | class ImageItem : NSObject {
29 | private var originalAsset: ALAsset?
30 | var thumbnailImage: UIImage?
31 | lazy var image: UIImage? = {
32 | if let origAsset = self.originalAsset {
33 | return self.fullScreenImage
34 | } else {
35 | return self.image
36 | }
37 | }()
38 | lazy var fullScreenImage: UIImage? = {
39 | return UIImage(CGImage: self.originalAsset?.defaultRepresentation().fullScreenImage().takeUnretainedValue())
40 | }()
41 | lazy var fullResolutionImage: UIImage? = {
42 | return UIImage(CGImage: self.originalAsset?.defaultRepresentation().fullResolutionImage().takeUnretainedValue())
43 | }()
44 | var url: NSURL?
45 |
46 | override func isEqual(object: AnyObject?) -> Bool {
47 | let other = object as! ImageItem
48 | if let url = self.url, otherUrl = other.url {
49 | return url.isEqual(otherUrl)
50 | }
51 | return false
52 | }
53 | }
54 |
55 | // MARK: - AAImagePickerController
56 | class AAImagePickerController : UINavigationController {
57 |
58 | internal weak var pickerDelegate : AAImagePickerControllerDelegate?
59 | var listController : AAImagePickerControllerList!
60 | var allowsMultipleSelection : Bool = true
61 | var maximumNumberOfSelection : Int = 0
62 | var numberOfColumnInPortrait : Int = 4
63 | var numberOfColumnInLandscape : Int = 7
64 | var showTakePhoto : Bool = true
65 | var selectionColor = UIColor.clearColor() {
66 | didSet {
67 | self.listController.selectionColor = selectionColor
68 | }
69 | }
70 | internal var selectedItems = [ImageItem]() {
71 | willSet(newValue) {
72 | let currentCount = selectedItems.count
73 | println("selectedItems = \(currentCount)")
74 | if newValue.count == 0 {
75 | addBtn.title = "Add"
76 | addBtn.enabled = false
77 | } else if (newValue.count != currentCount) {
78 | addBtn.title = "Add (\(newValue.count))"
79 | addBtn.enabled = true
80 | }
81 | }
82 | }
83 | lazy internal var addBtn : UIBarButtonItem = {
84 | let btn : UIBarButtonItem = UIBarButtonItem(title: "Add", style: .Done, target: self, action: "addAction")
85 | btn.enabled = false
86 | return btn
87 | }()
88 |
89 | // MARK: Initialization
90 | convenience init() {
91 | let aListController = AAImagePickerControllerList()
92 | self.init(rootViewController: aListController)
93 | listController = aListController
94 | }
95 |
96 | // MARK: View lifecycle
97 | override func viewDidLoad() {
98 | super.viewDidLoad()
99 | }
100 |
101 | // MARK: UINavigationController
102 | override func pushViewController(viewController: UIViewController, animated: Bool) {
103 | super.pushViewController(viewController, animated: animated)
104 |
105 | self.topViewController.navigationItem.rightBarButtonItem = addBtn
106 |
107 | if self.viewControllers.count == 1 &&
108 | self.topViewController?.navigationItem.leftBarButtonItem == nil {
109 | self.topViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Cancel, target: self, action: "cancelAction")
110 | }
111 | }
112 |
113 | // MARK: Delegate
114 | func cancelAction() {
115 | if let aDelegate = self.pickerDelegate {
116 | aDelegate.imagePickerControllerDidCancel()
117 | }
118 | }
119 |
120 | func addAction() {
121 | if let delegate = self.pickerDelegate {
122 | delegate.imagePickerControllerDidFinishSelection(selectedItems)
123 | }
124 | }
125 | }
126 |
127 | internal extension UIViewController {
128 | var imagePickerController: AAImagePickerController? {
129 | get {
130 | let nav = self.navigationController
131 | if nav is AAImagePickerController {
132 | return nav as? AAImagePickerController
133 | } else {
134 | return nil
135 | }
136 | }
137 | }
138 | }
139 |
140 | // MARK : AACollectionViewFlowLayout
141 | internal class AACollectionViewFlowLayout : UICollectionViewFlowLayout {
142 | static let interval: CGFloat = 1
143 |
144 | func commonInit() {
145 | self.minimumInteritemSpacing = AACollectionViewFlowLayout.interval
146 | self.minimumLineSpacing = AACollectionViewFlowLayout.interval
147 | }
148 |
149 | override init() {
150 | super.init()
151 | self.commonInit()
152 | }
153 |
154 | required init(coder aDecoder: NSCoder) {
155 | super.init(coder: aDecoder)
156 | self.commonInit()
157 | }
158 | }
159 |
160 | // MARK: - AAImagePickerControllerList
161 | class AAImagePickerControllerList : UICollectionViewController {
162 |
163 | lazy private var albumPickerView = AKPickerView()
164 | lazy private var library: ALAssetsLibrary = {
165 | return ALAssetsLibrary()
166 | }()
167 | lazy private var groups: NSMutableArray = {
168 | return NSMutableArray()
169 | }()
170 | private lazy var imageItems: NSMutableArray = {
171 | return NSMutableArray()
172 | }()
173 | var selectionColor = UIColor(red: 55/255, green: 93/255, blue: 129/255, alpha: 1.0)
174 | var currentGroupSelection : Int?
175 | var accessDeniedView: UIView = {
176 | let label = UILabel()
177 | label.text = "This application doesn't have access to your photos"
178 | label.textAlignment = NSTextAlignment.Center
179 | label.textColor = UIColor.lightGrayColor()
180 | label.numberOfLines = 0
181 | return label
182 | }()
183 |
184 | // MARK: Initialization
185 | override init(collectionViewLayout layout: UICollectionViewLayout) {
186 | super.init(collectionViewLayout: layout)
187 | }
188 |
189 | convenience init() {
190 | let layout = AACollectionViewFlowLayout()
191 | self.init(collectionViewLayout: layout)
192 | }
193 |
194 | required init(coder aDecoder: NSCoder) {
195 | fatalError("init(coder:) has not been implemented")
196 | }
197 |
198 | // MARK: View lifecycle
199 | override func viewDidLoad() {
200 | super.viewDidLoad()
201 |
202 | albumPickerInitialisation()
203 |
204 | collectionView!.backgroundColor = UIColor.whiteColor()
205 | collectionView!.allowsMultipleSelection = imagePickerController!.allowsMultipleSelection
206 | collectionView!.registerClass(AAImagePickerCollectionCell.self, forCellWithReuseIdentifier: AAImageCellIdentifier)
207 | collectionView!.registerClass(AATakePhotoCollectionCell.self, forCellWithReuseIdentifier: AATakePhotoCellIdentifier)
208 | }
209 |
210 | override func viewDidAppear(animated: Bool) {
211 | super.viewDidAppear(animated)
212 |
213 | updateGroups { () -> () in
214 | self.updateImagesList()
215 | }
216 | }
217 |
218 | // MARK: Library methods
219 | func updateGroups(callback: () -> ()) {
220 | library.enumerateGroupsWithTypes(ALAssetsGroupAll, usingBlock: { (group: ALAssetsGroup!, stop: UnsafeMutablePointer) -> Void in
221 | if group != nil {
222 | if group.numberOfAssets() != 0 {
223 | let groupName = group.valueForProperty(ALAssetsGroupPropertyName) as! String
224 | let assetGroup = ImageGroup()
225 | assetGroup.name = groupName
226 | assetGroup.group = group
227 | self.groups.insertObject(assetGroup, atIndex: 0)
228 | }
229 | } else {
230 | self.albumPickerView.reloadData()
231 | callback()
232 | }
233 | }, failureBlock: { (error: NSError!) -> Void in
234 | self.accessDeniedView.frame = self.collectionView!.bounds
235 | self.collectionView!.addSubview(self.accessDeniedView)
236 | })
237 | }
238 |
239 | func updateImagesList() {
240 | let selectedAlbum = self.albumPickerView.selectedItem
241 | let currentGroup = groups[selectedAlbum] as! ImageGroup
242 |
243 | self.imageItems.removeAllObjects()
244 | currentGroup.group.setAssetsFilter(ALAssetsFilter.allPhotos())
245 | currentGroup.group.enumerateAssetsUsingBlock { (result: ALAsset!, index: Int, stop: UnsafeMutablePointer) -> Void in
246 | if result != nil {
247 | let item = ImageItem()
248 | item.thumbnailImage = UIImage(CGImage:result.thumbnail()?.takeUnretainedValue())
249 | item.url = result.valueForProperty(ALAssetPropertyAssetURL) as? NSURL
250 | item.originalAsset = result
251 | self.imageItems.insertObject(item, atIndex: 0)
252 | } else {
253 | self.collectionView!.reloadData()
254 | }
255 | }
256 | }
257 |
258 | // MARK: UICollectionViewDataSource
259 | override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
260 | return 1
261 | }
262 |
263 | override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
264 | if imageItems.count > 0 {
265 | return imageItems.count + (imagePickerController?.showTakePhoto == true ? 1 : 0)
266 | }
267 | return 0
268 | }
269 |
270 | override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
271 | if indexPath.row == 0 && imagePickerController?.showTakePhoto == true {
272 | let takePhotoCell = collectionView.dequeueReusableCellWithReuseIdentifier(AATakePhotoCellIdentifier, forIndexPath: indexPath) as! AATakePhotoCollectionCell
273 | animateCell(takePhotoCell, pos: indexPath.row)
274 | return takePhotoCell
275 | } else {
276 | let item = imageItems[indexPath.row - (imagePickerController?.showTakePhoto == true ? 1 : 0)] as! ImageItem
277 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(AAImageCellIdentifier, forIndexPath: indexPath) as! AAImagePickerCollectionCell
278 | cell.thumbnail = item.thumbnailImage
279 | cell.selectionColor = selectionColor
280 | if find(imagePickerController!.selectedItems, item) != nil {
281 | cell.selected = true
282 | collectionView.selectItemAtIndexPath(indexPath, animated: false, scrollPosition: .None)
283 | } else {
284 | cell.selected = false
285 | collectionView.deselectItemAtIndexPath(indexPath, animated: false)
286 | }
287 | animateCell(cell, pos: indexPath.row)
288 | return cell
289 | }
290 | }
291 |
292 | func animateCell(cell: UICollectionViewCell, pos: Int) {
293 | cell.transform = CGAffineTransformMakeScale(0, 0)
294 | UIView.animateWithDuration(0.3, delay: Double(pos) * 0.01, options: UIViewAnimationOptions.CurveEaseInOut,
295 | animations: { () -> Void in
296 | cell.transform = CGAffineTransformMakeScale(1, 1)
297 | }, completion: nil)
298 | }
299 |
300 | // MARK: UICollectionViewDelegate
301 | override func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
302 | let max = imagePickerController!.maximumNumberOfSelection
303 | return max > 0 ? (imagePickerController!.selectedItems.count < max) : true
304 | }
305 |
306 | override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
307 | if indexPath.row == 0 && imagePickerController?.showTakePhoto == true {
308 | collectionView.deselectItemAtIndexPath(indexPath, animated: false)
309 | if UIImagePickerController.isSourceTypeAvailable(.Camera) {
310 | var picker = UIImagePickerController()
311 | picker.delegate = self
312 | picker.allowsEditing = true
313 | picker.sourceType = UIImagePickerControllerSourceType.Camera
314 | self.presentViewController(picker, animated: true, completion: nil)
315 | } else {
316 | let alert = UIAlertView(title: "Error", message: "This device has no camera", delegate: nil, cancelButtonTitle: "Ok")
317 | alert.show()
318 | }
319 | } else {
320 | let item = imageItems[indexPath.row - (imagePickerController?.showTakePhoto == true ? 1 : 0)] as! ImageItem
321 | if find(imagePickerController!.selectedItems, item) == nil {
322 | imagePickerController!.selectedItems.append(item)
323 | }
324 | }
325 | }
326 |
327 | override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
328 | if indexPath.row != 0 {
329 | let item = imageItems[indexPath.row - (imagePickerController?.showTakePhoto == true ? 1 : 0)] as! ImageItem
330 | imagePickerController!.selectedItems.removeAtIndex(find(imagePickerController!.selectedItems, item)!)
331 | }
332 | }
333 |
334 | // MARK : Rotation
335 | override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
336 | super.willRotateToInterfaceOrientation(toInterfaceOrientation, duration: duration)
337 | self.collectionView!.collectionViewLayout.invalidateLayout()
338 | }
339 | }
340 |
341 | extension AAImagePickerControllerList : UICollectionViewDelegateFlowLayout {
342 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
343 | let numberOfColumns : Int
344 | let side : CGFloat
345 | if UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication().statusBarOrientation) {
346 | numberOfColumns = imagePickerController!.numberOfColumnInPortrait
347 | } else {
348 | numberOfColumns = imagePickerController!.numberOfColumnInLandscape
349 | }
350 | side = (CGRectGetWidth(collectionView.frame) - AACollectionViewFlowLayout.interval * CGFloat(numberOfColumns - 1)) / CGFloat(numberOfColumns)
351 | return CGSizeMake(side, side)
352 | }
353 | }
354 |
355 | extension AAImagePickerControllerList : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
356 | func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
357 | let item = ImageItem()
358 | item.image = info[UIImagePickerControllerEditedImage] as? UIImage
359 | item.url = info[UIImagePickerControllerReferenceURL] as? NSURL
360 | imagePickerController!.selectedItems = [item]
361 | imagePickerController!.addAction()
362 | picker.dismissViewControllerAnimated(true, completion:nil)
363 | }
364 |
365 | func imagePickerControllerDidCancel(picker: UIImagePickerController) {
366 | picker.dismissViewControllerAnimated(true, completion: nil)
367 | }
368 | }
369 |
370 | extension AAImagePickerControllerList : AKPickerViewDelegate, AKPickerViewDataSource {
371 |
372 | func albumPickerInitialisation() {
373 | self.albumPickerView.delegate = self
374 | self.albumPickerView.dataSource = self
375 | self.albumPickerView.font = UIFont(name: "HelveticaNeue-Light", size: 20)!
376 | self.albumPickerView.highlightedFont = UIFont(name: "HelveticaNeue", size: 20)!
377 | self.albumPickerView.interitemSpacing = 10.0
378 | self.albumPickerView.viewDepth = 1000.0
379 | self.albumPickerView.pickerViewStyle = .Wheel
380 | self.albumPickerView.maskDisabled = false
381 | self.albumPickerView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
382 | if self.navigationController != nil {
383 | self.albumPickerView.frame = self.navigationController!.navigationBar.bounds
384 | }
385 | self.navigationItem.titleView = self.albumPickerView
386 | }
387 |
388 | func numberOfItemsInPickerView(pickerView: AKPickerView) -> Int {
389 | return groups.count
390 | }
391 |
392 | func pickerView(pickerView: AKPickerView, titleForItem item: Int) -> String {
393 | let assetGroup : ImageGroup = groups[item] as! ImageGroup
394 | return assetGroup.name
395 | }
396 |
397 | func pickerView(pickerView: AKPickerView, didSelectItem item: Int) {
398 | if currentGroupSelection == nil || currentGroupSelection != item {
399 | currentGroupSelection = item
400 | updateImagesList()
401 | }
402 | }
403 | }
404 |
405 | // MARK: - UIImage extension
406 | extension UIImage {
407 | func imageWithColor(color: UIColor) -> UIImage {
408 | let rect = CGRectMake(0, 0, self.size.width, self.size.height)
409 | UIGraphicsBeginImageContext(rect.size)
410 | let context = UIGraphicsGetCurrentContext()
411 | color.set()
412 | CGContextTranslateCTM(context, 0, self.size.height)
413 | CGContextScaleCTM(context, 1.0, -1.0)
414 | CGContextClipToMask(context, rect, self.CGImage)
415 | CGContextFillRect(context, rect)
416 | let image = UIGraphicsGetImageFromCurrentImageContext()
417 | UIGraphicsEndImageContext()
418 | return image
419 | }
420 | }
421 |
422 | // MARK: - AAImagePickerCollectionCell
423 | class AAImagePickerCollectionCell: UICollectionViewCell {
424 | private var imageView = UIImageView()
425 | private var selectedView = UIImageView(image: UIImage(named: "check"))
426 | private var overlay = UIView()
427 |
428 | var selectionColor = UIColor.clearColor() {
429 | didSet {
430 | layer.borderColor = selectionColor.CGColor
431 | // selectedView.image = selectedView.image?.imageWithColor(selectionColor)
432 | }
433 | }
434 |
435 | var thumbnail: UIImage! {
436 | didSet {
437 | self.imageView.image = thumbnail
438 | }
439 | }
440 |
441 | override var selected: Bool {
442 | didSet {
443 | selectedView.hidden = !super.selected
444 | overlay.hidden = !super.selected
445 | layer.borderWidth = super.selected ? 0.6 : 0
446 | }
447 | }
448 |
449 | override init(frame: CGRect) {
450 | super.init(frame: frame)
451 |
452 | imageView.frame = self.bounds
453 | overlay.frame = self.bounds
454 | overlay.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.2)
455 | self.contentView.addSubview(imageView)
456 | self.contentView.addSubview(overlay)
457 | self.contentView.addSubview(selectedView)
458 | }
459 |
460 | required init(coder aDecoder: NSCoder) {
461 | fatalError("init(coder:) has not been implemented")
462 | }
463 |
464 | override func layoutSubviews() {
465 | super.layoutSubviews()
466 |
467 | imageView.frame = self.bounds
468 | overlay.frame = self.bounds
469 | selectedView.frame.size = CGSizeMake(self.contentView.bounds.width / 4, self.contentView.bounds.height / 4)
470 | selectedView.frame.origin = CGPoint(x: self.contentView.bounds.width - selectedView.bounds.width - 5,
471 | y: self.contentView.bounds.height - selectedView.bounds.height - 5)
472 | }
473 | }
474 |
475 | // MARK: - AATakePhotoCollectionCell
476 | class AATakePhotoCollectionCell: UICollectionViewCell {
477 | private var imageView = UIImageView(image: UIImage(named: "take_photo"))
478 |
479 | override init(frame: CGRect) {
480 | super.init(frame: frame)
481 |
482 | imageView.frame = self.bounds
483 | self.contentView.addSubview(imageView)
484 | }
485 |
486 | required init(coder aDecoder: NSCoder) {
487 | fatalError("init(coder:) has not been implemented")
488 | }
489 |
490 | override func layoutSubviews() {
491 | super.layoutSubviews()
492 |
493 | imageView.frame = self.bounds
494 | }
495 | }
496 |
497 |
--------------------------------------------------------------------------------
/AAImagePickerControllerDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 8A678A861B077A480036A1BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A678A851B077A480036A1BC /* AppDelegate.swift */; };
11 | 8A678A881B077A480036A1BC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A678A871B077A480036A1BC /* ViewController.swift */; };
12 | 8A678A8B1B077A480036A1BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A678A891B077A480036A1BC /* Main.storyboard */; };
13 | 8A678A8D1B077A480036A1BC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8A678A8C1B077A480036A1BC /* Images.xcassets */; };
14 | 8A678A901B077A480036A1BC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8A678A8E1B077A480036A1BC /* LaunchScreen.xib */; };
15 | 8A678A9C1B077A490036A1BC /* AAImagePickerControllerDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A678A9B1B077A490036A1BC /* AAImagePickerControllerDemoTests.swift */; };
16 | 8A678AAB1B077AB60036A1BC /* AAImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A678AAA1B077AB60036A1BC /* AAImagePickerController.swift */; };
17 | 8A678AAD1B078CF60036A1BC /* AKPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A678AAC1B078CF60036A1BC /* AKPickerView.swift */; };
18 | 8A678AAF1B07C4C10036A1BC /* take_photo.png in Resources */ = {isa = PBXBuildFile; fileRef = 8A678AAE1B07C4C10036A1BC /* take_photo.png */; };
19 | 8A678AB11B0811C50036A1BC /* check.png in Resources */ = {isa = PBXBuildFile; fileRef = 8A678AB01B0811C50036A1BC /* check.png */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXContainerItemProxy section */
23 | 8A678A961B077A490036A1BC /* PBXContainerItemProxy */ = {
24 | isa = PBXContainerItemProxy;
25 | containerPortal = 8A678A781B077A480036A1BC /* Project object */;
26 | proxyType = 1;
27 | remoteGlobalIDString = 8A678A7F1B077A480036A1BC;
28 | remoteInfo = AAImagePickerControllerDemo;
29 | };
30 | /* End PBXContainerItemProxy section */
31 |
32 | /* Begin PBXFileReference section */
33 | 8A678A801B077A480036A1BC /* AAImagePickerControllerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AAImagePickerControllerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
34 | 8A678A841B077A480036A1BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
35 | 8A678A851B077A480036A1BC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
36 | 8A678A871B077A480036A1BC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
37 | 8A678A8A1B077A480036A1BC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
38 | 8A678A8C1B077A480036A1BC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
39 | 8A678A8F1B077A480036A1BC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
40 | 8A678A951B077A490036A1BC /* AAImagePickerControllerDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AAImagePickerControllerDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 8A678A9A1B077A490036A1BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
42 | 8A678A9B1B077A490036A1BC /* AAImagePickerControllerDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AAImagePickerControllerDemoTests.swift; sourceTree = ""; };
43 | 8A678AAA1B077AB60036A1BC /* AAImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAImagePickerController.swift; sourceTree = ""; };
44 | 8A678AAC1B078CF60036A1BC /* AKPickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKPickerView.swift; sourceTree = ""; };
45 | 8A678AAE1B07C4C10036A1BC /* take_photo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = take_photo.png; sourceTree = ""; };
46 | 8A678AB01B0811C50036A1BC /* check.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = check.png; sourceTree = ""; };
47 | /* End PBXFileReference section */
48 |
49 | /* Begin PBXFrameworksBuildPhase section */
50 | 8A678A7D1B077A480036A1BC /* Frameworks */ = {
51 | isa = PBXFrameworksBuildPhase;
52 | buildActionMask = 2147483647;
53 | files = (
54 | );
55 | runOnlyForDeploymentPostprocessing = 0;
56 | };
57 | 8A678A921B077A490036A1BC /* Frameworks */ = {
58 | isa = PBXFrameworksBuildPhase;
59 | buildActionMask = 2147483647;
60 | files = (
61 | );
62 | runOnlyForDeploymentPostprocessing = 0;
63 | };
64 | /* End PBXFrameworksBuildPhase section */
65 |
66 | /* Begin PBXGroup section */
67 | 8A678A771B077A480036A1BC = {
68 | isa = PBXGroup;
69 | children = (
70 | 8A678AAC1B078CF60036A1BC /* AKPickerView.swift */,
71 | 8A678AA91B077AB60036A1BC /* AAImagePickerController */,
72 | 8A678A821B077A480036A1BC /* AAImagePickerControllerDemo */,
73 | 8A678A981B077A490036A1BC /* AAImagePickerControllerDemoTests */,
74 | 8A678A811B077A480036A1BC /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 8A678A811B077A480036A1BC /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 8A678A801B077A480036A1BC /* AAImagePickerControllerDemo.app */,
82 | 8A678A951B077A490036A1BC /* AAImagePickerControllerDemoTests.xctest */,
83 | );
84 | name = Products;
85 | sourceTree = "";
86 | };
87 | 8A678A821B077A480036A1BC /* AAImagePickerControllerDemo */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 8A678A851B077A480036A1BC /* AppDelegate.swift */,
91 | 8A678A871B077A480036A1BC /* ViewController.swift */,
92 | 8A678A891B077A480036A1BC /* Main.storyboard */,
93 | 8A678A8C1B077A480036A1BC /* Images.xcassets */,
94 | 8A678A8E1B077A480036A1BC /* LaunchScreen.xib */,
95 | 8A678A831B077A480036A1BC /* Supporting Files */,
96 | );
97 | path = AAImagePickerControllerDemo;
98 | sourceTree = "";
99 | };
100 | 8A678A831B077A480036A1BC /* Supporting Files */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 8A678A841B077A480036A1BC /* Info.plist */,
104 | );
105 | name = "Supporting Files";
106 | sourceTree = "";
107 | };
108 | 8A678A981B077A490036A1BC /* AAImagePickerControllerDemoTests */ = {
109 | isa = PBXGroup;
110 | children = (
111 | 8A678A9B1B077A490036A1BC /* AAImagePickerControllerDemoTests.swift */,
112 | 8A678A991B077A490036A1BC /* Supporting Files */,
113 | );
114 | path = AAImagePickerControllerDemoTests;
115 | sourceTree = "";
116 | };
117 | 8A678A991B077A490036A1BC /* Supporting Files */ = {
118 | isa = PBXGroup;
119 | children = (
120 | 8A678A9A1B077A490036A1BC /* Info.plist */,
121 | );
122 | name = "Supporting Files";
123 | sourceTree = "";
124 | };
125 | 8A678AA91B077AB60036A1BC /* AAImagePickerController */ = {
126 | isa = PBXGroup;
127 | children = (
128 | 8A678AB01B0811C50036A1BC /* check.png */,
129 | 8A678AAE1B07C4C10036A1BC /* take_photo.png */,
130 | 8A678AAA1B077AB60036A1BC /* AAImagePickerController.swift */,
131 | );
132 | path = AAImagePickerController;
133 | sourceTree = "";
134 | };
135 | /* End PBXGroup section */
136 |
137 | /* Begin PBXNativeTarget section */
138 | 8A678A7F1B077A480036A1BC /* AAImagePickerControllerDemo */ = {
139 | isa = PBXNativeTarget;
140 | buildConfigurationList = 8A678A9F1B077A490036A1BC /* Build configuration list for PBXNativeTarget "AAImagePickerControllerDemo" */;
141 | buildPhases = (
142 | 8A678A7C1B077A480036A1BC /* Sources */,
143 | 8A678A7D1B077A480036A1BC /* Frameworks */,
144 | 8A678A7E1B077A480036A1BC /* Resources */,
145 | );
146 | buildRules = (
147 | );
148 | dependencies = (
149 | );
150 | name = AAImagePickerControllerDemo;
151 | productName = AAImagePickerControllerDemo;
152 | productReference = 8A678A801B077A480036A1BC /* AAImagePickerControllerDemo.app */;
153 | productType = "com.apple.product-type.application";
154 | };
155 | 8A678A941B077A490036A1BC /* AAImagePickerControllerDemoTests */ = {
156 | isa = PBXNativeTarget;
157 | buildConfigurationList = 8A678AA21B077A490036A1BC /* Build configuration list for PBXNativeTarget "AAImagePickerControllerDemoTests" */;
158 | buildPhases = (
159 | 8A678A911B077A490036A1BC /* Sources */,
160 | 8A678A921B077A490036A1BC /* Frameworks */,
161 | 8A678A931B077A490036A1BC /* Resources */,
162 | );
163 | buildRules = (
164 | );
165 | dependencies = (
166 | 8A678A971B077A490036A1BC /* PBXTargetDependency */,
167 | );
168 | name = AAImagePickerControllerDemoTests;
169 | productName = AAImagePickerControllerDemoTests;
170 | productReference = 8A678A951B077A490036A1BC /* AAImagePickerControllerDemoTests.xctest */;
171 | productType = "com.apple.product-type.bundle.unit-test";
172 | };
173 | /* End PBXNativeTarget section */
174 |
175 | /* Begin PBXProject section */
176 | 8A678A781B077A480036A1BC /* Project object */ = {
177 | isa = PBXProject;
178 | attributes = {
179 | LastUpgradeCheck = 0630;
180 | ORGANIZATIONNAME = Anas;
181 | TargetAttributes = {
182 | 8A678A7F1B077A480036A1BC = {
183 | CreatedOnToolsVersion = 6.3;
184 | };
185 | 8A678A941B077A490036A1BC = {
186 | CreatedOnToolsVersion = 6.3;
187 | TestTargetID = 8A678A7F1B077A480036A1BC;
188 | };
189 | };
190 | };
191 | buildConfigurationList = 8A678A7B1B077A480036A1BC /* Build configuration list for PBXProject "AAImagePickerControllerDemo" */;
192 | compatibilityVersion = "Xcode 3.2";
193 | developmentRegion = English;
194 | hasScannedForEncodings = 0;
195 | knownRegions = (
196 | en,
197 | Base,
198 | );
199 | mainGroup = 8A678A771B077A480036A1BC;
200 | productRefGroup = 8A678A811B077A480036A1BC /* Products */;
201 | projectDirPath = "";
202 | projectRoot = "";
203 | targets = (
204 | 8A678A7F1B077A480036A1BC /* AAImagePickerControllerDemo */,
205 | 8A678A941B077A490036A1BC /* AAImagePickerControllerDemoTests */,
206 | );
207 | };
208 | /* End PBXProject section */
209 |
210 | /* Begin PBXResourcesBuildPhase section */
211 | 8A678A7E1B077A480036A1BC /* Resources */ = {
212 | isa = PBXResourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | 8A678A8B1B077A480036A1BC /* Main.storyboard in Resources */,
216 | 8A678AB11B0811C50036A1BC /* check.png in Resources */,
217 | 8A678A901B077A480036A1BC /* LaunchScreen.xib in Resources */,
218 | 8A678A8D1B077A480036A1BC /* Images.xcassets in Resources */,
219 | 8A678AAF1B07C4C10036A1BC /* take_photo.png in Resources */,
220 | );
221 | runOnlyForDeploymentPostprocessing = 0;
222 | };
223 | 8A678A931B077A490036A1BC /* Resources */ = {
224 | isa = PBXResourcesBuildPhase;
225 | buildActionMask = 2147483647;
226 | files = (
227 | );
228 | runOnlyForDeploymentPostprocessing = 0;
229 | };
230 | /* End PBXResourcesBuildPhase section */
231 |
232 | /* Begin PBXSourcesBuildPhase section */
233 | 8A678A7C1B077A480036A1BC /* Sources */ = {
234 | isa = PBXSourcesBuildPhase;
235 | buildActionMask = 2147483647;
236 | files = (
237 | 8A678A881B077A480036A1BC /* ViewController.swift in Sources */,
238 | 8A678AAB1B077AB60036A1BC /* AAImagePickerController.swift in Sources */,
239 | 8A678AAD1B078CF60036A1BC /* AKPickerView.swift in Sources */,
240 | 8A678A861B077A480036A1BC /* AppDelegate.swift in Sources */,
241 | );
242 | runOnlyForDeploymentPostprocessing = 0;
243 | };
244 | 8A678A911B077A490036A1BC /* Sources */ = {
245 | isa = PBXSourcesBuildPhase;
246 | buildActionMask = 2147483647;
247 | files = (
248 | 8A678A9C1B077A490036A1BC /* AAImagePickerControllerDemoTests.swift in Sources */,
249 | );
250 | runOnlyForDeploymentPostprocessing = 0;
251 | };
252 | /* End PBXSourcesBuildPhase section */
253 |
254 | /* Begin PBXTargetDependency section */
255 | 8A678A971B077A490036A1BC /* PBXTargetDependency */ = {
256 | isa = PBXTargetDependency;
257 | target = 8A678A7F1B077A480036A1BC /* AAImagePickerControllerDemo */;
258 | targetProxy = 8A678A961B077A490036A1BC /* PBXContainerItemProxy */;
259 | };
260 | /* End PBXTargetDependency section */
261 |
262 | /* Begin PBXVariantGroup section */
263 | 8A678A891B077A480036A1BC /* Main.storyboard */ = {
264 | isa = PBXVariantGroup;
265 | children = (
266 | 8A678A8A1B077A480036A1BC /* Base */,
267 | );
268 | name = Main.storyboard;
269 | sourceTree = "";
270 | };
271 | 8A678A8E1B077A480036A1BC /* LaunchScreen.xib */ = {
272 | isa = PBXVariantGroup;
273 | children = (
274 | 8A678A8F1B077A480036A1BC /* Base */,
275 | );
276 | name = LaunchScreen.xib;
277 | sourceTree = "";
278 | };
279 | /* End PBXVariantGroup section */
280 |
281 | /* Begin XCBuildConfiguration section */
282 | 8A678A9D1B077A490036A1BC /* Debug */ = {
283 | isa = XCBuildConfiguration;
284 | buildSettings = {
285 | ALWAYS_SEARCH_USER_PATHS = NO;
286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
287 | CLANG_CXX_LIBRARY = "libc++";
288 | CLANG_ENABLE_MODULES = YES;
289 | CLANG_ENABLE_OBJC_ARC = YES;
290 | CLANG_WARN_BOOL_CONVERSION = YES;
291 | CLANG_WARN_CONSTANT_CONVERSION = YES;
292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
293 | CLANG_WARN_EMPTY_BODY = YES;
294 | CLANG_WARN_ENUM_CONVERSION = YES;
295 | CLANG_WARN_INT_CONVERSION = YES;
296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
297 | CLANG_WARN_UNREACHABLE_CODE = YES;
298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
299 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
300 | COPY_PHASE_STRIP = NO;
301 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
302 | ENABLE_STRICT_OBJC_MSGSEND = YES;
303 | GCC_C_LANGUAGE_STANDARD = gnu99;
304 | GCC_DYNAMIC_NO_PIC = NO;
305 | GCC_NO_COMMON_BLOCKS = YES;
306 | GCC_OPTIMIZATION_LEVEL = 0;
307 | GCC_PREPROCESSOR_DEFINITIONS = (
308 | "DEBUG=1",
309 | "$(inherited)",
310 | );
311 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
314 | GCC_WARN_UNDECLARED_SELECTOR = YES;
315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
316 | GCC_WARN_UNUSED_FUNCTION = YES;
317 | GCC_WARN_UNUSED_VARIABLE = YES;
318 | IPHONEOS_DEPLOYMENT_TARGET = 8.3;
319 | MTL_ENABLE_DEBUG_INFO = YES;
320 | ONLY_ACTIVE_ARCH = YES;
321 | SDKROOT = iphoneos;
322 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
323 | TARGETED_DEVICE_FAMILY = "1,2";
324 | };
325 | name = Debug;
326 | };
327 | 8A678A9E1B077A490036A1BC /* Release */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | ALWAYS_SEARCH_USER_PATHS = NO;
331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
332 | CLANG_CXX_LIBRARY = "libc++";
333 | CLANG_ENABLE_MODULES = YES;
334 | CLANG_ENABLE_OBJC_ARC = YES;
335 | CLANG_WARN_BOOL_CONVERSION = YES;
336 | CLANG_WARN_CONSTANT_CONVERSION = YES;
337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
338 | CLANG_WARN_EMPTY_BODY = YES;
339 | CLANG_WARN_ENUM_CONVERSION = YES;
340 | CLANG_WARN_INT_CONVERSION = YES;
341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
342 | CLANG_WARN_UNREACHABLE_CODE = YES;
343 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
344 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
345 | COPY_PHASE_STRIP = NO;
346 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
347 | ENABLE_NS_ASSERTIONS = NO;
348 | ENABLE_STRICT_OBJC_MSGSEND = YES;
349 | GCC_C_LANGUAGE_STANDARD = gnu99;
350 | GCC_NO_COMMON_BLOCKS = YES;
351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
353 | GCC_WARN_UNDECLARED_SELECTOR = YES;
354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
355 | GCC_WARN_UNUSED_FUNCTION = YES;
356 | GCC_WARN_UNUSED_VARIABLE = YES;
357 | IPHONEOS_DEPLOYMENT_TARGET = 8.3;
358 | MTL_ENABLE_DEBUG_INFO = NO;
359 | SDKROOT = iphoneos;
360 | TARGETED_DEVICE_FAMILY = "1,2";
361 | VALIDATE_PRODUCT = YES;
362 | };
363 | name = Release;
364 | };
365 | 8A678AA01B077A490036A1BC /* Debug */ = {
366 | isa = XCBuildConfiguration;
367 | buildSettings = {
368 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
369 | INFOPLIST_FILE = AAImagePickerControllerDemo/Info.plist;
370 | IPHONEOS_DEPLOYMENT_TARGET = 7.0;
371 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
372 | PRODUCT_NAME = "$(TARGET_NAME)";
373 | };
374 | name = Debug;
375 | };
376 | 8A678AA11B077A490036A1BC /* Release */ = {
377 | isa = XCBuildConfiguration;
378 | buildSettings = {
379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
380 | INFOPLIST_FILE = AAImagePickerControllerDemo/Info.plist;
381 | IPHONEOS_DEPLOYMENT_TARGET = 7.0;
382 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
383 | PRODUCT_NAME = "$(TARGET_NAME)";
384 | };
385 | name = Release;
386 | };
387 | 8A678AA31B077A490036A1BC /* Debug */ = {
388 | isa = XCBuildConfiguration;
389 | buildSettings = {
390 | BUNDLE_LOADER = "$(TEST_HOST)";
391 | FRAMEWORK_SEARCH_PATHS = (
392 | "$(SDKROOT)/Developer/Library/Frameworks",
393 | "$(inherited)",
394 | );
395 | GCC_PREPROCESSOR_DEFINITIONS = (
396 | "DEBUG=1",
397 | "$(inherited)",
398 | );
399 | INFOPLIST_FILE = AAImagePickerControllerDemoTests/Info.plist;
400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
401 | PRODUCT_NAME = "$(TARGET_NAME)";
402 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AAImagePickerControllerDemo.app/AAImagePickerControllerDemo";
403 | };
404 | name = Debug;
405 | };
406 | 8A678AA41B077A490036A1BC /* Release */ = {
407 | isa = XCBuildConfiguration;
408 | buildSettings = {
409 | BUNDLE_LOADER = "$(TEST_HOST)";
410 | FRAMEWORK_SEARCH_PATHS = (
411 | "$(SDKROOT)/Developer/Library/Frameworks",
412 | "$(inherited)",
413 | );
414 | INFOPLIST_FILE = AAImagePickerControllerDemoTests/Info.plist;
415 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
416 | PRODUCT_NAME = "$(TARGET_NAME)";
417 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AAImagePickerControllerDemo.app/AAImagePickerControllerDemo";
418 | };
419 | name = Release;
420 | };
421 | /* End XCBuildConfiguration section */
422 |
423 | /* Begin XCConfigurationList section */
424 | 8A678A7B1B077A480036A1BC /* Build configuration list for PBXProject "AAImagePickerControllerDemo" */ = {
425 | isa = XCConfigurationList;
426 | buildConfigurations = (
427 | 8A678A9D1B077A490036A1BC /* Debug */,
428 | 8A678A9E1B077A490036A1BC /* Release */,
429 | );
430 | defaultConfigurationIsVisible = 0;
431 | defaultConfigurationName = Release;
432 | };
433 | 8A678A9F1B077A490036A1BC /* Build configuration list for PBXNativeTarget "AAImagePickerControllerDemo" */ = {
434 | isa = XCConfigurationList;
435 | buildConfigurations = (
436 | 8A678AA01B077A490036A1BC /* Debug */,
437 | 8A678AA11B077A490036A1BC /* Release */,
438 | );
439 | defaultConfigurationIsVisible = 0;
440 | };
441 | 8A678AA21B077A490036A1BC /* Build configuration list for PBXNativeTarget "AAImagePickerControllerDemoTests" */ = {
442 | isa = XCConfigurationList;
443 | buildConfigurations = (
444 | 8A678AA31B077A490036A1BC /* Debug */,
445 | 8A678AA41B077A490036A1BC /* Release */,
446 | );
447 | defaultConfigurationIsVisible = 0;
448 | };
449 | /* End XCConfigurationList section */
450 | };
451 | rootObject = 8A678A781B077A480036A1BC /* Project object */;
452 | }
453 |
--------------------------------------------------------------------------------
/AKPickerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AKPickerView.swift
3 | // AKPickerView
4 | //
5 | // Created by Akio Yasui on 1/29/15.
6 | // Copyright (c) 2015 Akkyie Y. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /**
12 | Styles of AKPickerView.
13 |
14 | - Wheel: Style with 3D appearance like UIPickerView.
15 | - Flat: Flat style.
16 | */
17 | public enum AKPickerViewStyle {
18 | case Wheel
19 | case Flat
20 | }
21 |
22 | // MARK: - Protocols
23 | // MARK: AKPickerViewDataSource
24 | /**
25 | Protocols to specify the number and type of contents.
26 | */
27 | @objc public protocol AKPickerViewDataSource {
28 | func numberOfItemsInPickerView(pickerView: AKPickerView) -> Int
29 | optional func pickerView(pickerView: AKPickerView, titleForItem item: Int) -> String
30 | optional func pickerView(pickerView: AKPickerView, imageForItem item: Int) -> UIImage
31 | }
32 |
33 | // MARK: AKPickerViewDelegate
34 | /**
35 | Protocols to specify the attitude when user selected an item,
36 | and customize the appearance of labels.
37 | */
38 | @objc public protocol AKPickerViewDelegate: UIScrollViewDelegate {
39 | optional func pickerView(pickerView: AKPickerView, didSelectItem item: Int)
40 | optional func pickerView(pickerView: AKPickerView, marginForItem item: Int) -> CGSize
41 | optional func pickerView(pickerView: AKPickerView, configureLabel label: UILabel, forItem item: Int)
42 | }
43 |
44 | // MARK: - Private Classes and Protocols
45 | // MARK: AKCollectionViewLayoutDelegate
46 | /**
47 | Private. Used to deliver the style of the picker.
48 | */
49 | private protocol AKCollectionViewLayoutDelegate {
50 | func pickerViewStyleForCollectionViewLayout(layout: AKCollectionViewLayout) -> AKPickerViewStyle
51 | }
52 |
53 | // MARK: AKCollectionViewCell
54 | /**
55 | Private. A subclass of UICollectionViewCell used in AKPickerView's collection view.
56 | */
57 | private class AKCollectionViewCell: UICollectionViewCell {
58 | var label: UILabel!
59 | var imageView: UIImageView!
60 | var font = UIFont.systemFontOfSize(UIFont.systemFontSize())
61 | var highlightedFont = UIFont.systemFontOfSize(UIFont.systemFontSize())
62 | var _selected: Bool = false {
63 | didSet(selected) {
64 | let animation = CATransition()
65 | animation.type = kCATransitionFade
66 | animation.duration = 0.15
67 | self.label.layer.addAnimation(animation, forKey: "")
68 | self.label.font = self.selected ? self.highlightedFont : self.font
69 | }
70 | }
71 |
72 | func initialize() {
73 | self.layer.doubleSided = false
74 | self.layer.shouldRasterize = true
75 | self.layer.rasterizationScale = UIScreen.mainScreen().scale
76 |
77 | self.label = UILabel(frame: self.contentView.bounds)
78 | self.label.backgroundColor = UIColor.clearColor()
79 | self.label.textAlignment = .Center
80 | self.label.textColor = UIColor.grayColor()
81 | self.label.numberOfLines = 1
82 | self.label.lineBreakMode = .ByTruncatingTail
83 | self.label.highlightedTextColor = UIColor.blackColor()
84 | self.label.font = self.font
85 | self.label.autoresizingMask =
86 | .FlexibleTopMargin |
87 | .FlexibleLeftMargin |
88 | .FlexibleBottomMargin |
89 | .FlexibleRightMargin;
90 | self.contentView.addSubview(self.label)
91 |
92 | self.imageView = UIImageView(frame: self.contentView.bounds)
93 | self.imageView.backgroundColor = UIColor.clearColor()
94 | self.imageView.contentMode = .Center
95 | self.imageView.autoresizingMask = .FlexibleWidth | .FlexibleHeight;
96 | self.contentView.addSubview(self.imageView)
97 | }
98 |
99 | init() {
100 | super.init(frame: CGRectZero)
101 | self.initialize()
102 | }
103 |
104 | override init(frame: CGRect) {
105 | super.init(frame: frame)
106 | self.initialize()
107 | }
108 |
109 | required init(coder aDecoder: NSCoder) {
110 | super.init(coder: aDecoder)
111 | self.initialize()
112 | }
113 | }
114 |
115 | // MARK: AKCollectionViewLayout
116 | /**
117 | Private. A subclass of UICollectionViewFlowLayout used in the collection view.
118 | */
119 | private class AKCollectionViewLayout: UICollectionViewFlowLayout {
120 | var delegate: AKCollectionViewLayoutDelegate!
121 | var width: CGFloat!
122 | var midX: CGFloat!
123 | var maxAngle: CGFloat!
124 |
125 | func initialize() {
126 | self.sectionInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)
127 | self.scrollDirection = .Horizontal
128 | self.minimumLineSpacing = 0.0
129 | }
130 |
131 | override init() {
132 | super.init()
133 | self.initialize()
134 | }
135 |
136 | required init(coder aDecoder: NSCoder) {
137 | super.init(coder: aDecoder)
138 | self.initialize()
139 | }
140 |
141 | private override func prepareLayout() {
142 | let visibleRect = CGRect(origin: self.collectionView!.contentOffset, size: self.collectionView!.bounds.size)
143 | self.midX = CGRectGetMidX(visibleRect);
144 | self.width = CGRectGetWidth(visibleRect) / 2;
145 | self.maxAngle = CGFloat(M_PI_2);
146 | }
147 |
148 | private override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
149 | return true
150 | }
151 |
152 | private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
153 | let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
154 | switch self.delegate.pickerViewStyleForCollectionViewLayout(self) {
155 | case .Flat:
156 | return attributes
157 | case .Wheel:
158 | let distance = CGRectGetMidX(attributes.frame) - self.midX;
159 | let currentAngle = self.maxAngle * distance / self.width / CGFloat(M_PI_2);
160 | var transform = CATransform3DIdentity;
161 | transform = CATransform3DTranslate(transform, -distance, 0, -self.width);
162 | transform = CATransform3DRotate(transform, currentAngle, 0, 1, 0);
163 | transform = CATransform3DTranslate(transform, 0, 0, self.width);
164 | attributes.transform3D = transform;
165 | attributes.alpha = fabs(currentAngle) < self.maxAngle ? 1.0 : 0.0;
166 | return attributes;
167 | }
168 | }
169 |
170 | private override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
171 | switch self.delegate.pickerViewStyleForCollectionViewLayout(self) {
172 | case .Flat:
173 | return super.layoutAttributesForElementsInRect(rect)
174 | case .Wheel:
175 | var attributes = [AnyObject]()
176 | if self.collectionView!.numberOfSections() > 0 {
177 | for i in 0 ..< self.collectionView!.numberOfItemsInSection(0) {
178 | let indexPath = NSIndexPath(forItem: i, inSection: 0)
179 | attributes.append(self.layoutAttributesForItemAtIndexPath(indexPath))
180 | }
181 | }
182 | return attributes
183 | }
184 | }
185 |
186 | }
187 |
188 | // MARK: AKPickerViewDelegateIntercepter
189 | /**
190 | Private. Used to hook UICollectionViewDelegate and throw it AKPickerView,
191 | and if it conforms to UIScrollViewDelegate, also throw it to AKPickerView's delegate.
192 | */
193 | private class AKPickerViewDelegateIntercepter: NSObject, UICollectionViewDelegate {
194 | weak var pickerView: AKPickerView?
195 | weak var delegate: UIScrollViewDelegate?
196 |
197 | init(pickerView: AKPickerView, delegate: UIScrollViewDelegate?) {
198 | self.pickerView = pickerView
199 | self.delegate = delegate
200 | }
201 |
202 | private override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
203 | if self.pickerView!.respondsToSelector(aSelector) {
204 | return self.pickerView
205 | } else if self.delegate != nil && self.delegate!.respondsToSelector(aSelector) {
206 | return self.delegate
207 | } else {
208 | return nil
209 | }
210 | }
211 |
212 | private override func respondsToSelector(aSelector: Selector) -> Bool {
213 | if self.pickerView!.respondsToSelector(aSelector) {
214 | return true
215 | } else if self.delegate != nil && self.delegate!.respondsToSelector(aSelector) {
216 | return true
217 | } else {
218 | return super.respondsToSelector(aSelector)
219 | }
220 | }
221 |
222 | }
223 |
224 | // MARK: - AKPickerView
225 | // TODO: Make these delegate conformation private
226 | /**
227 | Horizontal picker view. This is just a subclass of UIView, contains a UICollectionView.
228 | */
229 | public class AKPickerView: UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, AKCollectionViewLayoutDelegate {
230 |
231 | // MARK: - Properties
232 | // MARK: Readwrite Properties
233 | /// Readwrite. Data source of picker view.
234 | public weak var dataSource: AKPickerViewDataSource? = nil
235 | /// Readwrite. Delegate of picker view.
236 | public weak var delegate: AKPickerViewDelegate? = nil {
237 | didSet(delegate) {
238 | self.intercepter.delegate = delegate
239 | }
240 | }
241 | /// Readwrite. A font which used in NOT selected cells.
242 | public lazy var font = UIFont.systemFontOfSize(20)
243 | /// Readwrite. A font which used in selected cells.
244 | public lazy var highlightedFont = UIFont.boldSystemFontOfSize(20)
245 | /// Readwrite. A color of the text on NOT selected cells.
246 | public lazy var textColor = UIColor.darkGrayColor()
247 | /// Readwrite. A color of the text on selected cells.
248 | public lazy var highlightedTextColor = UIColor.blackColor()
249 | /// Readwrite. A float value which indicates the spacing between cells.
250 | public var interitemSpacing: CGFloat = 0.0
251 | /// Readwrite. The style of the picker view. See AKPickerViewStyle.
252 | public var pickerViewStyle = AKPickerViewStyle.Wheel
253 | /// Readwrite. A float value which determines the perspective representation which used when using AKPickerViewStyle.Wheel style.
254 | public var viewDepth: CGFloat = 1000.0 {
255 | didSet {
256 | self.collectionView.layer.sublayerTransform = self.viewDepth > 0.0 ? {
257 | var transform = CATransform3DIdentity;
258 | transform.m34 = -1.0 / self.viewDepth;
259 | return transform;
260 | }() : CATransform3DIdentity;
261 | }
262 | }
263 | /// Readwrite. A boolean value indicates whether the mask is disabled.
264 | public var maskDisabled: Bool! = nil {
265 | didSet {
266 | self.collectionView.layer.mask = self.maskDisabled == true ? nil : {
267 | let maskLayer = CAGradientLayer()
268 | maskLayer.frame = self.collectionView.bounds
269 | maskLayer.colors = [
270 | UIColor.clearColor().CGColor,
271 | UIColor.blackColor().CGColor,
272 | UIColor.blackColor().CGColor,
273 | UIColor.clearColor().CGColor]
274 | maskLayer.locations = [0.0, 0.33, 0.66, 1.0]
275 | maskLayer.startPoint = CGPointMake(0.0, 0.0)
276 | maskLayer.endPoint = CGPointMake(1.0, 0.0)
277 | return maskLayer
278 | }()
279 | }
280 | }
281 |
282 | // MARK: Readonly Properties
283 | /// Readonly. Index of currently selected item.
284 | private(set) var selectedItem: Int = 0
285 | /// Readonly. The point at which the origin of the content view is offset from the origin of the picker view.
286 | public var contentOffset: CGPoint {
287 | get {
288 | return self.collectionView.contentOffset
289 | }
290 | }
291 |
292 | // MARK: Private Properties
293 | /// Private. A UICollectionView which shows contents on cells.
294 | private var collectionView: UICollectionView!
295 | /// Private. An intercepter to hook UICollectionViewDelegate then throw it picker view and its delegate
296 | private var intercepter: AKPickerViewDelegateIntercepter!
297 | /// Private. A UICollectionViewFlowLayout used in picker view's collection view.
298 | private var collectionViewLayout: AKCollectionViewLayout {
299 | let layout = AKCollectionViewLayout()
300 | layout.delegate = self
301 | return layout
302 | }
303 |
304 | // MARK: - Functions
305 | // MARK: View Lifecycle
306 | /**
307 | Private. Initializes picker view's subviews and friends.
308 | */
309 | private func initialize() {
310 | self.collectionView?.removeFromSuperview()
311 | self.collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: self.collectionViewLayout)
312 | self.collectionView.showsHorizontalScrollIndicator = false
313 | self.collectionView.backgroundColor = UIColor.clearColor()
314 | self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast
315 | self.collectionView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
316 | self.collectionView.dataSource = self
317 | self.collectionView.registerClass(
318 | AKCollectionViewCell.self,
319 | forCellWithReuseIdentifier: NSStringFromClass(AKCollectionViewCell.self))
320 | self.addSubview(self.collectionView)
321 |
322 | self.intercepter = AKPickerViewDelegateIntercepter(pickerView: self, delegate: self.delegate)
323 | self.collectionView.delegate = self.intercepter
324 |
325 | self.maskDisabled = self.maskDisabled == nil ? false : self.maskDisabled
326 | }
327 |
328 | public init() {
329 | super.init(frame: CGRectZero)
330 | self.initialize()
331 | }
332 |
333 | public override init(frame: CGRect) {
334 | super.init(frame: frame)
335 | self.initialize()
336 | }
337 |
338 | public required init(coder aDecoder: NSCoder) {
339 | super.init(coder: aDecoder)
340 | self.initialize()
341 | }
342 |
343 | deinit {
344 | self.collectionView.delegate = nil
345 | }
346 |
347 | // MARK: Layout
348 |
349 | public override func layoutSubviews() {
350 | super.layoutSubviews()
351 | if self.dataSource != nil && self.dataSource!.numberOfItemsInPickerView(self) > 0 {
352 | self.collectionView.collectionViewLayout = self.collectionViewLayout
353 | self.scrollToItem(self.selectedItem, animated: false)
354 | }
355 | self.collectionView.layer.mask?.frame = self.collectionView.bounds
356 | }
357 |
358 | public override func intrinsicContentSize() -> CGSize {
359 | return CGSizeMake(UIViewNoIntrinsicMetric, max(self.font.lineHeight, self.highlightedFont.lineHeight))
360 | }
361 |
362 | // MARK: Calculation Functions
363 |
364 | /**
365 | Private. Used to calculate bounding size of given string with picker view's font and highlightedFont
366 |
367 | :param: string A NSString to calculate size
368 | :returns: A CGSize which contains given string just.
369 | */
370 | private func sizeForString(string: NSString) -> CGSize {
371 | let size = string.sizeWithAttributes([NSFontAttributeName: self.font])
372 | let highlightedSize = string.sizeWithAttributes([NSFontAttributeName: self.highlightedFont])
373 | return CGSize(
374 | width: ceil(max(size.width, highlightedSize.width)),
375 | height: ceil(max(size.height, highlightedSize.height)))
376 | }
377 |
378 | /**
379 | Private. Used to calculate the x-coordinate of the content offset of specified item.
380 |
381 | :param: item An integer value which indicates the index of cell.
382 | :returns: An x-coordinate of the cell whose index is given one.
383 | */
384 | private func offsetForItem(item: Int) -> CGFloat {
385 | var offset: CGFloat = 0
386 | for i in 0 ..< item {
387 | let indexPath = NSIndexPath(forItem: i, inSection: 0)
388 | let cellSize = self.collectionView(
389 | self.collectionView,
390 | layout: self.collectionView.collectionViewLayout,
391 | sizeForItemAtIndexPath: indexPath)
392 | offset += cellSize.width
393 | }
394 |
395 | let firstIndexPath = NSIndexPath(forItem: 0, inSection: 0)
396 | let firstSize = self.collectionView(
397 | self.collectionView,
398 | layout: self.collectionView.collectionViewLayout,
399 | sizeForItemAtIndexPath: firstIndexPath)
400 | let selectedIndexPath = NSIndexPath(forItem: item, inSection: 0)
401 | let selectedSize = self.collectionView(
402 | self.collectionView,
403 | layout: self.collectionView.collectionViewLayout,
404 | sizeForItemAtIndexPath: selectedIndexPath)
405 | offset -= (firstSize.width - selectedSize.width) / 2.0
406 |
407 | return offset
408 | }
409 |
410 | // MARK: View Controls
411 | /**
412 | Reload the picker view's contents and styles. Call this method always after any property is changed.
413 | */
414 | public func reloadData() {
415 | self.invalidateIntrinsicContentSize()
416 | self.collectionView.collectionViewLayout.invalidateLayout()
417 | self.collectionView.reloadData()
418 | if self.dataSource != nil && self.dataSource!.numberOfItemsInPickerView(self) > 0 {
419 | self.selectItem(self.selectedItem, animated: false, notifySelection: false)
420 | }
421 | }
422 |
423 | /**
424 | Move to the cell whose index is given one without selection change.
425 |
426 | :param: item An integer value which indicates the index of cell.
427 | :param: animated True if the scrolling should be animated, false if it should be immediate.
428 | */
429 | public func scrollToItem(item: Int, animated: Bool = false) {
430 | switch self.pickerViewStyle {
431 | case .Flat:
432 | self.collectionView.scrollToItemAtIndexPath(
433 | NSIndexPath(
434 | forItem: item,
435 | inSection: 0),
436 | atScrollPosition: .CenteredHorizontally,
437 | animated: animated)
438 | case .Wheel:
439 | self.collectionView.setContentOffset(
440 | CGPoint(
441 | x: self.offsetForItem(item),
442 | y: self.collectionView.contentOffset.y),
443 | animated: animated)
444 | }
445 | }
446 |
447 | /**
448 | Select a cell whose index is given one and move to it.
449 |
450 | :param: item An integer value which indicates the index of cell.
451 | :param: animated True if the scrolling should be animated, false if it should be immediate.
452 | */
453 | public func selectItem(item: Int, animated: Bool = false) {
454 | self.selectItem(item, animated: animated, notifySelection: true)
455 | }
456 |
457 | /**
458 | Private. Select a cell whose index is given one and move to it, with specifying whether it calls delegate method.
459 |
460 | :param: item An integer value which indicates the index of cell.
461 | :param: animated True if the scrolling should be animated, false if it should be immediate.
462 | :param: notifySelection True if the delegate method should be called, false if not.
463 | */
464 | private func selectItem(item: Int, animated: Bool, notifySelection: Bool) {
465 | self.collectionView.selectItemAtIndexPath(
466 | NSIndexPath(forItem: item, inSection: 0),
467 | animated: animated,
468 | scrollPosition: .None)
469 | self.scrollToItem(item, animated: animated)
470 | self.selectedItem = item
471 | if notifySelection {
472 | self.delegate?.pickerView?(self, didSelectItem: item)
473 | }
474 | }
475 |
476 | // MARK: Delegate Handling
477 | /**
478 | Private.
479 | */
480 | private func didEndScrolling() {
481 | switch self.pickerViewStyle {
482 | case .Flat:
483 | let center = self.convertPoint(self.collectionView.center, toView: self.collectionView)
484 | if let indexPath = self.collectionView.indexPathForItemAtPoint(center) {
485 | self.selectItem(indexPath.item, animated: true, notifySelection: true)
486 | }
487 | case .Wheel:
488 | if let numberOfItems = self.dataSource?.numberOfItemsInPickerView(self) {
489 | for i in 0 ..< numberOfItems {
490 | let indexPath = NSIndexPath(forItem: i, inSection: 0)
491 | let cellSize = self.collectionView(
492 | self.collectionView,
493 | layout: self.collectionView.collectionViewLayout,
494 | sizeForItemAtIndexPath: indexPath)
495 | if self.offsetForItem(i) + cellSize.width / 2 > self.collectionView.contentOffset.x {
496 | self.selectItem(i, animated: true, notifySelection: true)
497 | break
498 | }
499 | }
500 | }
501 | }
502 | }
503 |
504 | // MARK: UICollectionViewDataSource
505 | public func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
506 | return self.dataSource != nil && self.dataSource!.numberOfItemsInPickerView(self) > 0 ? 1 : 0
507 | }
508 |
509 | public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
510 | return self.dataSource != nil ? self.dataSource!.numberOfItemsInPickerView(self) : 0
511 | }
512 |
513 | public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
514 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(NSStringFromClass(AKCollectionViewCell.self), forIndexPath: indexPath) as! AKCollectionViewCell
515 | if let title = self.dataSource?.pickerView?(self, titleForItem: indexPath.item) {
516 | cell.label.text = title
517 | cell.label.textColor = self.textColor
518 | cell.label.highlightedTextColor = self.highlightedTextColor
519 | cell.label.font = self.font
520 | cell.font = self.font
521 | cell.highlightedFont = self.highlightedFont
522 | cell.label.bounds = CGRect(origin: CGPointZero, size: self.sizeForString(title))
523 | if let delegate = self.delegate {
524 | delegate.pickerView?(self, configureLabel: cell.label, forItem: indexPath.item)
525 | if let margin = delegate.pickerView?(self, marginForItem: indexPath.item) {
526 | cell.label.frame = CGRectInset(cell.label.frame, -margin.width, -margin.height)
527 | }
528 | }
529 | } else if let image = self.dataSource?.pickerView?(self, imageForItem: indexPath.item) {
530 | cell.imageView.image = image
531 | }
532 | cell._selected = (indexPath.item == self.selectedItem)
533 | return cell
534 | }
535 |
536 | // MARK: UICollectionViewDelegateFlowLayout
537 | public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
538 | var size = CGSizeMake(self.interitemSpacing, collectionView.bounds.size.height)
539 | if let title = self.dataSource?.pickerView?(self, titleForItem: indexPath.item) {
540 | size.width += self.sizeForString(title).width
541 | if let margin = self.delegate?.pickerView?(self, marginForItem: indexPath.item) {
542 | size.width += margin.width * 2
543 | }
544 | } else if let image = self.dataSource?.pickerView?(self, imageForItem: indexPath.item) {
545 | size.width += image.size.width
546 | }
547 | return size
548 | }
549 |
550 | public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
551 | return 0.0
552 | }
553 |
554 | public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
555 | return 0.0
556 | }
557 |
558 | public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
559 | let number = self.collectionView(collectionView, numberOfItemsInSection: section)
560 | let firstIndexPath = NSIndexPath(forItem: 0, inSection: section)
561 | let firstSize = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, sizeForItemAtIndexPath: firstIndexPath)
562 | let lastIndexPath = NSIndexPath(forItem: number - 1, inSection: section)
563 | let lastSize = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, sizeForItemAtIndexPath: lastIndexPath)
564 | return UIEdgeInsetsMake(
565 | 0, (collectionView.bounds.size.width - firstSize.width) / 2,
566 | 0, (collectionView.bounds.size.width - lastSize.width) / 2
567 | )
568 | }
569 |
570 | // MARK: UICollectionViewDelegate
571 | public func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
572 | self.selectItem(indexPath.item, animated: true)
573 | }
574 |
575 | // MARK: UIScrollViewDelegate
576 | public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
577 | self.delegate?.scrollViewDidEndDecelerating?(scrollView)
578 | self.didEndScrolling()
579 | }
580 |
581 | public func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
582 | self.delegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
583 | if !decelerate {
584 | self.didEndScrolling()
585 | }
586 | }
587 |
588 | public func scrollViewDidScroll(scrollView: UIScrollView) {
589 | self.delegate?.scrollViewDidScroll?(scrollView)
590 | CATransaction.begin()
591 | CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
592 | self.collectionView.layer.mask?.frame = self.collectionView.bounds
593 | CATransaction.commit()
594 | }
595 |
596 | // MARK: AKCollectionViewLayoutDelegate
597 | private func pickerViewStyleForCollectionViewLayout(layout: AKCollectionViewLayout) -> AKPickerViewStyle {
598 | return self.pickerViewStyle
599 | }
600 |
601 | }
602 |
--------------------------------------------------------------------------------