KSMediaPickerController.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. //
  2. // KSMediaPickerController.swift
  3. //
  4. //
  5. // Created by kinsun on 2019/3/1.
  6. //
  7. import UIKit
  8. open class KSNavigationController: UINavigationController {
  9. override open func viewDidLoad() {
  10. super.viewDidLoad()
  11. view.sendSubviewToBack(navigationBar)
  12. }
  13. }
  14. import Photos
  15. import JXSegmentedView
  16. @objc public protocol KSMediaPickerControllerDelegate: NSObjectProtocol {
  17. @objc optional func mediaPicker(_ mediaPicker: KSMediaPickerController, didFinishSelected outputArray: [KSMediaPickerOutputModel])
  18. }
  19. open class KSMediaPickerController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
  20. typealias DismissClosure = () -> Void
  21. var dismissClosure: DismissClosure?
  22. typealias CropClosure = (_ vItemMdl: KSMediaPickerItemModel) -> Void
  23. var cropClosure: CropClosure?
  24. typealias PubImgClosure = (_ selectedAssetArray: Array<UIImage>) -> Void
  25. var pubImgClosure: PubImgClosure?
  26. @objc public enum mediaType : Int {
  27. case all = 0
  28. case picture = 1
  29. case video = 2
  30. }
  31. @objc public let maxItemCount: UInt
  32. @objc public let maxVideoItemCount: UInt
  33. @objc public let maxPictureItemCount: UInt
  34. @objc public let mediaType: KSMediaPickerController.mediaType
  35. @objc open weak var delegate: KSMediaPickerControllerDelegate?
  36. /// 限制单一媒体类型混合显示构造函数,此函数为指定初始化器
  37. ///
  38. /// - Parameters:
  39. /// - maxVideoItemCount: 选择视频的最大数量
  40. /// - maxPictureItemCount: 选择图片的最大数量
  41. @objc public init(maxVideoItemCount: UInt, maxPictureItemCount: UInt) {
  42. if maxVideoItemCount == 0 {
  43. self.mediaType = .picture
  44. } else if maxPictureItemCount == 0 {
  45. self.mediaType = .video
  46. } else {
  47. self.mediaType = .all
  48. }
  49. maxItemCount = maxVideoItemCount+maxPictureItemCount
  50. self.maxPictureItemCount = maxPictureItemCount
  51. self.maxVideoItemCount = maxVideoItemCount
  52. super.init(nibName: nil, bundle: nil)
  53. }
  54. /// 自由视频与图片构造函数,就是图片加视频的总数为maxItemCount,不对每种做限制只对总数做限制,此函数为指定初始化器
  55. ///
  56. /// - Parameter maxItemCount: 选择媒体的最大总数
  57. @objc public init(maxItemCount: UInt = 9) {
  58. self.mediaType = .all
  59. self.maxItemCount = maxItemCount
  60. maxPictureItemCount = 0
  61. maxVideoItemCount = 0
  62. super.init(nibName: nil, bundle: nil)
  63. }
  64. @nonobjc required public init?(coder aDecoder: NSCoder) {
  65. fatalError("init(coder:) has not been implemented")
  66. }
  67. private static let k_image_item_class = KSMediaPickerViewImageCell.self
  68. private static let k_video_item_class = KSMediaPickerViewVideoCell.self
  69. private static let k_image_item_iden = NSStringFromClass(k_image_item_class)
  70. private static let k_video_item_iden = NSStringFromClass(k_video_item_class)
  71. override open func loadView() {
  72. let view = KSMediaPickerView()
  73. let nav = view.albumNavigationView
  74. nav.closeButton.addTarget(self, action: #selector(_didClick(closeButton:)), for: .touchUpInside)
  75. nav.nextButton.addTarget(self, action: #selector(_didClick(nextButton:)), for: .touchUpInside)
  76. let classObj = KSMediaPickerController.self
  77. let collectionView = view.collectionView
  78. collectionView.register(classObj.k_image_item_class, forCellWithReuseIdentifier: classObj.k_image_item_iden)
  79. collectionView.register(classObj.k_video_item_class, forCellWithReuseIdentifier: classObj.k_video_item_iden)
  80. collectionView.delegate = self
  81. collectionView.dataSource = self
  82. self.view = view
  83. }
  84. override open func viewDidDisappear(_ animated: Bool) {
  85. super.viewDidDisappear(animated)
  86. (view as! KSMediaPickerView).previewView.videoPause()
  87. }
  88. override open func viewDidAppear(_ animated: Bool) {
  89. super.viewDidAppear(animated)
  90. (view as! KSMediaPickerView).previewView.videoPlay()
  91. }
  92. override open func viewDidLoad() {
  93. super.viewDidLoad()
  94. let cancelHandler: ((UIAlertAction) -> Void) = {[weak self] (action) in
  95. self?._didClick(closeButton: nil)
  96. }
  97. KSMediaPickerController.authorityCheckUp(controller: self, type: .picture, completionHandler: {[weak self] (type) in
  98. if let obj = self {
  99. KSMediaPickerController.authorityCheckUp(controller: obj, type: .video, completionHandler: { (type) in
  100. KSMediaPickerController.authorityAudioCheckUp(controller: obj, completionHandler: {
  101. DispatchQueue.global().async {
  102. let assetCollections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil)
  103. let regularAssetCollections = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: nil)
  104. let array = [assetCollections, regularAssetCollections]
  105. self?._set(assetData: array)
  106. }
  107. }, cancelHandler: cancelHandler)
  108. }, cancelHandler: cancelHandler)
  109. }
  110. }, cancelHandler: cancelHandler)
  111. }
  112. private var _albumList: [KSMediaPickerAlbumModel]?
  113. private func _set(assetData: [PHFetchResult<PHAssetCollection>]) {
  114. _albumList = KSMediaPickerAlbumModel.albumModel(from: assetData, mediaType: self.mediaType)
  115. DispatchQueue.main.async(execute: _loadAssetDataFinish)
  116. }
  117. private var _selectedAlbum: KSMediaPickerAlbumModel? {
  118. didSet {
  119. let view = self.view as! KSMediaPickerView
  120. _updateHighlightedItemStatus()
  121. if let itemModel = _selectedAlbum?.assetList.first {
  122. let isStandard = _selectedAssetArray.count == 0 || itemModel == (_selectedAssetArray.firstObject as! KSMediaPickerItemModel)
  123. view.previewView.set(itemModel: itemModel, isStandard: isStandard)
  124. itemModel.isHighlight = true
  125. }
  126. _highlightedItemIndexPath = IndexPath(item: 0, section: 0)
  127. view.collectionView.reloadData()
  128. }
  129. }
  130. private func _loadAssetDataFinish() {
  131. _selectedAlbum = _albumList?.first
  132. }
  133. @objc private func _didClick(closeButton: UIButton?) {
  134. if let dismissClosure = self.dismissClosure {
  135. (view as! KSMediaPickerView).previewView.videoPause()
  136. dismissClosure()
  137. }
  138. }
  139. @objc private func _didClick(nextButton: UIButton) {
  140. (view as! KSMediaPickerView).previewView.saveCurrentState()
  141. if _currentSingleType == .video {
  142. if let cropClosure = self.cropClosure {
  143. (view as! KSMediaPickerView).previewView.videoPause()
  144. let vItemModel: KSMediaPickerItemModel = _selectedAssetArray.firstObject as! KSMediaPickerItemModel
  145. cropClosure(vItemModel)
  146. }
  147. } else {
  148. var imageArray: Array<UIImage> = []
  149. for selItem in _selectedAssetArray {
  150. let itemModel = selItem as! KSMediaPickerItemModel
  151. let image: UIImage = itemModel.thumb ?? UIImage()
  152. imageArray.append(image)
  153. }
  154. if let pubImgClosure = self.pubImgClosure {
  155. (view as! KSMediaPickerView).previewView.videoPause()
  156. pubImgClosure(imageArray)
  157. }
  158. }
  159. }
  160. private func _didClickCell(collectionViewCell: KSMediaPickerViewImageCell) -> UInt {
  161. guard let itemModel = collectionViewCell.itemModel, !itemModel.isLoseFocus else {
  162. return 0
  163. }
  164. let indexPath = (view as! KSMediaPickerView).collectionView.indexPath(for: collectionViewCell)
  165. if indexPath != nil {
  166. _updateHighlightItem(at: indexPath!)
  167. }
  168. if itemModel.index > 0 {
  169. return _remove(itemModel: itemModel)
  170. } else {
  171. return _add(itemModel: itemModel)
  172. }
  173. }
  174. private func _update(asset: PHAsset) {
  175. _updateHighlightedItemStatus()
  176. let itemModel = KSMediaPickerItemModel(asset)
  177. itemModel.isHighlight = true
  178. _selectedAlbum?.assetList.insert(itemModel, at: 0)
  179. let indexPath = IndexPath(item: 0, section: 0)
  180. _highlightedItemIndexPath = indexPath
  181. let isStandard = _selectedAssetArray.count == 0 || itemModel == (_selectedAssetArray.firstObject as! KSMediaPickerItemModel)
  182. let view = self.view as! KSMediaPickerView
  183. view.previewView.set(itemModel: itemModel, isStandard: isStandard)
  184. let collectionView = view.collectionView
  185. collectionView.performBatchUpdates({
  186. collectionView.insertItems(at: [indexPath])
  187. }) {[weak self] (finished) in
  188. let index = self?._add(itemModel: itemModel) ?? 0
  189. if index > 0 {
  190. itemModel.index = index
  191. } else {
  192. itemModel.isLoseFocus = true
  193. }
  194. collectionView.reloadItems(at: [indexPath])
  195. view.collectionViewScrollToTop()
  196. }
  197. }
  198. private lazy var _selectedAssetArray = NSMutableArray(capacity: Int(maxItemCount))
  199. private func _add(itemModel: KSMediaPickerItemModel) -> UInt {
  200. let selectedAssetArray = _selectedAssetArray
  201. let count = UInt(selectedAssetArray.count)
  202. var k_maxItemCount = maxItemCount
  203. let assetMediaType = (selectedAssetArray.firstObject as? KSMediaPickerItemModel)?.asset.mediaType ?? itemModel.asset.mediaType
  204. var isSingleType = false
  205. if self.mediaType == .all {
  206. if assetMediaType == .video && maxVideoItemCount != 0 {
  207. k_maxItemCount = maxVideoItemCount
  208. _currentSingleType = .video
  209. } else if assetMediaType == .image && maxPictureItemCount != 0 {
  210. k_maxItemCount = maxPictureItemCount
  211. _currentSingleType = .picture
  212. }
  213. if _currentSingleType != nil && count == 0 {
  214. isSingleType = true
  215. }
  216. }
  217. guard count < k_maxItemCount else {
  218. return 0
  219. }
  220. selectedAssetArray.add(itemModel)
  221. let lastCount = count+1
  222. let isLastItem = lastCount == k_maxItemCount
  223. let view = self.view as! KSMediaPickerView
  224. if isSingleType || isLastItem {
  225. let assetList = _selectedAlbum!.assetList
  226. var indexPaths = Array<IndexPath>()
  227. for (i, k_itemModel) in assetList.enumerated() {
  228. let isOk = (isSingleType && k_itemModel.asset.mediaType != assetMediaType) || (isLastItem && !selectedAssetArray.contains(k_itemModel))
  229. if isOk && !k_itemModel.isLoseFocus {
  230. k_itemModel.isLoseFocus = true
  231. let indexPath = IndexPath(item: i, section: 0)
  232. indexPaths.append(indexPath)
  233. }
  234. }
  235. let collectionView = view.collectionView
  236. collectionView.performBatchUpdates({
  237. collectionView.reloadItems(at: indexPaths)
  238. }, completion: nil)
  239. }
  240. view.albumNavigationView.nextButton.isEnabled = lastCount > 0
  241. return lastCount
  242. }
  243. private var _currentSingleType: KSMediaPickerController.mediaType?
  244. private func _remove(itemModel: KSMediaPickerItemModel) -> UInt {
  245. let selectedAssetArray = _selectedAssetArray
  246. let index = selectedAssetArray.index(of: itemModel)
  247. let count = selectedAssetArray.count
  248. guard index >= 0, index < count else {
  249. return 0
  250. }
  251. itemModel.index = 0
  252. selectedAssetArray.removeObject(at: index)
  253. let view = self.view as! KSMediaPickerView
  254. let assetMediaType = itemModel.asset.mediaType
  255. var k_maxItemCount = maxItemCount
  256. var needUpdateSingleType = false
  257. let isSingleType = _currentSingleType != nil
  258. if isSingleType {
  259. if _currentSingleType! == .video {
  260. k_maxItemCount = maxVideoItemCount
  261. } else if _currentSingleType! == .picture {
  262. k_maxItemCount = maxPictureItemCount
  263. }
  264. needUpdateSingleType = count == 1
  265. }
  266. let needUpdateIndexNumber = index != count-1
  267. let needUpdateFocus = count == k_maxItemCount
  268. if needUpdateIndexNumber && needUpdateFocus {
  269. let assetList = _selectedAlbum!.assetList
  270. var j = Int(1)
  271. for k_itemModel in assetList {
  272. if selectedAssetArray.contains(k_itemModel) {
  273. k_itemModel.index = UInt(j)
  274. j += 1
  275. } else {
  276. let ok = isSingleType ? (assetMediaType == k_itemModel.asset.mediaType) : true
  277. if (needUpdateSingleType || (ok && k_itemModel.isLoseFocus)) {
  278. k_itemModel.isLoseFocus = false
  279. }
  280. }
  281. }
  282. let collectionView = view.collectionView
  283. collectionView.reloadData()
  284. } else if needUpdateIndexNumber {
  285. let assetList = _selectedAlbum!.assetList as NSArray
  286. let assetListCount = assetList.count
  287. var indexPaths = Array<IndexPath>()
  288. for (i, k_itemModel) in selectedAssetArray.enumerated() {
  289. let l_itemModel = k_itemModel as! KSMediaPickerItemModel
  290. l_itemModel.index = UInt(i+1)
  291. let k_index = assetList.index(of: l_itemModel)
  292. if k_index >= 0 && k_index < assetListCount {
  293. let indexPath = IndexPath(item: k_index, section: 0)
  294. indexPaths.append(indexPath)
  295. }
  296. }
  297. let collectionView = view.collectionView
  298. collectionView.performBatchUpdates({
  299. collectionView.reloadItems(at: indexPaths)
  300. }, completion: nil)
  301. } else {
  302. let collectionView = view.collectionView
  303. let assetList = _selectedAlbum!.assetList
  304. if needUpdateSingleType {
  305. for k_itemModel in assetList {
  306. k_itemModel.isLoseFocus = false
  307. }
  308. collectionView.reloadData()
  309. } else if needUpdateFocus {
  310. var indexPaths = Array<IndexPath>()
  311. for (i, k_itemModel) in assetList.enumerated() {
  312. let ok = isSingleType ? (assetMediaType == k_itemModel.asset.mediaType) : true
  313. if (ok && k_itemModel.isLoseFocus) {
  314. k_itemModel.isLoseFocus = false
  315. let indexPath = IndexPath(item: i, section: 0)
  316. indexPaths.append(indexPath)
  317. }
  318. }
  319. collectionView.performBatchUpdates({
  320. collectionView.reloadItems(at: indexPaths)
  321. }, completion: nil)
  322. }
  323. }
  324. let albumNavigationView = view.albumNavigationView
  325. albumNavigationView.nextButton.isEnabled = selectedAssetArray.count > 0
  326. return 0
  327. }
  328. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  329. return _selectedAlbum?.assetList.count ?? 0
  330. }
  331. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  332. let itemModel = _selectedAlbum?.assetList[indexPath.item]
  333. let mediaType = itemModel?.asset.mediaType
  334. let isPictureCell = mediaType == .image
  335. let iden = isPictureCell ? KSMediaPickerController.k_image_item_iden : KSMediaPickerController.k_video_item_iden
  336. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: iden, for: indexPath) as! KSMediaPickerViewImageCell
  337. if cell.didSelectedItem == nil {
  338. cell.didSelectedItem = {[weak self] (collectionViewCell) -> UInt in
  339. return self?._didClickCell(collectionViewCell: collectionViewCell) ?? 0
  340. }
  341. cell.isMultipleSelected = maxItemCount > 1
  342. }
  343. cell.itemModel = itemModel
  344. return cell
  345. }
  346. private var _highlightedItemIndexPath: IndexPath?
  347. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  348. collectionView.deselectItem(at: indexPath, animated: true)
  349. _updateHighlightItem(at: indexPath)
  350. }
  351. private func _updateHighlightItem(at indexPath: IndexPath) {
  352. let view = self.view as! KSMediaPickerView
  353. guard indexPath != _highlightedItemIndexPath,
  354. let cell = view.collectionView.cellForItem(at: indexPath) as? KSMediaPickerViewImageCell,
  355. let itemModel = cell.itemModel,
  356. !itemModel.isLoseFocus else {
  357. return
  358. }
  359. let isStandard = _selectedAssetArray.count == 0 || itemModel == (_selectedAssetArray.firstObject as! KSMediaPickerItemModel)
  360. let previewView = view.previewView
  361. previewView.set(itemModel: itemModel, isStandard: isStandard)
  362. ///滚动至选择item区域
  363. let collectionView = view.collectionView
  364. let top = collectionView.contentInset.top
  365. var frame = collectionView.frame
  366. frame.origin.y = top
  367. frame.size.height -= top
  368. let cellFrame = cell.frame
  369. let frameInSuper = collectionView.convert(cellFrame, to: view)
  370. if !frame.contains(frameInSuper) {
  371. var point = CGPoint(x: 0.0, y: cellFrame.origin.y-top)
  372. let contentSizeHeight = collectionView.contentSize.height
  373. if cellFrame.maxY >= contentSizeHeight {
  374. point.y = contentSizeHeight-collectionView.bounds.size.height
  375. }
  376. collectionView.setContentOffset(point, animated: true)
  377. }
  378. ///滚动至选择item区域end
  379. view.showPreview(true)
  380. _updateHighlightedItemStatus()
  381. itemModel.isHighlight = true
  382. cell.itemIsHighlight = true
  383. _highlightedItemIndexPath = indexPath
  384. }
  385. private func _updateHighlightedItemStatus() {
  386. guard let indexPath = _highlightedItemIndexPath else {
  387. return
  388. }
  389. let highlightedItemModel: KSMediaPickerItemModel
  390. let highlightedCell = (view as! KSMediaPickerView).collectionView.cellForItem(at: indexPath) as? KSMediaPickerViewImageCell
  391. if highlightedCell == nil {
  392. highlightedItemModel = _selectedAlbum!.assetList[indexPath.item]
  393. } else {
  394. highlightedItemModel = highlightedCell!.itemModel
  395. highlightedCell?.itemIsHighlight = false
  396. }
  397. highlightedItemModel.isHighlight = false
  398. }
  399. }
  400. extension KSMediaPickerController {
  401. open class func authorityCheckUp(controller: UIViewController, type: KSMediaPickerController.mediaType, completionHandler: @escaping ((KSMediaPickerController.mediaType) -> Void), cancelHandler: ((UIAlertAction) -> Void)?) {
  402. switch type {
  403. case .picture:
  404. let authorization = {(status: PHAuthorizationStatus) in
  405. switch status {
  406. case .authorized:
  407. completionHandler(type)
  408. break
  409. case .denied:
  410. authorityAlert(controller: controller, name: "照片", cancelHandler: cancelHandler)
  411. break
  412. default:
  413. break
  414. }
  415. }
  416. let status = PHPhotoLibrary.authorizationStatus()
  417. if status == .notDetermined {
  418. PHPhotoLibrary.requestAuthorization(authorization)
  419. } else {
  420. authorization(status)
  421. }
  422. break
  423. case .video:
  424. let authorization = {(granted: Bool) in
  425. if granted {
  426. completionHandler(type)
  427. } else {
  428. authorityAlert(controller: controller, name: "照相机", cancelHandler: cancelHandler)
  429. }
  430. }
  431. let mediaType = AVMediaType.video
  432. let status = AVCaptureDevice.authorizationStatus(for: mediaType)
  433. if status == .notDetermined {
  434. AVCaptureDevice.requestAccess(for: mediaType, completionHandler: authorization)
  435. } else {
  436. authorization(status == .authorized)
  437. }
  438. break
  439. default :
  440. break
  441. }
  442. }
  443. open class func authorityAlert(controller: UIViewController, name: String, cancelHandler: ((UIAlertAction) -> Void)?) {
  444. let bundle = Bundle.main
  445. let appName = NSLocalizedString("CFBundleDisplayName", tableName: "InfoPlist", bundle: bundle, comment: "")
  446. let title = "没有打开“\(appName)”访问权限"
  447. let message = "请进入“设置”-“\(appName)”打开\(appName)开关"
  448. let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
  449. let go = UIAlertAction(title: "去设置", style: .default) { (action) in
  450. let application = UIApplication.shared
  451. let url = URL(string: UIApplication.openSettingsURLString)!
  452. if application.canOpenURL(url) {
  453. if #available(iOS 10.0, *) {
  454. application.open(url, options: [.universalLinksOnly: false], completionHandler: nil)
  455. } else {
  456. application.openURL(url)
  457. }
  458. if cancelHandler != nil {
  459. cancelHandler!(action)
  460. }
  461. }
  462. }
  463. alert.addAction(go)
  464. let cancel = UIAlertAction(title: "取消", style: .cancel, handler: cancelHandler)
  465. alert.addAction(cancel)
  466. controller.present(alert, animated: true, completion: nil)
  467. }
  468. open class func authorityAudioCheckUp(controller: UIViewController, completionHandler: @escaping (() -> Void), cancelHandler: ((UIAlertAction) -> Void)?) {
  469. let authorization = {(granted: Bool) in
  470. if granted {
  471. completionHandler()
  472. } else {
  473. authorityAlert(controller: controller, name: "麦克风", cancelHandler: cancelHandler)
  474. }
  475. }
  476. let mediaType = AVMediaType.audio
  477. let status = AVCaptureDevice.authorizationStatus(for: mediaType)
  478. if status == .notDetermined {
  479. AVCaptureDevice.requestAccess(for: mediaType, completionHandler: authorization)
  480. } else {
  481. authorization(status == .authorized)
  482. }
  483. }
  484. }
  485. // MARK: - JXSegmentedDelegate
  486. extension KSMediaPickerController : JXSegmentedListContainerViewListDelegate {
  487. public func listView() -> UIView {
  488. return view
  489. }
  490. public func listDidAppear() {
  491. (view as! KSMediaPickerView).previewView.videoPlay()
  492. }
  493. public func listDidDisappear() {
  494. (view as! KSMediaPickerView).previewView.videoPause()
  495. }
  496. }