PPAssetsCollectionController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import UIKit
  2. import AVFoundation
  3. protocol PPAssetsViewControllerDelegate: class {
  4. func assetsViewController(_ controller: PPAssetsCollectionController, didChange itemsCount: Int, _ onlyPhotos: Bool, _ onlyVideos: Bool)
  5. func assetsViewControllerDidRequestCameraController(_ controller: PPAssetsCollectionController)
  6. func assetsViewControllerDidRequestAuthorization(_ controller: PPAssetsCollectionController)
  7. }
  8. /**
  9. Top part of Assets Action Controller that represents camera roll assets preview.
  10. */
  11. class PPAssetsCollectionController: UICollectionViewController {
  12. public weak var delegate: PPAssetsViewControllerDelegate?
  13. private var flowLayout: PPCollectionViewLayout!
  14. fileprivate var heightConstraint: NSLayoutConstraint!
  15. private let assetManager = PPAssetManager()
  16. fileprivate var phAssets: PHFetchResult<PHAsset> = PHFetchResult<PHAsset>()
  17. private var selectedItemRows = Set<Int>()
  18. fileprivate var config: PPAssetsActionConfig!
  19. fileprivate var captureSession: AVCaptureSession?
  20. fileprivate var captureLayer: AVCaptureVideoPreviewLayer?
  21. fileprivate let cameraIsAvailable = UIImagePickerController.isSourceTypeAvailable(.camera)
  22. public init(aConfig: PPAssetsActionConfig) {
  23. flowLayout = PPCollectionViewLayout()
  24. config = aConfig
  25. super.init(collectionViewLayout: flowLayout)
  26. flowLayout.itemsInfoProvider = self
  27. }
  28. required init?(coder aDecoder: NSCoder) {
  29. super.init(coder: aDecoder)
  30. }
  31. deinit {
  32. stopCaptureSession()
  33. }
  34. override func viewDidLoad() {
  35. super.viewDidLoad()
  36. collectionView?.backgroundColor = UIColor.clear
  37. collectionView?.translatesAutoresizingMaskIntoConstraints = false
  38. collectionView?.register(PPPhotoViewCell.self, forCellWithReuseIdentifier: PPPhotoViewCell.reuseIdentifier)
  39. collectionView?.register(PPVideoViewCell.self, forCellWithReuseIdentifier: PPVideoViewCell.reuseIdentifier)
  40. collectionView?.register(PPLiveCameraCell.self, forCellWithReuseIdentifier: PPLiveCameraCell.reuseIdentifier)
  41. collectionView?.showsVerticalScrollIndicator = false
  42. collectionView?.showsHorizontalScrollIndicator = false
  43. collectionView?.delegate = self
  44. heightConstraint = NSLayoutConstraint(item: collectionView!,
  45. attribute: .height,
  46. relatedBy: .equal,
  47. toItem: nil,
  48. attribute: .notAnAttribute,
  49. multiplier: 1.0,
  50. constant: config.assetsPreviewRegularHeight)
  51. collectionView?.addConstraint(heightConstraint)
  52. self.flowLayout.viewWidth = self.view!.frame.width
  53. let requestImages = {
  54. self.assetManager.getPHAssets(imagesOnly: !self.config.showVideos, fetchLimit: self.config.fetchLimit, { (result) in
  55. if (result?.count)! > 0 {
  56. self.phAssets = result!
  57. self.collectionView?.reloadData()
  58. }
  59. })
  60. }
  61. if assetManager.authorizationStatus() == .authorized {
  62. if (config.showGalleryPreview) {
  63. requestImages()
  64. }
  65. } else if config.askPhotoPermissions {
  66. assetManager.requestAuthorization { status in
  67. if status == .authorized {
  68. if (self.config.showGalleryPreview) {
  69. requestImages()
  70. }
  71. self.delegate?.assetsViewControllerDidRequestAuthorization(self)
  72. } else {
  73. self.heightConstraint.constant = 0
  74. }
  75. }
  76. } else {
  77. self.heightConstraint.constant = 0
  78. }
  79. if rowCountForLiveCameraCell() == 1 {
  80. self.setupCaptureSession()
  81. }
  82. }
  83. func selectedPHMedia() -> [MediaProvider] {
  84. return selectedItemRows.map { phAssets[$0] }
  85. }
  86. func updateCollectionView() {
  87. collectionView?.setNeedsLayout()
  88. let flowLayout = PPCollectionViewLayout()
  89. flowLayout.itemsInfoProvider = self
  90. flowLayout.viewWidth = view.frame.width
  91. UIView.animate(withDuration: 0.5,
  92. delay: 0.0,
  93. usingSpringWithDamping: 1.0,
  94. initialSpringVelocity: 1.0,
  95. options: .curveEaseIn,
  96. animations:
  97. {
  98. // FIXME: iOS10 layout workaround. Think of a better way.
  99. self.collectionView?.superview?.superview?.layoutIfNeeded()
  100. self.collectionView?.setCollectionViewLayout(flowLayout, animated: true)
  101. }) { result in
  102. self.collectionView?.reloadData()
  103. }
  104. }
  105. // MARK: UICollectionViewDataSource
  106. override func numberOfSections(in collectionView: UICollectionView) -> Int {
  107. return 1
  108. }
  109. override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  110. if collectionView.isHidden {
  111. return 0
  112. }
  113. return phAssets.count + rowCountForLiveCameraCell()
  114. }
  115. override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  116. if indexPath.row == 0 && rowCountForLiveCameraCell() == 1 {
  117. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PPLiveCameraCell.reuseIdentifier, for: indexPath) as! PPLiveCameraCell
  118. cell.backgroundColor = UIColor.black
  119. cell.accessibilityLabel = PPLiveCameraCell.reuseIdentifier
  120. if let layer = captureLayer {
  121. cell.set(layer: layer)
  122. }
  123. return cell
  124. }
  125. let mediaProvider = phAssets[modifiedRow(for: indexPath.row)]
  126. var cell: PPCheckedViewCell!
  127. if mediaProvider.mediaType == .video {
  128. let videoCell = collectionView.dequeueReusableCell(withReuseIdentifier: PPVideoViewCell.reuseIdentifier, for: indexPath) as! PPVideoViewCell
  129. videoCell.setVideo(mediaProvider)
  130. cell = videoCell
  131. } else {
  132. let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: PPPhotoViewCell.reuseIdentifier, for: indexPath) as! PPPhotoViewCell
  133. photoCell.set(mediaProvider)
  134. cell = photoCell
  135. }
  136. cell.checked.tintColor = config.tintColor
  137. if (heightConstraint.constant == config.assetsPreviewExpandedHeight) {
  138. cell.set(selected: selectedItemRows.contains(modifiedRow(for: indexPath.row)))
  139. }
  140. cell.accessibilityLabel = "asset-\(modifiedRow(for: indexPath.row))"
  141. return cell
  142. }
  143. override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  144. if indexPath.row == 0 && rowCountForLiveCameraCell() == 1 {
  145. delegate?.assetsViewControllerDidRequestCameraController(self)
  146. return
  147. }
  148. if selectedItemRows.contains(modifiedRow(for: indexPath.row)) {
  149. selectedItemRows.remove(modifiedRow(for: indexPath.row))
  150. } else {
  151. if (config.maxSelectableAssets != 0 && config.maxSelectableAssets == selectedItemRows.count) {
  152. return
  153. }
  154. selectedItemRows.insert(modifiedRow(for: indexPath.row))
  155. }
  156. var isVideoSelected = false
  157. var isPhotoSelected = false
  158. let media = selectedPHMedia()
  159. for (_, value) in media.enumerated() {
  160. if (value.phasset()?.phassetIsImage())! {
  161. isPhotoSelected = true
  162. } else {
  163. isVideoSelected = true
  164. }
  165. }
  166. let onlyPhotos = isPhotoSelected && !isVideoSelected
  167. let onlyVideos = isVideoSelected && !isPhotoSelected
  168. delegate?.assetsViewController(self, didChange: selectedItemRows.count, onlyPhotos, onlyVideos)
  169. if (heightConstraint.constant < config.assetsPreviewExpandedHeight) {
  170. heightConstraint.constant = config.assetsPreviewExpandedHeight
  171. updateCollectionView()
  172. } else {
  173. if selectedItemRows.count == 0 {
  174. heightConstraint.constant = config.assetsPreviewRegularHeight
  175. updateCollectionView()
  176. }
  177. collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
  178. if let cell = collectionView.cellForItem(at: indexPath) as? PPPhotoViewCell {
  179. cell.set(selected: selectedItemRows.contains(modifiedRow(for: indexPath.row)))
  180. }
  181. }
  182. }
  183. // override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  184. // for cell in collectionView!.visibleCells as [UICollectionViewCell] {
  185. // if let videoCell = cell as? PPVideoViewCell {
  186. // videoCell.stopVideo()
  187. // }
  188. // }
  189. // }
  190. //
  191. // override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
  192. // if velocity.x == 0 && velocity.y == 0 {
  193. // for cell in self.collectionView!.visibleCells as [UICollectionViewCell] {
  194. // if let videoCell = cell as? PPVideoViewCell {
  195. // videoCell.startVideo()
  196. // }
  197. // }
  198. // }
  199. // }
  200. //
  201. // override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  202. // for cell in self.collectionView!.visibleCells as [UICollectionViewCell] {
  203. // if let videoCell = cell as? PPVideoViewCell {
  204. // videoCell.startVideo()
  205. // }
  206. // }
  207. // }
  208. }
  209. // MARK: - Camera
  210. extension PPAssetsCollectionController {
  211. func setupCaptureSession() {
  212. let defaultDevice = AVCaptureDevice.default(for: AVMediaType.video)
  213. if let input = try? AVCaptureDeviceInput(device: defaultDevice!) {
  214. captureSession = AVCaptureSession()
  215. captureSession?.addInput(input)
  216. captureLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
  217. captureLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
  218. captureSession?.startRunning()
  219. self.collectionView?.reloadData()
  220. }
  221. }
  222. func stopCaptureSession() {
  223. if let session = captureSession {
  224. session.stopRunning()
  225. captureSession = nil
  226. }
  227. }
  228. }
  229. extension PPAssetsCollectionController: UICollectionViewDelegateFlowLayout {
  230. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  231. if indexPath.row == 0 && rowCountForLiveCameraCell() == 1 {
  232. return CGSize(width: heightConstraint.constant, height: heightConstraint.constant)
  233. }
  234. let asset = phAssets[modifiedRow(for: indexPath.row)]
  235. if asset.pixelHeight == 0 || asset.pixelWidth == 0 {
  236. return CGSize(width: heightConstraint.constant, height: heightConstraint.constant)
  237. } else {
  238. let factor = heightConstraint.constant / CGFloat(asset.pixelHeight)
  239. return CGSize(width: CGFloat(asset.pixelWidth) * factor, height: heightConstraint.constant)
  240. }
  241. }
  242. }
  243. extension PPAssetsCollectionController {
  244. func modifiedRow(for row: Int) -> Int {
  245. return row - (config.showLiveCameraCell ? rowCountForLiveCameraCell() : 0)
  246. }
  247. func rowCountForLiveCameraCell() -> Int {
  248. return cameraIsAvailable && config.showLiveCameraCell && config.showGalleryPreview ? 1 : 0
  249. }
  250. }