KSMediaPickerController.swift 22 KB

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