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.

379 lines
14 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
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 is not the only way to export video.
  203. //There is many way that export a video.
  204. //This method was one of them.
  205. public func exportVideoFile(options: PHVideoRequestOptions? = nil,
  206. outputURL: URL? = nil,
  207. outputFileType: AVFileType = .mov,
  208. progressBlock:((Double) -> Void)? = nil,
  209. completionBlock:@escaping ((URL,String) -> Void))
  210. // get original asset file name
  211. public var originalFileName: String?
  212. }
  213. ```
  214. > Note: convenience export method
  215. > fullResolutionImage, cloudImageDownload, tempCopyMediaFile, exportVideoFile
  216. > It's not enough if you wanted to use more complicated export asset options. ( progress, export type, etc..)
  217. ## Customize
  218. ```swift
  219. let viewController = TLPhotosPickerViewController()
  220. var configure = TLPhotosPickerConfigure()
  221. viewController.configure = configure
  222. public struct TLPhotosPickerConfigure {
  223. public var customLoclizedTitle: [String: String] = ["Camera Roll": "Camera Roll"] // Set [:] if you want use default localized title of album
  224. public var tapHereToChange = "Tap here to change"
  225. public var cancelTitle = "Cancel"
  226. public var doneTitle = "Done"
  227. public var emptyMessage = "No albums"
  228. public var emptyImage: UIImage? = nil
  229. public var usedCameraButton = true
  230. public var usedPrefetch = false
  231. public var allowedLivePhotos = true
  232. public var allowedVideo = true
  233. public var allowedAlbumCloudShared = false
  234. public var allowedVideoRecording = true //for camera : allow this option when you want to recording video.
  235. public var recordingVideoQuality: UIImagePickerControllerQualityType = .typeMedium //for camera : recording video quality
  236. public var maxVideoDuration:TimeInterval? = nil //for camera : max video recording duration
  237. public var autoPlay = true
  238. public var muteAudio = true
  239. public var mediaType: PHAssetMediaType? = nil
  240. public var numberOfColumn = 3
  241. public var singleSelectedMode = false
  242. public var maxSelectedAssets: Int? = nil //default: inf
  243. public var fetchOption: PHFetchOptions? = nil //default: creationDate
  244. public var fetchCollectionOption: [FetchCollectionType: PHFetchOptions] = [:]
  245. public var singleSelectedMode = false
  246. public var selectedColor = UIColor(red: 88/255, green: 144/255, blue: 255/255, alpha: 1.0)
  247. public var cameraBgColor = UIColor(red: 221/255, green: 223/255, blue: 226/255, alpha: 1)
  248. public var cameraIcon = TLBundle.podBundleImage(named: "camera")
  249. public var videoIcon = TLBundle.podBundleImage(named: "video")
  250. public var placeholderIcon = TLBundle.podBundleImage(named: "insertPhotoMaterial")
  251. public var nibSet: (nibName: String, bundle:Bundle)? = nil // custom cell
  252. public var cameraCellNibSet: (nibName: String, bundle:Bundle)? = nil // custom camera cell
  253. public var fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)]? = nil
  254. public var groupByFetch: PHFetchedResultGroupedBy? = nil // cannot be used prefetch options
  255. public var supportedInterfaceOrientations: UIInterfaceOrientationMask = .portrait
  256. public var popup: [PopupConfigure] = []
  257. public init() {
  258. }
  259. }
  260. //Related issue: https://github.com/tilltue/TLPhotoPicker/issues/201
  261. //e.g.
  262. //let option = PHFetchOptions()
  263. //configure.fetchCollectionOption[.assetCollections(.smartAlbum)] = option
  264. //configure.fetchCollectionOption[.assetCollections(.album)] = option
  265. //configure.fetchCollectionOption[.topLevelUserCollections] = option
  266. public enum FetchCollectionType {
  267. case assetCollections(PHAssetCollectionType)
  268. case topLevelUserCollections
  269. }
  270. public enum PopupConfigure {
  271. //Popup album view animation duration
  272. case animation(TimeInterval)
  273. }
  274. // PHFetchedResultGroupedBy
  275. //
  276. // CGrouped by date, cannot be used prefetch options
  277. // take about few seconds ( 5000 image iPhoneX: 1 ~ 1.5 sec )
  278. public enum PHFetchedResultGroupedBy {
  279. case year
  280. case month
  281. case week
  282. case day
  283. case hour
  284. case custom(dateFormat: String)
  285. }
  286. //customizable photos picker viewcontroller
  287. class CustomPhotoPickerViewController: TLPhotosPickerViewController {
  288. override func makeUI() {
  289. super.makeUI()
  290. self.customNavItem.leftBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .stop, target: nil, action: #selector(customAction))
  291. }
  292. func customAction() {
  293. self.dismiss(animated: true, completion: nil)
  294. }
  295. }
  296. //for log
  297. public protocol TLPhotosPickerLogDelegate: class {
  298. func selectedCameraCell(picker: TLPhotosPickerViewController)
  299. func deselectedPhoto(picker: TLPhotosPickerViewController, at: Int)
  300. func selectedPhoto(picker: TLPhotosPickerViewController, at: Int)
  301. func selectedAlbum(picker: TLPhotosPickerViewController, title: String, at: Int)
  302. }
  303. //for collection supplement view
  304. let viewController = TLPhotosPickerViewController()
  305. viewController.customDataSouces = CustomDataSources() // inherit TLPhotopickerDataSourcesProtocol
  306. public protocol TLPhotopickerDataSourcesProtocol {
  307. func headerReferenceSize() -> CGSize
  308. func footerReferenceSize() -> CGSize
  309. func registerSupplementView(collectionView: UICollectionView)
  310. func supplementIdentifier(kind: String) -> String
  311. func configure(supplement view: UICollectionReusableView, section: (title: String, assets: [TLPHAsset]))
  312. }
  313. ```
  314. ## Author
  315. Does your organization or project use TLPhotoPicker? Please let me know by email.
  316. wade.hawk, junhyi.park@gmail.com
  317. ## License
  318. TLPhotoPicker is available under the MIT license. See the LICENSE file for more info.