├── .gitignore ├── AKPickerView-Swift.podspec ├── AKPickerView ├── AKPickerView.h ├── AKPickerView.swift └── Info.plist ├── AKPickerViewSample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── AKPickerView.xcscheme ├── AKPickerViewSample ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── ViewController.swift ├── LICENSE ├── README.md ├── Screenshot.gif └── Screenshot2.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /AKPickerView-Swift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'AKPickerView-Swift' 4 | s.version = '2.0.1' 5 | s.summary = 'A simple yet customizable horizontal picker view.' 6 | 7 | s.description = 'A simple yet customizable horizontal picker view. Works on iOS 8' 8 | 9 | s.license = 'MIT' 10 | 11 | s.homepage = 'https://github.com/Akkyie/AKPickerView-Swift' 12 | 13 | s.authors = { "Akkyie Y" => "akio@prioris.org" } 14 | s.social_media_url = "http://twitter.com/akkyie" 15 | 16 | s.source = { :git => 'https://github.com/Akkyie/AKPickerView-Swift.git', :tag => s.version } 17 | 18 | s.ios.deployment_target = '8.0' 19 | 20 | s.source_files = 'AKPickerView/AKPickerView.swift' 21 | 22 | s.requires_arc = true 23 | 24 | end 25 | -------------------------------------------------------------------------------- /AKPickerView/AKPickerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // AKPickerView.h 3 | // AKPickerView 4 | // 5 | // Created by Akio Yasui on 2/10/15. 6 | // Copyright (c) 2015 Akio Yasui. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXPORT double AKPickerViewVersionNumber; 12 | 13 | FOUNDATION_EXPORT const unsigned char AKPickerViewVersionString[]; 14 | -------------------------------------------------------------------------------- /AKPickerView/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 | @objc optional func pickerView(_ pickerView: AKPickerView, titleForItem item: Int) -> String 30 | @objc 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 | @objc optional func pickerView(_ pickerView: AKPickerView, didSelectItem item: Int) 40 | @objc optional func pickerView(_ pickerView: AKPickerView, marginForItem item: Int) -> CGSize 41 | @objc 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.systemFont(ofSize: UIFont.systemFontSize) 61 | var highlightedFont = UIFont.systemFont(ofSize: 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.add(animation, forKey: "") 68 | self.label.font = self.isSelected ? self.highlightedFont : self.font 69 | } 70 | } 71 | 72 | func initialize() { 73 | self.layer.isDoubleSided = false 74 | self.layer.shouldRasterize = true 75 | self.layer.rasterizationScale = UIScreen.main.scale 76 | 77 | self.label = UILabel(frame: self.contentView.bounds) 78 | self.label.backgroundColor = UIColor.clear 79 | self.label.textAlignment = .center 80 | self.label.textColor = UIColor.gray 81 | self.label.numberOfLines = 1 82 | self.label.lineBreakMode = .byTruncatingTail 83 | self.label.highlightedTextColor = UIColor.black 84 | self.label.font = self.font 85 | self.label.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleBottomMargin, .flexibleRightMargin] 86 | self.contentView.addSubview(self.label) 87 | 88 | self.imageView = UIImageView(frame: self.contentView.bounds) 89 | self.imageView.backgroundColor = UIColor.clear 90 | self.imageView.contentMode = .center 91 | self.imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 92 | self.contentView.addSubview(self.imageView) 93 | } 94 | 95 | init() { 96 | super.init(frame: CGRect.zero) 97 | self.initialize() 98 | } 99 | 100 | override init(frame: CGRect) { 101 | super.init(frame: frame) 102 | self.initialize() 103 | } 104 | 105 | required init!(coder aDecoder: NSCoder) { 106 | super.init(coder: aDecoder) 107 | self.initialize() 108 | } 109 | } 110 | 111 | // MARK: AKCollectionViewLayout 112 | /** 113 | Private. A subclass of UICollectionViewFlowLayout used in the collection view. 114 | */ 115 | private class AKCollectionViewLayout: UICollectionViewFlowLayout { 116 | var delegate: AKCollectionViewLayoutDelegate! 117 | var width: CGFloat! 118 | var midX: CGFloat! 119 | var maxAngle: CGFloat! 120 | 121 | func initialize() { 122 | self.sectionInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0) 123 | self.scrollDirection = .horizontal 124 | self.minimumLineSpacing = 0.0 125 | } 126 | 127 | override init() { 128 | super.init() 129 | self.initialize() 130 | } 131 | 132 | required init!(coder aDecoder: NSCoder) { 133 | super.init(coder: aDecoder) 134 | self.initialize() 135 | } 136 | 137 | fileprivate override func prepare() { 138 | let visibleRect = CGRect(origin: self.collectionView!.contentOffset, size: self.collectionView!.bounds.size) 139 | self.midX = visibleRect.midX; 140 | self.width = visibleRect.width / 2; 141 | self.maxAngle = CGFloat(M_PI_2); 142 | } 143 | 144 | fileprivate override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 145 | return true 146 | } 147 | 148 | fileprivate override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 149 | if let attributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes { 150 | switch self.delegate.pickerViewStyleForCollectionViewLayout(self) { 151 | case .flat: 152 | return attributes 153 | case .wheel: 154 | let distance = attributes.frame.midX - self.midX; 155 | let currentAngle = self.maxAngle * distance / self.width / CGFloat(M_PI_2); 156 | var transform = CATransform3DIdentity; 157 | transform = CATransform3DTranslate(transform, -distance, 0, -self.width); 158 | transform = CATransform3DRotate(transform, currentAngle, 0, 1, 0); 159 | transform = CATransform3DTranslate(transform, 0, 0, self.width); 160 | attributes.transform3D = transform; 161 | attributes.alpha = fabs(currentAngle) < self.maxAngle ? 1.0 : 0.0; 162 | return attributes; 163 | } 164 | } 165 | 166 | return nil 167 | } 168 | 169 | private func layoutAttributesForElementsInRect(_ rect: CGRect) -> [AnyObject]? { 170 | switch self.delegate.pickerViewStyleForCollectionViewLayout(self) { 171 | case .flat: 172 | return super.layoutAttributesForElements(in: rect) 173 | case .wheel: 174 | var attributes = [AnyObject]() 175 | if self.collectionView!.numberOfSections > 0 { 176 | for i in 0 ..< self.collectionView!.numberOfItems(inSection: 0) { 177 | let indexPath = IndexPath(item: i, section: 0) 178 | attributes.append(self.layoutAttributesForItem(at: indexPath)!) 179 | } 180 | } 181 | return attributes 182 | } 183 | } 184 | 185 | } 186 | 187 | // MARK: AKPickerViewDelegateIntercepter 188 | /** 189 | Private. Used to hook UICollectionViewDelegate and throw it AKPickerView, 190 | and if it conforms to UIScrollViewDelegate, also throw it to AKPickerView's delegate. 191 | */ 192 | private class AKPickerViewDelegateIntercepter: NSObject, UICollectionViewDelegate { 193 | weak var pickerView: AKPickerView? 194 | weak var delegate: UIScrollViewDelegate? 195 | 196 | init(pickerView: AKPickerView, delegate: UIScrollViewDelegate?) { 197 | self.pickerView = pickerView 198 | self.delegate = delegate 199 | } 200 | 201 | fileprivate override func forwardingTarget(for aSelector: Selector) -> Any? { 202 | if self.pickerView!.responds(to: aSelector) { 203 | return self.pickerView 204 | } else if self.delegate != nil && self.delegate!.responds(to: aSelector) { 205 | return self.delegate 206 | } else { 207 | return nil 208 | } 209 | } 210 | 211 | fileprivate override func responds(to aSelector: Selector) -> Bool { 212 | if self.pickerView!.responds(to: aSelector) { 213 | return true 214 | } else if self.delegate != nil && self.delegate!.responds(to: aSelector) { 215 | return true 216 | } else { 217 | return super.responds(to: aSelector) 218 | } 219 | } 220 | 221 | } 222 | 223 | // MARK: - AKPickerView 224 | // TODO: Make these delegate conformation private 225 | /** 226 | Horizontal picker view. This is just a subclass of UIView, contains a UICollectionView. 227 | */ 228 | public class AKPickerView: UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, AKCollectionViewLayoutDelegate { 229 | 230 | // MARK: - Properties 231 | // MARK: Readwrite Properties 232 | /// Readwrite. Data source of picker view. 233 | public weak var dataSource: AKPickerViewDataSource? = nil 234 | /// Readwrite. Delegate of picker view. 235 | public weak var delegate: AKPickerViewDelegate? = nil { 236 | didSet(delegate) { 237 | self.intercepter.delegate = delegate 238 | } 239 | } 240 | /// Readwrite. A font which used in NOT selected cells. 241 | public lazy var font = UIFont.systemFont(ofSize: 20) 242 | 243 | /// Readwrite. A font which used in selected cells. 244 | public lazy var highlightedFont = UIFont.boldSystemFont(ofSize: 20) 245 | 246 | /// Readwrite. A color of the text on NOT selected cells. 247 | @IBInspectable public lazy var textColor: UIColor = UIColor.darkGray 248 | 249 | /// Readwrite. A color of the text on selected cells. 250 | @IBInspectable public lazy var highlightedTextColor: UIColor = UIColor.black 251 | 252 | /// Readwrite. A float value which indicates the spacing between cells. 253 | @IBInspectable public var interitemSpacing: CGFloat = 0.0 254 | 255 | /// Readwrite. The style of the picker view. See AKPickerViewStyle. 256 | public var pickerViewStyle = AKPickerViewStyle.wheel 257 | 258 | /// Readwrite. A float value which determines the perspective representation which used when using AKPickerViewStyle.Wheel style. 259 | @IBInspectable public var viewDepth: CGFloat = 1000.0 { 260 | didSet { 261 | self.collectionView.layer.sublayerTransform = self.viewDepth > 0.0 ? { 262 | var transform = CATransform3DIdentity; 263 | transform.m34 = -1.0 / self.viewDepth; 264 | return transform; 265 | }() : CATransform3DIdentity; 266 | } 267 | } 268 | /// Readwrite. A boolean value indicates whether the mask is disabled. 269 | @IBInspectable public var maskDisabled: Bool! = nil { 270 | didSet { 271 | self.collectionView.layer.mask = self.maskDisabled == true ? nil : { 272 | let maskLayer = CAGradientLayer() 273 | maskLayer.frame = self.collectionView.bounds 274 | maskLayer.colors = [ 275 | UIColor.clear.cgColor, 276 | UIColor.black.cgColor, 277 | UIColor.black.cgColor, 278 | UIColor.clear.cgColor] 279 | maskLayer.locations = [0.0, 0.33, 0.66, 1.0] 280 | maskLayer.startPoint = CGPoint(x: 0.0, y: 0.0) 281 | maskLayer.endPoint = CGPoint(x: 1.0, y: 0.0) 282 | return maskLayer 283 | }() 284 | } 285 | } 286 | 287 | // MARK: Readonly Properties 288 | /// Readonly. Index of currently selected item. 289 | public private(set) var selectedItem: Int = 0 290 | /// Readonly. The point at which the origin of the content view is offset from the origin of the picker view. 291 | public var contentOffset: CGPoint { 292 | get { 293 | return self.collectionView.contentOffset 294 | } 295 | } 296 | 297 | // MARK: Private Properties 298 | /// Private. A UICollectionView which shows contents on cells. 299 | fileprivate var collectionView: UICollectionView! 300 | /// Private. An intercepter to hook UICollectionViewDelegate then throw it picker view and its delegate 301 | fileprivate var intercepter: AKPickerViewDelegateIntercepter! 302 | /// Private. A UICollectionViewFlowLayout used in picker view's collection view. 303 | fileprivate var collectionViewLayout: AKCollectionViewLayout { 304 | let layout = AKCollectionViewLayout() 305 | layout.delegate = self 306 | return layout 307 | } 308 | 309 | // MARK: - Functions 310 | // MARK: View Lifecycle 311 | /** 312 | Private. Initializes picker view's subviews and friends. 313 | */ 314 | fileprivate func initialize() { 315 | self.collectionView?.removeFromSuperview() 316 | self.collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: self.collectionViewLayout) 317 | self.collectionView.showsHorizontalScrollIndicator = false 318 | self.collectionView.backgroundColor = UIColor.clear 319 | self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast 320 | self.collectionView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight] 321 | self.collectionView.dataSource = self 322 | self.collectionView.register( 323 | AKCollectionViewCell.self, 324 | forCellWithReuseIdentifier: NSStringFromClass(AKCollectionViewCell.self)) 325 | self.addSubview(self.collectionView) 326 | 327 | self.intercepter = AKPickerViewDelegateIntercepter(pickerView: self, delegate: self.delegate) 328 | self.collectionView.delegate = self.intercepter 329 | 330 | self.maskDisabled = self.maskDisabled == nil ? false : self.maskDisabled 331 | } 332 | 333 | public init() { 334 | super.init(frame: CGRect.zero) 335 | self.initialize() 336 | } 337 | 338 | public override init(frame: CGRect) { 339 | super.init(frame: frame) 340 | self.initialize() 341 | } 342 | 343 | public required init!(coder aDecoder: NSCoder) { 344 | super.init(coder: aDecoder) 345 | self.initialize() 346 | } 347 | 348 | deinit { 349 | self.collectionView.delegate = nil 350 | } 351 | 352 | // MARK: Layout 353 | 354 | open override func layoutSubviews() { 355 | super.layoutSubviews() 356 | if self.dataSource != nil && self.dataSource!.numberOfItemsInPickerView(self) > 0 { 357 | self.collectionView.collectionViewLayout = self.collectionViewLayout 358 | self.scrollToItem(self.selectedItem, animated: false) 359 | } 360 | self.collectionView.layer.mask?.frame = self.collectionView.bounds 361 | } 362 | 363 | open override var intrinsicContentSize : CGSize { 364 | return CGSize(width: UIViewNoIntrinsicMetric, height: max(self.font.lineHeight, self.highlightedFont.lineHeight)) 365 | } 366 | 367 | // MARK: Calculation Functions 368 | 369 | /** 370 | Private. Used to calculate bounding size of given string with picker view's font and highlightedFont 371 | 372 | :param: string A NSString to calculate size 373 | :returns: A CGSize which contains given string just. 374 | */ 375 | fileprivate func sizeForString(_ string: NSString) -> CGSize { 376 | let size = string.size(attributes: [NSFontAttributeName: self.font]) 377 | let highlightedSize = string.size(attributes: [NSFontAttributeName: self.highlightedFont]) 378 | return CGSize( 379 | width: ceil(max(size.width, highlightedSize.width)), 380 | height: ceil(max(size.height, highlightedSize.height))) 381 | } 382 | 383 | /** 384 | Private. Used to calculate the x-coordinate of the content offset of specified item. 385 | 386 | :param: item An integer value which indicates the index of cell. 387 | :returns: An x-coordinate of the cell whose index is given one. 388 | */ 389 | fileprivate func offsetForItem(_ item: Int) -> CGFloat { 390 | var offset: CGFloat = 0 391 | for i in 0 ..< item { 392 | let indexPath = IndexPath(item: i, section: 0) 393 | let cellSize = self.collectionView( 394 | self.collectionView, 395 | layout: self.collectionView.collectionViewLayout, 396 | sizeForItemAt: indexPath) 397 | offset += cellSize.width 398 | } 399 | 400 | let firstIndexPath = IndexPath(item: 0, section: 0) 401 | let firstSize = self.collectionView( 402 | self.collectionView, 403 | layout: self.collectionView.collectionViewLayout, 404 | sizeForItemAt: firstIndexPath) 405 | let selectedIndexPath = IndexPath(item: item, section: 0) 406 | let selectedSize = self.collectionView( 407 | self.collectionView, 408 | layout: self.collectionView.collectionViewLayout, 409 | sizeForItemAt: selectedIndexPath) 410 | offset -= (firstSize.width - selectedSize.width) / 2.0 411 | 412 | return offset 413 | } 414 | 415 | // MARK: View Controls 416 | /** 417 | Reload the picker view's contents and styles. Call this method always after any property is changed. 418 | */ 419 | public func reloadData() { 420 | self.invalidateIntrinsicContentSize() 421 | self.collectionView.collectionViewLayout.invalidateLayout() 422 | self.collectionView.reloadData() 423 | if self.dataSource != nil && self.dataSource!.numberOfItemsInPickerView(self) > 0 { 424 | self.selectItem(self.selectedItem, animated: false, notifySelection: false) 425 | } 426 | } 427 | 428 | /** 429 | Move to the cell whose index is given one without selection change. 430 | 431 | :param: item An integer value which indicates the index of cell. 432 | :param: animated True if the scrolling should be animated, false if it should be immediate. 433 | */ 434 | public func scrollToItem(_ item: Int, animated: Bool = false) { 435 | switch self.pickerViewStyle { 436 | case .flat: 437 | self.collectionView.scrollToItem( 438 | at: IndexPath( 439 | item: item, 440 | section: 0), 441 | at: .centeredHorizontally, 442 | animated: animated) 443 | case .wheel: 444 | self.collectionView.setContentOffset( 445 | CGPoint( 446 | x: self.offsetForItem(item), 447 | y: self.collectionView.contentOffset.y), 448 | animated: animated) 449 | } 450 | } 451 | 452 | /** 453 | Select a cell whose index is given one and move to it. 454 | 455 | :param: item An integer value which indicates the index of cell. 456 | :param: animated True if the scrolling should be animated, false if it should be immediate. 457 | */ 458 | public func selectItem(_ item: Int, animated: Bool = false) { 459 | self.selectItem(item, animated: animated, notifySelection: true) 460 | } 461 | 462 | /** 463 | Private. Select a cell whose index is given one and move to it, with specifying whether it calls delegate method. 464 | 465 | :param: item An integer value which indicates the index of cell. 466 | :param: animated True if the scrolling should be animated, false if it should be immediate. 467 | :param: notifySelection True if the delegate method should be called, false if not. 468 | */ 469 | fileprivate func selectItem(_ item: Int, animated: Bool, notifySelection: Bool) { 470 | self.collectionView.selectItem( 471 | at: IndexPath(item: item, section: 0), 472 | animated: animated, 473 | scrollPosition: UICollectionViewScrollPosition()) 474 | self.scrollToItem(item, animated: animated) 475 | self.selectedItem = item 476 | if notifySelection { 477 | self.delegate?.pickerView?(self, didSelectItem: item) 478 | } 479 | } 480 | 481 | // MARK: Delegate Handling 482 | /** 483 | Private. 484 | */ 485 | fileprivate func didEndScrolling() { 486 | switch self.pickerViewStyle { 487 | case .flat: 488 | let center = self.convert(self.collectionView.center, to: self.collectionView) 489 | if let indexPath = self.collectionView.indexPathForItem(at: center) { 490 | self.selectItem(indexPath.item, animated: true, notifySelection: true) 491 | } 492 | case .wheel: 493 | if let numberOfItems = self.dataSource?.numberOfItemsInPickerView(self) { 494 | for i in 0 ..< numberOfItems { 495 | let indexPath = IndexPath(item: i, section: 0) 496 | let cellSize = self.collectionView( 497 | self.collectionView, 498 | layout: self.collectionView.collectionViewLayout, 499 | sizeForItemAt: indexPath) 500 | if self.offsetForItem(i) + cellSize.width / 2 > self.collectionView.contentOffset.x { 501 | self.selectItem(i, animated: true, notifySelection: true) 502 | break 503 | } 504 | } 505 | } 506 | } 507 | } 508 | 509 | // MARK: UICollectionViewDataSource 510 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 511 | return self.dataSource != nil && self.dataSource!.numberOfItemsInPickerView(self) > 0 ? 1 : 0 512 | } 513 | 514 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 515 | return self.dataSource != nil ? self.dataSource!.numberOfItemsInPickerView(self) : 0 516 | } 517 | 518 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 519 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(AKCollectionViewCell.self), for: indexPath) as! AKCollectionViewCell 520 | if let title = self.dataSource?.pickerView?(self, titleForItem: indexPath.item) { 521 | cell.label.text = title 522 | cell.label.textColor = self.textColor 523 | cell.label.highlightedTextColor = self.highlightedTextColor 524 | cell.label.font = self.font 525 | cell.font = self.font 526 | cell.highlightedFont = self.highlightedFont 527 | cell.label.bounds = CGRect(origin: CGPoint.zero, size: self.sizeForString(title as NSString)) 528 | if let delegate = self.delegate { 529 | delegate.pickerView?(self, configureLabel: cell.label, forItem: indexPath.item) 530 | if let margin = delegate.pickerView?(self, marginForItem: indexPath.item) { 531 | cell.label.frame = cell.label.frame.insetBy(dx: -margin.width, dy: -margin.height) 532 | } 533 | } 534 | } else if let image = self.dataSource?.pickerView?(self, imageForItem: indexPath.item) { 535 | cell.imageView.image = image 536 | } 537 | cell._selected = (indexPath.item == self.selectedItem) 538 | return cell 539 | } 540 | 541 | // MARK: UICollectionViewDelegateFlowLayout 542 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 543 | var size = CGSize(width: self.interitemSpacing, height: collectionView.bounds.size.height) 544 | if let title = self.dataSource?.pickerView?(self, titleForItem: indexPath.item) { 545 | size.width += self.sizeForString(title as NSString).width 546 | if let margin = self.delegate?.pickerView?(self, marginForItem: indexPath.item) { 547 | size.width += margin.width * 2 548 | } 549 | } else if let image = self.dataSource?.pickerView?(self, imageForItem: indexPath.item) { 550 | size.width += image.size.width 551 | } 552 | return size 553 | } 554 | 555 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 556 | return 0.0 557 | } 558 | 559 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 560 | return 0.0 561 | } 562 | 563 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 564 | let number = self.collectionView(collectionView, numberOfItemsInSection: section) 565 | let firstIndexPath = IndexPath(item: 0, section: section) 566 | let firstSize = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, sizeForItemAt: firstIndexPath) 567 | let lastIndexPath = IndexPath(item: number - 1, section: section) 568 | let lastSize = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, sizeForItemAt: lastIndexPath) 569 | return UIEdgeInsetsMake( 570 | 0, (collectionView.bounds.size.width - firstSize.width) / 2, 571 | 0, (collectionView.bounds.size.width - lastSize.width) / 2 572 | ) 573 | } 574 | 575 | // MARK: UICollectionViewDelegate 576 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 577 | self.selectItem(indexPath.item, animated: true) 578 | } 579 | 580 | // MARK: UIScrollViewDelegate 581 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 582 | self.delegate?.scrollViewDidEndDecelerating?(scrollView) 583 | self.didEndScrolling() 584 | } 585 | 586 | public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 587 | self.delegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) 588 | if !decelerate { 589 | self.didEndScrolling() 590 | } 591 | } 592 | 593 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 594 | self.delegate?.scrollViewDidScroll?(scrollView) 595 | CATransaction.begin() 596 | CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions) 597 | self.collectionView.layer.mask?.frame = self.collectionView.bounds 598 | CATransaction.commit() 599 | } 600 | 601 | // MARK: AKCollectionViewLayoutDelegate 602 | fileprivate func pickerViewStyleForCollectionViewLayout(_ layout: AKCollectionViewLayout) -> AKPickerViewStyle { 603 | return self.pickerViewStyle 604 | } 605 | 606 | } 607 | 608 | -------------------------------------------------------------------------------- /AKPickerView/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 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /AKPickerViewSample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 66A74BA21A95074E009AB290 /* AKPickerView-Swift.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 66A74BA11A95074E009AB290 /* AKPickerView-Swift.podspec */; }; 11 | 66C001461AD380FC005F2650 /* AKPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F602881A8A16E60006FA7E /* AKPickerView.swift */; }; 12 | 66F602671A8A167C0006FA7E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F602661A8A167C0006FA7E /* AppDelegate.swift */; }; 13 | 66F602691A8A167C0006FA7E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F602681A8A167C0006FA7E /* ViewController.swift */; }; 14 | 66F6026C1A8A167C0006FA7E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 66F6026A1A8A167C0006FA7E /* Main.storyboard */; }; 15 | 66F6026E1A8A167C0006FA7E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 66F6026D1A8A167C0006FA7E /* Images.xcassets */; }; 16 | 66F602711A8A167C0006FA7E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 66F6026F1A8A167C0006FA7E /* LaunchScreen.xib */; }; 17 | 66F602891A8A16E60006FA7E /* AKPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F602881A8A16E60006FA7E /* AKPickerView.swift */; }; 18 | 66F602941A8A17220006FA7E /* AKPickerView.h in Headers */ = {isa = PBXBuildFile; fileRef = 66F602931A8A17220006FA7E /* AKPickerView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 66A74BA11A95074E009AB290 /* AKPickerView-Swift.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "AKPickerView-Swift.podspec"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 23 | 66F602611A8A167C0006FA7E /* AKPickerViewSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AKPickerViewSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 66F602651A8A167C0006FA7E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | 66F602661A8A167C0006FA7E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | 66F602681A8A167C0006FA7E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 27 | 66F6026B1A8A167C0006FA7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 28 | 66F6026D1A8A167C0006FA7E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 29 | 66F602701A8A167C0006FA7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 30 | 66F602881A8A16E60006FA7E /* AKPickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKPickerView.swift; sourceTree = ""; }; 31 | 66F6028F1A8A17220006FA7E /* AKPickerView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AKPickerView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 66F602921A8A17220006FA7E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 66F602931A8A17220006FA7E /* AKPickerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AKPickerView.h; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 66F6025E1A8A167C0006FA7E /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | 66F6028B1A8A17220006FA7E /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | 66F602581A8A167C0006FA7E = { 55 | isa = PBXGroup; 56 | children = ( 57 | 66A74BA11A95074E009AB290 /* AKPickerView-Swift.podspec */, 58 | 66F602901A8A17220006FA7E /* AKPickerView */, 59 | 66F602631A8A167C0006FA7E /* AKPickerViewSample */, 60 | 66F602621A8A167C0006FA7E /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | 66F602621A8A167C0006FA7E /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 66F602611A8A167C0006FA7E /* AKPickerViewSample.app */, 68 | 66F6028F1A8A17220006FA7E /* AKPickerView.framework */, 69 | ); 70 | name = Products; 71 | sourceTree = ""; 72 | }; 73 | 66F602631A8A167C0006FA7E /* AKPickerViewSample */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 66F602661A8A167C0006FA7E /* AppDelegate.swift */, 77 | 66F602681A8A167C0006FA7E /* ViewController.swift */, 78 | 66F6026A1A8A167C0006FA7E /* Main.storyboard */, 79 | 66F6026D1A8A167C0006FA7E /* Images.xcassets */, 80 | 66F6026F1A8A167C0006FA7E /* LaunchScreen.xib */, 81 | 66F602641A8A167C0006FA7E /* Supporting Files */, 82 | ); 83 | path = AKPickerViewSample; 84 | sourceTree = ""; 85 | }; 86 | 66F602641A8A167C0006FA7E /* Supporting Files */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 66F602651A8A167C0006FA7E /* Info.plist */, 90 | ); 91 | name = "Supporting Files"; 92 | sourceTree = ""; 93 | }; 94 | 66F602901A8A17220006FA7E /* AKPickerView */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 66F602931A8A17220006FA7E /* AKPickerView.h */, 98 | 66F602881A8A16E60006FA7E /* AKPickerView.swift */, 99 | 66F602911A8A17220006FA7E /* Supporting Files */, 100 | ); 101 | path = AKPickerView; 102 | sourceTree = ""; 103 | }; 104 | 66F602911A8A17220006FA7E /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 66F602921A8A17220006FA7E /* Info.plist */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXHeadersBuildPhase section */ 115 | 66F6028C1A8A17220006FA7E /* Headers */ = { 116 | isa = PBXHeadersBuildPhase; 117 | buildActionMask = 2147483647; 118 | files = ( 119 | 66F602941A8A17220006FA7E /* AKPickerView.h in Headers */, 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | /* End PBXHeadersBuildPhase section */ 124 | 125 | /* Begin PBXNativeTarget section */ 126 | 66F602601A8A167C0006FA7E /* AKPickerViewSample */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = 66F602801A8A167C0006FA7E /* Build configuration list for PBXNativeTarget "AKPickerViewSample" */; 129 | buildPhases = ( 130 | 66F6025D1A8A167C0006FA7E /* Sources */, 131 | 66F6025E1A8A167C0006FA7E /* Frameworks */, 132 | 66F6025F1A8A167C0006FA7E /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = AKPickerViewSample; 139 | productName = AKPickerViewSample; 140 | productReference = 66F602611A8A167C0006FA7E /* AKPickerViewSample.app */; 141 | productType = "com.apple.product-type.application"; 142 | }; 143 | 66F6028E1A8A17220006FA7E /* AKPickerView */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 66F602A21A8A17220006FA7E /* Build configuration list for PBXNativeTarget "AKPickerView" */; 146 | buildPhases = ( 147 | 66F6028A1A8A17220006FA7E /* Sources */, 148 | 66F6028B1A8A17220006FA7E /* Frameworks */, 149 | 66F6028C1A8A17220006FA7E /* Headers */, 150 | 66F6028D1A8A17220006FA7E /* Resources */, 151 | ); 152 | buildRules = ( 153 | ); 154 | dependencies = ( 155 | ); 156 | name = AKPickerView; 157 | productName = AKPickerView; 158 | productReference = 66F6028F1A8A17220006FA7E /* AKPickerView.framework */; 159 | productType = "com.apple.product-type.framework"; 160 | }; 161 | /* End PBXNativeTarget section */ 162 | 163 | /* Begin PBXProject section */ 164 | 66F602591A8A167C0006FA7E /* Project object */ = { 165 | isa = PBXProject; 166 | attributes = { 167 | LastSwiftUpdateCheck = 0700; 168 | LastUpgradeCheck = 0810; 169 | ORGANIZATIONNAME = "Akio Yasui"; 170 | TargetAttributes = { 171 | 66F602601A8A167C0006FA7E = { 172 | CreatedOnToolsVersion = 6.1.1; 173 | LastSwiftMigration = 0810; 174 | }; 175 | 66F6028E1A8A17220006FA7E = { 176 | CreatedOnToolsVersion = 6.1.1; 177 | }; 178 | }; 179 | }; 180 | buildConfigurationList = 66F6025C1A8A167C0006FA7E /* Build configuration list for PBXProject "AKPickerViewSample" */; 181 | compatibilityVersion = "Xcode 3.2"; 182 | developmentRegion = English; 183 | hasScannedForEncodings = 0; 184 | knownRegions = ( 185 | en, 186 | Base, 187 | ); 188 | mainGroup = 66F602581A8A167C0006FA7E; 189 | productRefGroup = 66F602621A8A167C0006FA7E /* Products */; 190 | projectDirPath = ""; 191 | projectRoot = ""; 192 | targets = ( 193 | 66F602601A8A167C0006FA7E /* AKPickerViewSample */, 194 | 66F6028E1A8A17220006FA7E /* AKPickerView */, 195 | ); 196 | }; 197 | /* End PBXProject section */ 198 | 199 | /* Begin PBXResourcesBuildPhase section */ 200 | 66F6025F1A8A167C0006FA7E /* Resources */ = { 201 | isa = PBXResourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 66F6026C1A8A167C0006FA7E /* Main.storyboard in Resources */, 205 | 66A74BA21A95074E009AB290 /* AKPickerView-Swift.podspec in Resources */, 206 | 66F602711A8A167C0006FA7E /* LaunchScreen.xib in Resources */, 207 | 66F6026E1A8A167C0006FA7E /* Images.xcassets in Resources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 66F6028D1A8A17220006FA7E /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | 66F6025D1A8A167C0006FA7E /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | 66F602691A8A167C0006FA7E /* ViewController.swift in Sources */, 226 | 66F602671A8A167C0006FA7E /* AppDelegate.swift in Sources */, 227 | 66F602891A8A16E60006FA7E /* AKPickerView.swift in Sources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | 66F6028A1A8A17220006FA7E /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | 66C001461AD380FC005F2650 /* AKPickerView.swift in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXSourcesBuildPhase section */ 240 | 241 | /* Begin PBXVariantGroup section */ 242 | 66F6026A1A8A167C0006FA7E /* Main.storyboard */ = { 243 | isa = PBXVariantGroup; 244 | children = ( 245 | 66F6026B1A8A167C0006FA7E /* Base */, 246 | ); 247 | name = Main.storyboard; 248 | sourceTree = ""; 249 | }; 250 | 66F6026F1A8A167C0006FA7E /* LaunchScreen.xib */ = { 251 | isa = PBXVariantGroup; 252 | children = ( 253 | 66F602701A8A167C0006FA7E /* Base */, 254 | ); 255 | name = LaunchScreen.xib; 256 | sourceTree = ""; 257 | }; 258 | /* End PBXVariantGroup section */ 259 | 260 | /* Begin XCBuildConfiguration section */ 261 | 66F6027E1A8A167C0006FA7E /* Debug */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 266 | CLANG_CXX_LIBRARY = "libc++"; 267 | CLANG_ENABLE_MODULES = YES; 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INFINITE_RECURSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 281 | COPY_PHASE_STRIP = NO; 282 | ENABLE_STRICT_OBJC_MSGSEND = YES; 283 | ENABLE_TESTABILITY = YES; 284 | GCC_C_LANGUAGE_STANDARD = gnu99; 285 | GCC_DYNAMIC_NO_PIC = NO; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_OPTIMIZATION_LEVEL = 0; 288 | GCC_PREPROCESSOR_DEFINITIONS = ( 289 | "DEBUG=1", 290 | "$(inherited)", 291 | ); 292 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 300 | MTL_ENABLE_DEBUG_INFO = YES; 301 | ONLY_ACTIVE_ARCH = YES; 302 | SDKROOT = iphoneos; 303 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 304 | SWIFT_VERSION = 3.0.1; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | }; 307 | name = Debug; 308 | }; 309 | 66F6027F1A8A167C0006FA7E /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 314 | CLANG_CXX_LIBRARY = "libc++"; 315 | CLANG_ENABLE_MODULES = YES; 316 | CLANG_ENABLE_OBJC_ARC = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 329 | COPY_PHASE_STRIP = YES; 330 | ENABLE_NS_ASSERTIONS = NO; 331 | ENABLE_STRICT_OBJC_MSGSEND = YES; 332 | GCC_C_LANGUAGE_STANDARD = gnu99; 333 | GCC_NO_COMMON_BLOCKS = YES; 334 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 335 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 336 | GCC_WARN_UNDECLARED_SELECTOR = YES; 337 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 338 | GCC_WARN_UNUSED_FUNCTION = YES; 339 | GCC_WARN_UNUSED_VARIABLE = YES; 340 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 341 | MTL_ENABLE_DEBUG_INFO = NO; 342 | SDKROOT = iphoneos; 343 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 344 | SWIFT_VERSION = 3.0.1; 345 | TARGETED_DEVICE_FAMILY = "1,2"; 346 | VALIDATE_PRODUCT = YES; 347 | }; 348 | name = Release; 349 | }; 350 | 66F602811A8A167C0006FA7E /* Debug */ = { 351 | isa = XCBuildConfiguration; 352 | buildSettings = { 353 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 354 | DEVELOPMENT_TEAM = ""; 355 | INFOPLIST_FILE = AKPickerViewSample/Info.plist; 356 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 357 | PRODUCT_BUNDLE_IDENTIFIER = sh.aky.AKPickerViewSample; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | SWIFT_VERSION = 3.0.1; 360 | }; 361 | name = Debug; 362 | }; 363 | 66F602821A8A167C0006FA7E /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 367 | DEVELOPMENT_TEAM = ""; 368 | INFOPLIST_FILE = AKPickerViewSample/Info.plist; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = sh.aky.AKPickerViewSample; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SWIFT_VERSION = 3.0.1; 373 | }; 374 | name = Release; 375 | }; 376 | 66F602A31A8A17220006FA7E /* Debug */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 380 | CURRENT_PROJECT_VERSION = 1; 381 | DEFINES_MODULE = YES; 382 | DYLIB_COMPATIBILITY_VERSION = 1; 383 | DYLIB_CURRENT_VERSION = 1; 384 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 385 | GCC_PREPROCESSOR_DEFINITIONS = ( 386 | "DEBUG=1", 387 | "$(inherited)", 388 | ); 389 | INFOPLIST_FILE = AKPickerView/Info.plist; 390 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 392 | ONLY_ACTIVE_ARCH = YES; 393 | PRODUCT_BUNDLE_IDENTIFIER = "org.prioirs.akkyie.$(PRODUCT_NAME:rfc1034identifier)"; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SKIP_INSTALL = YES; 396 | SWIFT_VERSION = 3.0.1; 397 | VERSIONING_SYSTEM = "apple-generic"; 398 | VERSION_INFO_PREFIX = ""; 399 | }; 400 | name = Debug; 401 | }; 402 | 66F602A41A8A17220006FA7E /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 406 | CURRENT_PROJECT_VERSION = 1; 407 | DEFINES_MODULE = YES; 408 | DYLIB_COMPATIBILITY_VERSION = 1; 409 | DYLIB_CURRENT_VERSION = 1; 410 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 411 | INFOPLIST_FILE = AKPickerView/Info.plist; 412 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | ONLY_ACTIVE_ARCH = NO; 415 | PRODUCT_BUNDLE_IDENTIFIER = "org.prioirs.akkyie.$(PRODUCT_NAME:rfc1034identifier)"; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | SKIP_INSTALL = YES; 418 | SWIFT_VERSION = 3.0.1; 419 | VERSIONING_SYSTEM = "apple-generic"; 420 | VERSION_INFO_PREFIX = ""; 421 | }; 422 | name = Release; 423 | }; 424 | /* End XCBuildConfiguration section */ 425 | 426 | /* Begin XCConfigurationList section */ 427 | 66F6025C1A8A167C0006FA7E /* Build configuration list for PBXProject "AKPickerViewSample" */ = { 428 | isa = XCConfigurationList; 429 | buildConfigurations = ( 430 | 66F6027E1A8A167C0006FA7E /* Debug */, 431 | 66F6027F1A8A167C0006FA7E /* Release */, 432 | ); 433 | defaultConfigurationIsVisible = 0; 434 | defaultConfigurationName = Release; 435 | }; 436 | 66F602801A8A167C0006FA7E /* Build configuration list for PBXNativeTarget "AKPickerViewSample" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | 66F602811A8A167C0006FA7E /* Debug */, 440 | 66F602821A8A167C0006FA7E /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | 66F602A21A8A17220006FA7E /* Build configuration list for PBXNativeTarget "AKPickerView" */ = { 446 | isa = XCConfigurationList; 447 | buildConfigurations = ( 448 | 66F602A31A8A17220006FA7E /* Debug */, 449 | 66F602A41A8A17220006FA7E /* Release */, 450 | ); 451 | defaultConfigurationIsVisible = 0; 452 | defaultConfigurationName = Release; 453 | }; 454 | /* End XCConfigurationList section */ 455 | }; 456 | rootObject = 66F602591A8A167C0006FA7E /* Project object */; 457 | } 458 | -------------------------------------------------------------------------------- /AKPickerViewSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AKPickerViewSample.xcodeproj/xcshareddata/xcschemes/AKPickerView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /AKPickerViewSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AKPickerViewSample 4 | // 5 | // Created by Akio Yasui on 2/10/15. 6 | // Copyright (c) 2015 Akio Yasui. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | } 15 | 16 | -------------------------------------------------------------------------------- /AKPickerViewSample/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 | -------------------------------------------------------------------------------- /AKPickerViewSample/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 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /AKPickerViewSample/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 | } -------------------------------------------------------------------------------- /AKPickerViewSample/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 | 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 | -------------------------------------------------------------------------------- /AKPickerViewSample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AKPickerViewSample 4 | // 5 | // Created by Akio Yasui on 2/10/15. 6 | // Copyright (c) 2015 Akio Yasui. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, AKPickerViewDataSource, AKPickerViewDelegate { 12 | 13 | @IBOutlet var pickerView: AKPickerView! 14 | 15 | let titles = ["Tokyo", "Kanagawa", "Osaka", "Aichi", "Saitama", "Chiba", "Hyogo", "Hokkaido", "Fukuoka", "Shizuoka"] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | self.pickerView.delegate = self 20 | self.pickerView.dataSource = self 21 | 22 | self.pickerView.font = UIFont(name: "HelveticaNeue-Light", size: 20)! 23 | self.pickerView.highlightedFont = UIFont(name: "HelveticaNeue", size: 20)! 24 | self.pickerView.pickerViewStyle = .wheel 25 | self.pickerView.maskDisabled = false 26 | self.pickerView.reloadData() 27 | } 28 | 29 | // MARK: - AKPickerViewDataSource 30 | 31 | func numberOfItemsInPickerView(_ pickerView: AKPickerView) -> Int { 32 | return self.titles.count 33 | } 34 | 35 | /* 36 | 37 | Image Support 38 | ------------- 39 | Please comment '-pickerView:titleForItem:' entirely and 40 | uncomment '-pickerView:imageForItem:' to see how it works. 41 | 42 | */ 43 | func pickerView(_ pickerView: AKPickerView, titleForItem item: Int) -> String { 44 | return self.titles[item] 45 | } 46 | 47 | func pickerView(_ pickerView: AKPickerView, imageForItem item: Int) -> UIImage { 48 | return UIImage(named: self.titles[item])! 49 | } 50 | 51 | // MARK: - AKPickerViewDelegate 52 | 53 | func pickerView(_ pickerView: AKPickerView, didSelectItem item: Int) { 54 | print("Your favorite city is \(self.titles[item])") 55 | } 56 | 57 | /* 58 | 59 | Label Customization 60 | ------------------- 61 | You can customize labels by their any properties (except for fonts,) 62 | and margin around text. 63 | These methods are optional, and ignored when using images. 64 | 65 | */ 66 | 67 | /* 68 | func pickerView(pickerView: AKPickerView, configureLabel label: UILabel, forItem item: Int) { 69 | label.textColor = UIColor.lightGrayColor() 70 | label.highlightedTextColor = UIColor.whiteColor() 71 | label.backgroundColor = UIColor( 72 | hue: CGFloat(item) / CGFloat(self.titles.count), 73 | saturation: 1.0, 74 | brightness: 0.5, 75 | alpha: 1.0) 76 | } 77 | 78 | func pickerView(pickerView: AKPickerView, marginForItem item: Int) -> CGSize { 79 | return CGSizeMake(40, 20) 80 | } 81 | */ 82 | 83 | /* 84 | 85 | UIScrollViewDelegate Support 86 | ---------------------------- 87 | AKPickerViewDelegate inherits UIScrollViewDelegate. 88 | You can use UIScrollViewDelegate methods 89 | by simply setting pickerView's delegate. 90 | 91 | */ 92 | 93 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 94 | // println("\(scrollView.contentOffset.x)") 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Akkyie Y 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AKPickerView 2 | ============ 3 | 4 | Screenshot 5 | Screenshot 6 | 7 | A simple yet customizable horizontal picker view. 8 | 9 | The __Swift__ port of [AKPickerView](https://github.com/Akkyie/AKPickerView). 10 | 11 | Works on iOS 7 and 8. 12 | 13 | Installation 14 | ------------ 15 | 16 | ###[CocoaPods](http://cocoapods.org/): 17 | In your `Podfile`: 18 | ``` 19 | pod "AKPickerView-Swift" 20 | ``` 21 | And in your `*.swift`: 22 | ```swift 23 | import AKPickerView_Swift 24 | ``` 25 | 26 | ###[Carthage](https://github.com/Carthage/Carthage): 27 | In your `Cartfile`: 28 | ``` 29 | github "Akkyie/AKPickerView-Swift" 30 | ``` 31 | And in your `*.swift`: 32 | ```swift 33 | import AKPickerView 34 | ``` 35 | 36 | ###Manual Install 37 | Add `AKPickerView.swift` into your Xcode project. 38 | 39 | Usage 40 | ----- 41 | 42 | 1. Instantiate and set `delegate` and `dataSource` as you know, 43 | 44 | ```swift 45 | self.pickerView = AKPickerView(frame: <#frame#>) 46 | self.pickerView.delegate = self 47 | self.pickerView.dataSource = self 48 | ``` 49 | 50 | 1. then specify the number of items using `AKPickerViewDataSource`, 51 | ```swift 52 | func numberOfItemsInPickerView(pickerView: AKPickerView) -> Int {} 53 | ``` 54 | 55 | 1. and contents to be shown. You can use either texts or images: 56 | ```swift 57 | func pickerView(pickerView: AKPickerView, titleForItem item: Int) -> NSString {} 58 | // OR 59 | func pickerView(pickerView: AKPickerView, imageForItem item: Int) -> UIImage {} 60 | ``` 61 | 62 | - Using both texts and images are currently not supported. When you implement both, `titleForItem` will be called and the other won't. 63 | - You currently cannot specify image sizes; AKPickerView shows the original image in its original size. Resize your images in advance if you need. 64 | 65 | 1. You can change its appearance with properties below: 66 | 67 | ```swift 68 | var font: UIFont 69 | var highlightedFont: UIFont 70 | var textColor: UIColor 71 | var highlightedTextColor: UIColor 72 | var interitemSpacing: CGFloat 73 | var viewDepth: CGFloat 74 | var pickerViewStyle: AKPickerViewStyle 75 | ``` 76 | 77 | - All cells are laid out depending on the largest font, so large differnce between the sizes of *font* and *highlightedFont* is NOT recommended. 78 | - viewDepth property affects the perspective distortion. A value near the screen's height or width is recommended. 79 | 80 | 1. After all settings, **never forget to reload your picker**. 81 | ```swift 82 | self.pickerView.reloadData() 83 | ``` 84 | 85 | 1. Optional: You can use `AKPickerViewDelegate` methods to observe selection changes: 86 | ```swift 87 | func pickerView(pickerView: AKPickerView, didSelectItem item: Int) {} 88 | ``` 89 | Additionally, you can also use `UIScrollViewDelegate` methods to observe scrolling. 90 | 91 | For more detail, see the sample project. 92 | 93 | Contact 94 | ------- 95 | 96 | @akkyie http://twitter.com/akkyie 97 | 98 | License 99 | ------- 100 | MIT. See LICENSE. 101 | -------------------------------------------------------------------------------- /Screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkyie/AKPickerView-Swift/1ee8c7310bdef0f31f8fb92fbab0ab05978539f6/Screenshot.gif -------------------------------------------------------------------------------- /Screenshot2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkyie/AKPickerView-Swift/1ee8c7310bdef0f31f8fb92fbab0ab05978539f6/Screenshot2.gif --------------------------------------------------------------------------------