You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
14 KiB

5 years ago
  1. <img src="./Images/tlphotologo.png">
  2. [![Version](https://img.shields.io/cocoapods/v/TLPhotoPicker.svg?style=flat)](http://cocoapods.org/pods/TLPhotoPicker)
  3. [![License](https://img.shields.io/cocoapods/l/TLPhotoPicker.svg?style=flat)](http://cocoapods.org/pods/TLPhotoPicker)
  4. [![Platform](https://img.shields.io/cocoapods/p/TLPhotoPicker.svg?style=flat)](http://cocoapods.org/pods/TLPhotoPicker)
  5. ![Swift](https://img.shields.io/badge/%20in-swift%205.0-orange.svg)
  6. ## Written in Swift 5.0
  7. TLPhotoPicker enables application to pick images and videos from multiple smart album in iOS, similar to the current facebook app.
  8. ## Demo 🙉
  9. | Facebook Picker | TLPhotoPicker |
  10. | ------------- | ------------- |
  11. | ![Facebook Picker](Images/facebook_ex.gif) | ![TLPhotoPicker](Images/tlphotopicker_ex.gif) |
  12. ## Features
  13. - support smart album collection.
  14. - camera roll, selfies, panoramas, favorites, videos, custom users album
  15. - selected order index.
  16. - playback video and live photos.
  17. - just one. playback first video or live Photo in bounds of visible cell.
  18. - display video duration.
  19. - async phasset request and displayed cell.
  20. - scrolling performance is better than facebook in displaying video assets collection.
  21. - custom cell
  22. - custom display and selection rules
  23. - reload of changes that occur in the Photos library.
  24. - support iCloud Photo Library
  25. | Smart album collection | LivePhotoCell | VideoPhotoCell | PhotoCell | CustomCell(instagram) |
  26. | ------------- | ------------- | ------------- | ------------- | ------------- |
  27. | ![Facebook Picker](Images/smartalbum.png) | ![LivePhotoCell](Images/livephotocell.png) | ![VideoPhotoCell](Images/videophotocell.png) | ![PhotoCell](Images/photocell.png) | ![PhotoCell](Images/customcell.png) |
  28. Custom Camera Cell
  29. | Live CameraCell |
  30. | ------------- |
  31. | ![Like Line](Images/custom_cameracell.gif)
  32. ## Installation
  33. ### Requirements
  34. - Swift 5.0 ( Swift 4.2 -> use 'version 1.8.3' )
  35. - iOS 9.1 (for use live photos)
  36. ### Cocoapods
  37. TLPhotoPicker is available through [CocoaPods](http://cocoapods.org). To install
  38. it, simply add the following line to your Podfile:
  39. ```ruby
  40. platform :ios, '9.1'
  41. pod "TLPhotoPicker"
  42. ```
  43. ### Carthage
  44. Carthage is a simple, decentralized dependency manager for Cocoa.
  45. Specify TLPhotoPicker into your project's Cartfile:
  46. ```
  47. github "tilltue/TLPhotoPicker"
  48. ```
  49. > Don't forget the Privacy Description in `info.plist`.
  50. <img src="./Images/Privacy.png">
  51. ## Usage
  52. **use delegate**
  53. You can choose delegate method or closure for handle picker event.
  54. ```swift
  55. class ViewController: UIViewController,TLPhotosPickerViewControllerDelegate {
  56. var selectedAssets = [TLPHAsset]()
  57. @IBAction func pickerButtonTap() {
  58. let viewController = TLPhotosPickerViewController()
  59. viewController.delegate = self
  60. var configure = TLPhotosPickerConfigure()
  61. //configure.nibSet = (nibName: "CustomCell_Instagram", bundle: Bundle.main) // If you want use your custom cell..
  62. self.present(viewController, animated: true, completion: nil)
  63. }
  64. //TLPhotosPickerViewControllerDelegate
  65. func dismissPhotoPicker(withTLPHAssets: [TLPHAsset]) {
  66. // use selected order, fullresolution image
  67. self.selectedAssets = withTLPHAssets
  68. }
  69. func dismissPhotoPicker(withPHAssets: [PHAsset]) {
  70. // if you want to used phasset.
  71. }
  72. func photoPickerDidCancel() {
  73. // cancel
  74. }
  75. func dismissComplete() {
  76. // picker viewcontroller dismiss completion
  77. }
  78. func canSelectAsset(phAsset: PHAsset) -> Bool {
  79. //Custom Rules & Display
  80. //You can decide in which case the selection of the cell could be forbidden.
  81. }
  82. func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController) {
  83. // exceed max selection
  84. }
  85. func handleNoAlbumPermissions(picker: TLPhotosPickerViewController) {
  86. // handle denied albums permissions case
  87. }
  88. func handleNoCameraPermissions(picker: TLPhotosPickerViewController) {
  89. // handle denied camera permissions case
  90. }
  91. }
  92. ```
  93. **use closure**
  94. ```swift
  95. init(withPHAssets: (([PHAsset]) -> Void)? = nil, didCancel: ((Void) -> Void)? = nil)
  96. init(withTLPHAssets: (([TLPHAsset]) -> Void)? = nil, didCancel: ((Void) -> Void)? = nil)
  97. var canSelectAsset: ((PHAsset) -> Bool)? = nil
  98. var didExceedMaximumNumberOfSelection: ((TLPhotosPickerViewController) -> Void)? = nil
  99. var handleNoAlbumPermissions: ((TLPhotosPickerViewController) -> Void)? = nil
  100. var handleNoCameraPermissions: ((TLPhotosPickerViewController) -> Void)? = nil
  101. var dismissCompletion: (() -> Void)? = nil
  102. ```
  103. ```swift
  104. class ViewController: UIViewController,TLPhotosPickerViewControllerDelegate {
  105. var selectedAssets = [TLPHAsset]()
  106. @IBAction func pickerButtonTap() {
  107. let viewController = TLPhotosPickerViewController(withTLPHAssets: { [weak self] (assets) in // TLAssets
  108. self?.selectedAssets = assets
  109. }, didCancel: nil)
  110. viewController.didExceedMaximumNumberOfSelection = { [weak self] (picker) in
  111. //exceed max selection
  112. }
  113. viewController.handleNoAlbumPermissions = { [weak self] (picker) in
  114. // handle denied albums permissions case
  115. }
  116. viewController.handleNoCameraPermissions = { [weak self] (picker) in
  117. // handle denied camera permissions case
  118. }
  119. viewController.selectedAssets = self.selectedAssets
  120. self.present(viewController, animated: true, completion: nil)
  121. }
  122. }
  123. ```
  124. **Custom Cell**
  125. Custom Cell must subclass TLPhotoCollectionViewCell
  126. ```Swift
  127. class CustomCell_Instagram: TLPhotoCollectionViewCell {
  128. }
  129. //If you want custom camera cell?
  130. //only used camera cell
  131. [Sample](https://github.com/tilltue/TLPhotoPicker/blob/master/Example/TLPhotoPicker/CustomCameraCell.swift)
  132. //Adding the possibility to handle cell display according to a specific conditions
  133. func update(with phAsset: PHAsset)
  134. func selectedCell()
  135. func willDisplayCell()
  136. func endDisplayingCell()
  137. ```
  138. **Custom Rules & Display**
  139. You can implement your own rules to handle the cell display. You can decide in which case the selection of the cell could be forbidden.
  140. For example, if you want to disable the selection of a cell if its width is under 300, you can follow these steps:
  141. - Override the update method of your custom cell and add your own display rule
  142. ```swift
  143. override func update(with phAsset: PHAsset) {
  144. super.update(with: phAsset)
  145. self.sizeRequiredOverlayView?.isHidden = !(phAsset.pixelHeight <= 300 && phAsset.pixelWidth <= 300)
  146. }
  147. ```
  148. In this code, we show an overlay when the height and width required values are not satisified.
  149. - When you instanciate a `TLPhotosPickerViewController` subclass, you can pass a closure called `canSelectAsset` to handle the selection according to some rules. ( or delegate)
  150. ```Swift
  151. //use delegate
  152. public protocol TLPhotosPickerViewControllerDelegate: class {
  153. ...
  154. func canSelectAsset(phAsset: PHAsset) -> Bool
  155. ...
  156. }
  157. extension UserViewController: TLPhotosPickerViewControllerDelegate {
  158. func canSelectAsset(phAsset: PHAsset) -> Bool {
  159. if asset.pixelHeight < 100 || asset.pixelWidth < 100 {
  160. self?.showUnsatisifiedSizeAlert(vc: viewController)
  161. return false
  162. }
  163. return true
  164. }
  165. }
  166. //or use closure
  167. viewController.canSelectAsset = { [weak self] asset -> Bool in
  168. if asset.pixelHeight < 100 || asset.pixelWidth < 100 {
  169. self?.showUnsatisifiedSizeAlert(vc: viewController)
  170. return false
  171. }
  172. return true
  173. }
  174. ```
  175. In this code, we show an alert when the condition in the closure are not satisfiied.
  176. **TLPHAsset**
  177. ```swift
  178. public struct TLPHAsset {
  179. public enum AssetType {
  180. case photo,video,livePhoto
  181. }
  182. // phasset
  183. public var phAsset: PHAsset? = nil
  184. // selected order index
  185. public var selectedOrder: Int = 0
  186. // asset type
  187. public var type: AssetType
  188. // get full resolution image
  189. public var fullResolutionImage: UIImage?
  190. // get photo file size (async)
  191. public func photoSize(options: PHImageRequestOptions? = nil ,completion: @escaping ((Int)->Void), livePhotoVideoSize: Bool = false)
  192. // get video file size (async)
  193. public func videoSize(options: PHVideoRequestOptions? = nil, completion: @escaping ((Int)->Void))
  194. // get async icloud image (download)
  195. @discardableResult
  196. public func cloudImageDownload(progressBlock: @escaping (Double) -> Void, completionBlock:@escaping (UIImage?)-> Void ) -> PHImageRequestID?
  197. // get original media file async copy temporary media file ( photo(png,gif...etc.) and video ) -> Don't forget, You should delete temporary file.
  198. // parmeter : convertLivePhotosToJPG
  199. // false : If you want mov file at live photos
  200. // true : If you want png file at live photos ( HEIC )
  201. public func tempCopyMediaFile(videoRequestOptions: PHVideoRequestOptions? = nil, imageRequestOptions: PHImageRequestOptions? = nil, exportPreset: String = AVAssetExportPresetHighestQuality, convertLivePhotosToJPG: Bool = false, progressBlock:((Double) -> Void)? = nil, completionBlock:@escaping ((URL,String) -> Void)) -> PHImageRequestID?
  202. //Apparently, this method is not be safety to export a video.
  203. //There is many way that export a video.
  204. //This method was one of them.
  205. public func exportVideoFile(options: PHVideoRequestOptions? = nil, progressBlock:((Float) -> Void)? = nil, completionBlock:@escaping ((URL,String) -> Void))
  206. // get original asset file name
  207. public var originalFileName: String?
  208. }
  209. ```
  210. > Note: convenience export method
  211. > fullResolutionImage, cloudImageDownload, tempCopyMediaFile, exportVideoFile
  212. > It's not enough if you wanted to use more complicated export asset options. ( progress, export type, etc..)
  213. ## Customize
  214. ```swift
  215. let viewController = TLPhotosPickerViewController()
  216. var configure = TLPhotosPickerConfigure()
  217. viewController.configure = configure
  218. public struct TLPhotosPickerConfigure {
  219. public var defaultCameraRollTitle = "Camera Roll"
  220. public var tapHereToChange = "Tap here to change"
  221. public var cancelTitle = "Cancel"
  222. public var doneTitle = "Done"
  223. public var emptyMessage = "No albums"
  224. public var emptyImage: UIImage? = nil
  225. public var usedCameraButton = true
  226. public var usedPrefetch = false
  227. public var allowedLivePhotos = true
  228. public var allowedVideo = true
  229. public var allowedAlbumCloudShared = false
  230. public var allowedVideoRecording = true //for camera : allow this option when you want to recording video.
  231. public var recordingVideoQuality: UIImagePickerControllerQualityType = .typeMedium //for camera : recording video quality
  232. public var maxVideoDuration:TimeInterval? = nil //for camera : max video recording duration
  233. public var autoPlay = true
  234. public var muteAudio = true
  235. public var mediaType: PHAssetMediaType? = nil
  236. public var numberOfColumn = 3
  237. public var singleSelectedMode = false
  238. public var maxSelectedAssets: Int? = nil //default: inf
  239. public var fetchOption: PHFetchOptions? = nil //default: creationDate
  240. public var singleSelectedMode = false
  241. public var selectedColor = UIColor(red: 88/255, green: 144/255, blue: 255/255, alpha: 1.0)
  242. public var cameraBgColor = UIColor(red: 221/255, green: 223/255, blue: 226/255, alpha: 1)
  243. public var cameraIcon = TLBundle.podBundleImage(named: "camera")
  244. public var videoIcon = TLBundle.podBundleImage(named: "video")
  245. public var placeholderIcon = TLBundle.podBundleImage(named: "insertPhotoMaterial")
  246. public var nibSet: (nibName: String, bundle:Bundle)? = nil // custom cell
  247. public var cameraCellNibSet: (nibName: String, bundle:Bundle)? = nil // custom camera cell
  248. public var fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)]? = nil
  249. public var groupByFetch: PHFetchedResultGroupedBy? = nil // cannot be used prefetch options
  250. public var supportedInterfaceOrientations: UIInterfaceOrientationMask = .portrait
  251. public init() {
  252. }
  253. }
  254. // PHFetchedResultGroupedBy
  255. //
  256. // CGrouped by date, cannot be used prefetch options
  257. // take about few seconds ( 5000 image iPhoneX: 1 ~ 1.5 sec )
  258. public enum PHFetchedResultGroupedBy {
  259. case year
  260. case month
  261. case week
  262. case day
  263. case hour
  264. case custom(dateFormat: String)
  265. }
  266. //customizable photos picker viewcontroller
  267. class CustomPhotoPickerViewController: TLPhotosPickerViewController {
  268. override func makeUI() {
  269. super.makeUI()
  270. self.customNavItem.leftBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .stop, target: nil, action: #selector(customAction))
  271. }
  272. func customAction() {
  273. self.dismiss(animated: true, completion: nil)
  274. }
  275. }
  276. //for log
  277. public protocol TLPhotosPickerLogDelegate: class {
  278. func selectedCameraCell(picker: TLPhotosPickerViewController)
  279. func deselectedPhoto(picker: TLPhotosPickerViewController, at: Int)
  280. func selectedPhoto(picker: TLPhotosPickerViewController, at: Int)
  281. func selectedAlbum(picker: TLPhotosPickerViewController, title: String, at: Int)
  282. }
  283. //for collection supplement view
  284. let viewController = TLPhotosPickerViewController()
  285. viewController.customDataSouces = CustomDataSources() // inherit TLPhotopickerDataSourcesProtocol
  286. public protocol TLPhotopickerDataSourcesProtocol {
  287. func headerReferenceSize() -> CGSize
  288. func footerReferenceSize() -> CGSize
  289. func registerSupplementView(collectionView: UICollectionView)
  290. func supplementIdentifier(kind: String) -> String
  291. func configure(supplement view: UICollectionReusableView, section: (title: String, assets: [TLPHAsset]))
  292. }
  293. ```
  294. ## Author
  295. Does your organization or project use TLPhotoPicker? Please let me know by email.
  296. wade.hawk, junhyi.park@gmail.com
  297. ## License
  298. TLPhotoPicker is available under the MIT license. See the LICENSE file for more info.