/Pick

Protocol-Oriented PickerViewController

Primary LanguageSwiftMIT LicenseMIT

Pick

Protocol-Oriented PickerViewController. You can pick whatever you want from defined data source by you.

GitHub release Language Carthage Compatible CocoaPods CocoaPodsDL

Feature

  • Can pick single(multiple) item(s).
  • Can use flexible DataSource.
  • Automatic register/dequeue cell inside picker view.
  • Support Pre-fetching.
  • Can customize using options.

How to use

Define Cell

Create Cell that inherit PickableCell.

final class AssetCell: PickableCell {
    private lazy var imageView: UIImageView = {
        let view = UIImageView(frame: .zero)
        view.contentMode = .scaleAspectFill
        view.clipsToBounds = true
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubview(imageView)
        imageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private static let fetchOption: PHImageRequestOptions = {
        let options = PHImageRequestOptions()
        options.isSynchronous = true
        options.resizeMode = .exact
        return options
    }()

    private var asset: PHAsset?
    func configure(with asset: PHAsset) {
        self.asset = asset
        PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 300, height: 300), contentMode: .aspectFill, options: type(of: self).fetchOption) { [weak self] (image, _) in
            if self?.asset?.localIdentifier == asset.localIdentifier {
                self?.imageView.image = image
            }
        }
    }
}

NOTE: If you want to use custom cell using xib, add code below.

final class AssetCell: PickableCell {
    override class var registerMode: RegisterMode {
        return .nib(defaultNib)
    }

    // or

    override class var registerMode: RegisterMode {
        return .nib(UINib(nibName: "AssetCell", bundle: nil))
    }
}

Define DataSource

Create DataSource that adapts PickableDataSource. You have to implement:

  • typealias Cell
  • typealias Item
  • configure(cell: Cell, at indexPath: IndexPath)
  • var numberOfItems: Int
  • pickItems(indexes: [Int]) -> [Item]
extension PHAsset: Pickable {
    static func ==(lhs: PHAsset, rhs: PHAsset) -> Bool {
        return rhs.localIdentifier == lhs.localIdentifier
    }
}

final class AssetDataSource: NSObject, PickableDataSource {
    typealias Cell = AssetCell
    typealias Item = PHAsset

    var items: [PHAsset]
    var selectedItems: [PHAsset]

    init(selectedItems: [PHAsset] = []) {
        self.selectedItems = selectedItems
        self.items = []
        super.init()
        fetchCameraroll()
        PHPhotoLibrary.shared().register(self)
    }

    func fetchCameraroll() {
        guard let cameraroll = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil).firstObject else {
            return
        }
        let assets = PHAsset.fetchAssets(in: cameraroll, options: nil)
        items = assets.objects(at: IndexSet(0..<assets.count))
    }

    func configure(cell: Cell, at indexPath: IndexPath) {
        cell.configure(with: items[indexPath.item])
    }

    var numberOfItems: Int {
        return items.count
    }

    func pickItems(indexes: [Int]) -> [Item] {
        return indexes.map { items[$0] }
    }

    deinit {
        PHPhotoLibrary.shared().unregisterChangeObserver(self)
    }
}

extension AssetDataSource: PHPhotoLibraryChangeObserver {
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        DispatchQueue.main.sync { [weak self] in
            guard let `self` = self else {
                return
            }

            self.fetchCameraroll()
            self.notifyUpdate()
        }
    }
}

In order to update picker view after updating datasource, call notifyUpdate()

final class SomeDataSource: NSObject, PickableDataSource {
    var items: [Int] = []

    func update() {
        items = ...
        notifyUpdate()
    }
}

Setup ViewController

Set data source to PickerViewController(PickerNavigationController). Also you can customize using PickerOptions.

let nav = PickerNavigationController(dataSource: AssetDataSource())
nav.options = {
    let options = PickerOptions()
    options.limitOfSelection = 3
    options.selectedBorderColor = .red
    options.viewTitle = "Camera Roll"
    return options
}()

nav.pickItemsHandler = { assets in
    print(assets)
}

viewController.present(nav, animated: true, completion: nil)

enjoy 😄

Todo

  • Document comment.

Requirements

  • iOS 10.0+
  • Xcode 9+
  • Swift 4+

Installation

Carthage

  • Add the following to your Cartfile:
github "sgr-ksmt/Pick" ~> 0.3
  • Run carthage update
  • Add the framework as described.
    Details: Carthage Readme

CocoaPods

Pick is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Pick', '~> 0.3'

and run pod install

Manually Install

Download all *.swift files and put your project.

Change log

Change log is here.

Special Thanks 🎉

  • miuP [Core Contributor]

Communication

  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.:muscle:

License

Pick is under MIT license. See the LICENSE file for more info.