ZLPhotoPreviewController.swift 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. //
  2. // ZLPhotoPreviewController.swift
  3. // ZLPhotoBrowser
  4. //
  5. // Created by long on 2020/8/20.
  6. //
  7. // Copyright (c) 2020 Long Zhang <495181165@qq.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import UIKit
  27. import Photos
  28. class ZLPhotoPreviewController: UIViewController {
  29. static let colItemSpacing: CGFloat = 40
  30. static let selPhotoPreviewH: CGFloat = 100
  31. static let previewVCScrollNotification = Notification.Name("previewVCScrollNotification")
  32. let arrDataSources: [ZLPhotoModel]
  33. let showBottomViewAndSelectBtn: Bool
  34. var currentIndex: Int
  35. var indexBeforOrientationChanged: Int
  36. var collectionView: UICollectionView!
  37. var navView: UIView!
  38. var navBlurView: UIVisualEffectView?
  39. var backBtn: UIButton!
  40. var selectBtn: UIButton!
  41. var indexLabel: UILabel!
  42. var bottomView: UIView!
  43. var bottomBlurView: UIVisualEffectView?
  44. var editBtn: UIButton!
  45. var originalBtn: UIButton!
  46. var doneBtn: UIButton!
  47. var selPhotoPreview: ZLPhotoPreviewSelectedView?
  48. var isFirstAppear = true
  49. var hideNavView = false
  50. var popInteractiveTransition: ZLPhotoPreviewPopInteractiveTransition?
  51. /// 是否在点击确定时候,当未选择任何照片时候,自动选择当前index的照片
  52. var autoSelectCurrentIfNotSelectAnyone = true
  53. /// 界面消失时,通知上个界面刷新(针对预览视图)
  54. var backBlock: ( () -> Void )?
  55. var orientation: UIInterfaceOrientation = .unknown
  56. override var prefersStatusBarHidden: Bool {
  57. return !ZLPhotoConfiguration.default().showStatusBarInPreviewInterface
  58. }
  59. override var preferredStatusBarStyle: UIStatusBarStyle {
  60. return ZLPhotoConfiguration.default().statusBarStyle
  61. }
  62. deinit {
  63. zl_debugPrint("ZLPhotoPreviewController deinit")
  64. }
  65. init(photos: [ZLPhotoModel], index: Int, showBottomViewAndSelectBtn: Bool = true) {
  66. self.arrDataSources = photos
  67. self.showBottomViewAndSelectBtn = showBottomViewAndSelectBtn
  68. self.currentIndex = index
  69. self.indexBeforOrientationChanged = index
  70. super.init(nibName: nil, bundle: nil)
  71. }
  72. required init?(coder: NSCoder) {
  73. fatalError("init(coder:) has not been implemented")
  74. }
  75. override func viewDidLoad() {
  76. super.viewDidLoad()
  77. self.setupUI()
  78. self.addPopInteractiveTransition()
  79. self.resetSubViewStatus()
  80. }
  81. override func viewWillAppear(_ animated: Bool) {
  82. super.viewWillAppear(animated)
  83. self.navigationController?.navigationBar.isHidden = true
  84. }
  85. override func viewDidAppear(_ animated: Bool) {
  86. super.viewDidAppear(animated)
  87. self.navigationController?.delegate = self
  88. guard self.isFirstAppear else { return }
  89. self.isFirstAppear = false
  90. self.reloadCurrentCell()
  91. }
  92. override func viewDidLayoutSubviews() {
  93. super.viewDidLayoutSubviews()
  94. var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
  95. if #available(iOS 11.0, *) {
  96. insets = self.view.safeAreaInsets
  97. }
  98. insets.top = max(20, insets.top)
  99. self.collectionView.frame = CGRect(x: -ZLPhotoPreviewController.colItemSpacing / 2, y: 0, width: self.view.frame.width + ZLPhotoPreviewController.colItemSpacing, height: self.view.frame.height)
  100. let navH = insets.top + 44
  101. self.navView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: navH)
  102. self.navBlurView?.frame = self.navView.bounds
  103. self.backBtn.frame = CGRect(x: insets.left, y: insets.top, width: 60, height: 44)
  104. self.selectBtn.frame = CGRect(x: self.view.frame.width - 40 - insets.right, y: insets.top + (44 - 25) / 2, width: 25, height: 25)
  105. self.indexLabel.frame = self.selectBtn.frame
  106. self.refreshBottomViewFrame()
  107. let ori = UIApplication.shared.statusBarOrientation
  108. if ori != self.orientation {
  109. self.orientation = ori
  110. self.collectionView.setContentOffset(CGPoint(x: (self.view.frame.width + ZLPhotoPreviewController.colItemSpacing) * CGFloat(self.indexBeforOrientationChanged), y: 0), animated: false)
  111. self.collectionView.performBatchUpdates({
  112. self.collectionView.setContentOffset(CGPoint(x: (self.view.frame.width + ZLPhotoPreviewController.colItemSpacing) * CGFloat(self.indexBeforOrientationChanged), y: 0), animated: false)
  113. })
  114. }
  115. }
  116. func reloadCurrentCell() {
  117. guard let cell = self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) else {
  118. return
  119. }
  120. if let cell = cell as? ZLGifPreviewCell {
  121. cell.loadGifWhenCellDisplaying()
  122. } else if let cell = cell as? ZLLivePhotoPreviewCell {
  123. cell.loadLivePhotoData()
  124. }
  125. }
  126. func refreshBottomViewFrame() {
  127. var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
  128. if #available(iOS 11.0, *) {
  129. insets = self.view.safeAreaInsets
  130. }
  131. var bottomViewH = ZLLayout.bottomToolViewH
  132. var showSelPhotoPreview = false
  133. if ZLPhotoConfiguration.default().showSelectedPhotoPreview {
  134. let nav = self.navigationController as! ZLImageNavController
  135. if !nav.arrSelectedModels.isEmpty {
  136. showSelPhotoPreview = true
  137. bottomViewH += ZLPhotoPreviewController.selPhotoPreviewH
  138. self.selPhotoPreview?.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: ZLPhotoPreviewController.selPhotoPreviewH)
  139. }
  140. }
  141. let btnH = ZLLayout.bottomToolBtnH
  142. self.bottomView.frame = CGRect(x: 0, y: self.view.frame.height-insets.bottom-bottomViewH, width: self.view.frame.width, height: bottomViewH+insets.bottom)
  143. self.bottomBlurView?.frame = self.bottomView.bounds
  144. let btnY: CGFloat = showSelPhotoPreview ? ZLPhotoPreviewController.selPhotoPreviewH + ZLLayout.bottomToolBtnY : ZLLayout.bottomToolBtnY
  145. let editTitle = localLanguageTextValue(.edit)
  146. let editBtnW = editTitle.boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width
  147. self.editBtn.frame = CGRect(x: 15, y: btnY, width: editBtnW, height: btnH)
  148. let originalTitle = localLanguageTextValue(.originalPhoto)
  149. let w = originalTitle.boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width + 30
  150. self.originalBtn.frame = CGRect(x: (self.bottomView.bounds.width-w)/2-5, y: btnY, width: w, height: btnH)
  151. let selCount = (self.navigationController as? ZLImageNavController)?.arrSelectedModels.count ?? 0
  152. var doneTitle = localLanguageTextValue(.done)
  153. if selCount > 0 {
  154. doneTitle += "(" + String(selCount) + ")"
  155. }
  156. let doneBtnW = doneTitle.boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width + 20
  157. self.doneBtn.frame = CGRect(x: self.bottomView.bounds.width-doneBtnW-15, y: btnY, width: doneBtnW, height: btnH)
  158. }
  159. func setupUI() {
  160. self.view.backgroundColor = .black
  161. self.automaticallyAdjustsScrollViewInsets = false
  162. let config = ZLPhotoConfiguration.default()
  163. // nav view
  164. self.navView = UIView()
  165. self.navView.backgroundColor = .navBarColor
  166. self.view.addSubview(self.navView)
  167. if let effect = config.navViewBlurEffect {
  168. self.navBlurView = UIVisualEffectView(effect: effect)
  169. self.navView.addSubview(self.navBlurView!)
  170. }
  171. self.backBtn = UIButton(type: .custom)
  172. self.backBtn.setImage(getImage("zl_navBack"), for: .normal)
  173. self.backBtn.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
  174. self.backBtn.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside)
  175. self.navView.addSubview(self.backBtn)
  176. self.selectBtn = UIButton(type: .custom)
  177. self.selectBtn.setImage(getImage("zl_btn_circle"), for: .normal)
  178. self.selectBtn.setImage(getImage("zl_btn_selected"), for: .selected)
  179. self.selectBtn.zl_enlargeValidTouchArea(inset: 10)
  180. self.selectBtn.addTarget(self, action: #selector(selectBtnClick), for: .touchUpInside)
  181. self.navView.addSubview(self.selectBtn)
  182. self.indexLabel = UILabel()
  183. self.indexLabel.backgroundColor = .indexLabelBgColor
  184. self.indexLabel.font = getFont(14)
  185. self.indexLabel.textColor = .white
  186. self.indexLabel.textAlignment = .center
  187. self.indexLabel.layer.cornerRadius = 25.0 / 2
  188. self.indexLabel.layer.masksToBounds = true
  189. self.indexLabel.isHidden = true
  190. self.selectBtn.addSubview(self.indexLabel)
  191. // collection view
  192. let layout = UICollectionViewFlowLayout()
  193. layout.scrollDirection = .horizontal
  194. self.collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
  195. self.collectionView.backgroundColor = .clear
  196. self.collectionView.dataSource = self
  197. self.collectionView.delegate = self
  198. self.collectionView.isPagingEnabled = true
  199. self.collectionView.showsHorizontalScrollIndicator = false
  200. self.view.addSubview(self.collectionView)
  201. ZLPhotoPreviewCell.zl_register(self.collectionView)
  202. ZLGifPreviewCell.zl_register(self.collectionView)
  203. ZLLivePhotoPreviewCell.zl_register(self.collectionView)
  204. ZLVideoPreviewCell.zl_register(self.collectionView)
  205. // bottom view
  206. self.bottomView = UIView()
  207. self.bottomView.backgroundColor = .bottomToolViewBgColor
  208. self.view.addSubview(self.bottomView)
  209. if let effect = config.bottomToolViewBlurEffect {
  210. self.bottomBlurView = UIVisualEffectView(effect: effect)
  211. self.bottomView.addSubview(self.bottomBlurView!)
  212. }
  213. if config.showSelectedPhotoPreview {
  214. let nav = self.navigationController as! ZLImageNavController
  215. self.selPhotoPreview = ZLPhotoPreviewSelectedView(selModels: nav.arrSelectedModels, currentShowModel: self.arrDataSources[self.currentIndex])
  216. self.selPhotoPreview?.selectBlock = { [weak self] (model) in
  217. self?.scrollToSelPreviewCell(model)
  218. }
  219. self.selPhotoPreview?.endSortBlock = { [weak self] (models) in
  220. self?.refreshCurrentCellIndex(models)
  221. }
  222. self.bottomView.addSubview(self.selPhotoPreview!)
  223. }
  224. func createBtn(_ title: String, _ action: Selector) -> UIButton {
  225. let btn = UIButton(type: .custom)
  226. btn.titleLabel?.font = ZLLayout.bottomToolTitleFont
  227. btn.setTitle(title, for: .normal)
  228. btn.setTitleColor(.bottomToolViewBtnNormalTitleColor, for: .normal)
  229. btn.setTitleColor(.bottomToolViewBtnDisableTitleColor, for: .disabled)
  230. btn.addTarget(self, action: action, for: .touchUpInside)
  231. return btn
  232. }
  233. self.editBtn = createBtn(localLanguageTextValue(.edit), #selector(editBtnClick))
  234. self.editBtn.isHidden = (!config.allowEditImage && !config.allowEditVideo)
  235. self.bottomView.addSubview(self.editBtn)
  236. self.originalBtn = createBtn(localLanguageTextValue(.originalPhoto), #selector(originalPhotoClick))
  237. self.originalBtn.setImage(getImage("zl_btn_original_circle"), for: .normal)
  238. self.originalBtn.setImage(getImage("zl_btn_original_selected"), for: .selected)
  239. self.originalBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
  240. self.originalBtn.isHidden = !(config.allowSelectOriginal && config.allowSelectImage)
  241. self.originalBtn.isSelected = (self.navigationController as! ZLImageNavController).isSelectedOriginal
  242. self.bottomView.addSubview(self.originalBtn)
  243. self.doneBtn = createBtn(localLanguageTextValue(.done), #selector(doneBtnClick))
  244. self.doneBtn.backgroundColor = .bottomToolViewBtnNormalBgColor
  245. self.doneBtn.layer.masksToBounds = true
  246. self.doneBtn.layer.cornerRadius = ZLLayout.bottomToolBtnCornerRadius
  247. self.bottomView.addSubview(self.doneBtn)
  248. self.view.bringSubviewToFront(self.navView)
  249. }
  250. func addPopInteractiveTransition() {
  251. guard (self.navigationController?.viewControllers.count ?? 0 ) > 1 else {
  252. // 仅有当前vc一个时候,说明不是从相册进入,不添加交互动画
  253. return
  254. }
  255. self.popInteractiveTransition = ZLPhotoPreviewPopInteractiveTransition(viewController: self)
  256. self.popInteractiveTransition?.shouldStartTransition = { [weak self] (point) -> Bool in
  257. guard let `self` = self else { return false }
  258. if !self.hideNavView && (self.navView.frame.contains(point) || self.bottomView.frame.contains(point)) {
  259. return false
  260. }
  261. return true
  262. }
  263. self.popInteractiveTransition?.startTransition = { [weak self] in
  264. guard let `self` = self else { return }
  265. self.navView.alpha = 0
  266. self.bottomView.alpha = 0
  267. guard let cell = self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) else {
  268. return
  269. }
  270. if cell is ZLVideoPreviewCell {
  271. (cell as! ZLVideoPreviewCell).pauseWhileTransition()
  272. } else if cell is ZLLivePhotoPreviewCell {
  273. (cell as! ZLLivePhotoPreviewCell).livePhotoView.stopPlayback()
  274. } else if cell is ZLGifPreviewCell {
  275. (cell as! ZLGifPreviewCell).pauseGif()
  276. }
  277. }
  278. self.popInteractiveTransition?.cancelTransition = { [weak self] in
  279. guard let `self` = self else { return }
  280. self.hideNavView = false
  281. self.navView.isHidden = false
  282. self.bottomView.isHidden = false
  283. UIView.animate(withDuration: 0.5) {
  284. self.navView.alpha = 1
  285. self.bottomView.alpha = 1
  286. }
  287. guard let cell = self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) else {
  288. return
  289. }
  290. if cell is ZLGifPreviewCell {
  291. (cell as! ZLGifPreviewCell).resumeGif()
  292. }
  293. }
  294. }
  295. func resetSubViewStatus() {
  296. let nav = self.navigationController as! ZLImageNavController
  297. let config = ZLPhotoConfiguration.default()
  298. let currentModel = self.arrDataSources[self.currentIndex]
  299. if (!config.allowMixSelect && currentModel.type == .video) || (!config.showSelectBtnWhenSingleSelect && config.maxSelectCount == 1) {
  300. self.selectBtn.isHidden = true
  301. } else {
  302. self.selectBtn.isHidden = false
  303. }
  304. self.selectBtn.isSelected = self.arrDataSources[self.currentIndex].isSelected
  305. self.resetIndexLabelStatus()
  306. guard self.showBottomViewAndSelectBtn else {
  307. self.selectBtn.isHidden = true
  308. self.bottomView.isHidden = true
  309. return
  310. }
  311. let selCount = nav.arrSelectedModels.count
  312. var doneTitle = localLanguageTextValue(.done)
  313. if selCount > 0 {
  314. doneTitle += "(" + String(selCount) + ")"
  315. }
  316. self.doneBtn.setTitle(doneTitle, for: .normal)
  317. self.selPhotoPreview?.isHidden = selCount == 0
  318. self.refreshBottomViewFrame()
  319. var hideEditBtn = true
  320. if selCount < config.maxSelectCount || nav.arrSelectedModels.contains(where: { $0 == currentModel }) {
  321. if config.allowEditImage && (currentModel.type == .image || (currentModel.type == .gif && !config.allowSelectGif) || (currentModel.type == .livePhoto && !config.allowSelectLivePhoto)) {
  322. hideEditBtn = false
  323. }
  324. if config.allowEditVideo && currentModel.type == .video && (selCount == 0 || (selCount == 1 && nav.arrSelectedModels.first == currentModel)) {
  325. hideEditBtn = false
  326. }
  327. }
  328. self.editBtn.isHidden = hideEditBtn
  329. if ZLPhotoConfiguration.default().allowSelectOriginal && ZLPhotoConfiguration.default().allowSelectImage {
  330. self.originalBtn.isHidden = !((currentModel.type == .image) || (currentModel.type == .livePhoto && !config.allowSelectLivePhoto) || (currentModel.type == .gif && !config.allowSelectGif))
  331. }
  332. }
  333. func resetIndexLabelStatus() {
  334. guard ZLPhotoConfiguration.default().showSelectedIndex else {
  335. self.indexLabel.isHidden = true
  336. return
  337. }
  338. let nav = self.navigationController as! ZLImageNavController
  339. if let index = nav.arrSelectedModels.firstIndex(where: { $0 == self.arrDataSources[self.currentIndex] }) {
  340. self.indexLabel.isHidden = false
  341. self.indexLabel.text = String(index + 1)
  342. } else {
  343. self.indexLabel.isHidden = true
  344. }
  345. }
  346. // MARK: btn actions
  347. @objc func backBtnClick() {
  348. self.backBlock?()
  349. let vc = self.navigationController?.popViewController(animated: true)
  350. if vc == nil {
  351. self.navigationController?.dismiss(animated: true, completion: nil)
  352. }
  353. }
  354. @objc func selectBtnClick() {
  355. let nav = self.navigationController as! ZLImageNavController
  356. let currentModel = self.arrDataSources[self.currentIndex]
  357. self.selectBtn.layer.removeAllAnimations()
  358. if currentModel.isSelected {
  359. currentModel.isSelected = false
  360. nav.arrSelectedModels.removeAll { $0 == currentModel }
  361. self.selPhotoPreview?.removeSelModel(model: currentModel)
  362. } else {
  363. self.selectBtn.layer.add(getSpringAnimation(), forKey: nil)
  364. if !canAddModel(currentModel, currentSelectCount: nav.arrSelectedModels.count, sender: self) {
  365. return
  366. }
  367. currentModel.isSelected = true
  368. nav.arrSelectedModels.append(currentModel)
  369. self.selPhotoPreview?.addSelModel(model: currentModel)
  370. }
  371. self.resetSubViewStatus()
  372. }
  373. @objc func editBtnClick() {
  374. let config = ZLPhotoConfiguration.default()
  375. let model = self.arrDataSources[self.currentIndex]
  376. let hud = ZLProgressHUD(style: config.hudStyle)
  377. if model.type == .image || (!config.allowSelectGif && model.type == .gif) || (!config.allowSelectLivePhoto && model.type == .livePhoto) {
  378. hud.show()
  379. ZLPhotoManager.fetchImage(for: model.asset, size: model.previewSize) { [weak self] (image, isDegraded) in
  380. if !isDegraded {
  381. if let image = image {
  382. self?.showEditImageVC(image: image)
  383. } else {
  384. showAlertView(localLanguageTextValue(.imageLoadFailed), self)
  385. }
  386. hud.hide()
  387. }
  388. }
  389. } else if model.type == .video || config.allowEditVideo {
  390. var requestAvAssetID: PHImageRequestID?
  391. hud.show(timeout: 20)
  392. hud.timeoutBlock = { [weak self] in
  393. showAlertView(localLanguageTextValue(.timeout), self)
  394. if let _ = requestAvAssetID {
  395. PHImageManager.default().cancelImageRequest(requestAvAssetID!)
  396. }
  397. }
  398. // fetch avasset
  399. requestAvAssetID = ZLPhotoManager.fetchAVAsset(forVideo: model.asset) { [weak self] (avAsset, _) in
  400. hud.hide()
  401. if let av = avAsset {
  402. self?.showEditVideoVC(model: model, avAsset: av)
  403. } else {
  404. showAlertView(localLanguageTextValue(.timeout), self)
  405. }
  406. }
  407. }
  408. }
  409. @objc func originalPhotoClick() {
  410. self.originalBtn.isSelected = !self.originalBtn.isSelected
  411. let nav = (self.navigationController as? ZLImageNavController)
  412. nav?.isSelectedOriginal = self.originalBtn.isSelected
  413. if nav?.arrSelectedModels.count == 0 {
  414. self.selectBtnClick()
  415. }
  416. }
  417. @objc func doneBtnClick() {
  418. let nav = self.navigationController as! ZLImageNavController
  419. let currentModel = self.arrDataSources[self.currentIndex]
  420. if self.autoSelectCurrentIfNotSelectAnyone {
  421. if nav.arrSelectedModels.isEmpty, canAddModel(currentModel, currentSelectCount: nav.arrSelectedModels.count, sender: self) {
  422. nav.arrSelectedModels.append(currentModel)
  423. }
  424. if !nav.arrSelectedModels.isEmpty {
  425. nav.selectImageBlock?()
  426. }
  427. } else {
  428. nav.selectImageBlock?()
  429. }
  430. }
  431. func scrollToSelPreviewCell(_ model: ZLPhotoModel) {
  432. guard let index = self.arrDataSources.lastIndex(of: model) else {
  433. return
  434. }
  435. self.collectionView.performBatchUpdates({
  436. self.collectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: false)
  437. }) { (_) in
  438. self.indexBeforOrientationChanged = self.currentIndex
  439. self.reloadCurrentCell()
  440. }
  441. }
  442. func refreshCurrentCellIndex(_ models: [ZLPhotoModel]) {
  443. let nav = self.navigationController as? ZLImageNavController
  444. nav?.arrSelectedModels.removeAll()
  445. nav?.arrSelectedModels.append(contentsOf: models)
  446. guard ZLPhotoConfiguration.default().showSelectedIndex else {
  447. return
  448. }
  449. self.resetIndexLabelStatus()
  450. }
  451. func tapPreviewCell() {
  452. self.hideNavView = !self.hideNavView
  453. let currentCell = self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0))
  454. if let cell = currentCell as? ZLVideoPreviewCell {
  455. if cell.isPlaying {
  456. self.hideNavView = true
  457. }
  458. }
  459. self.navView.isHidden = self.hideNavView
  460. self.bottomView.isHidden = self.showBottomViewAndSelectBtn ? self.hideNavView : true
  461. }
  462. func showEditImageVC(image: UIImage) {
  463. let model = self.arrDataSources[self.currentIndex]
  464. let nav = self.navigationController as! ZLImageNavController
  465. ZLEditImageViewController.showEditImageVC(parentVC: self, image: image, editModel: model.editImageModel) { [weak self, weak nav] (ei, editImageModel) in
  466. guard let `self` = self else { return }
  467. model.editImage = ei
  468. model.editImageModel = editImageModel
  469. if nav?.arrSelectedModels.contains(where: { $0 == model }) == false {
  470. model.isSelected = true
  471. nav?.arrSelectedModels.append(model)
  472. self.resetSubViewStatus()
  473. self.selPhotoPreview?.addSelModel(model: model)
  474. } else {
  475. self.selPhotoPreview?.refreshCell(for: model)
  476. }
  477. self.collectionView.reloadItems(at: [IndexPath(row: self.currentIndex, section: 0)])
  478. }
  479. }
  480. func showEditVideoVC(model: ZLPhotoModel, avAsset: AVAsset) {
  481. let nav = self.navigationController as! ZLImageNavController
  482. let vc = ZLEditVideoViewController(avAsset: avAsset)
  483. vc.modalPresentationStyle = .fullScreen
  484. vc.editFinishBlock = { [weak self, weak nav] (url) in
  485. if let u = url {
  486. ZLPhotoManager.saveVideoToAlbum(url: u) { [weak self, weak nav] (suc, asset) in
  487. if suc, asset != nil {
  488. let m = ZLPhotoModel(asset: asset!)
  489. nav?.arrSelectedModels.removeAll()
  490. nav?.arrSelectedModels.append(m)
  491. nav?.selectImageBlock?()
  492. } else {
  493. showAlertView(localLanguageTextValue(.saveVideoError), self)
  494. }
  495. }
  496. } else {
  497. nav?.arrSelectedModels.removeAll()
  498. nav?.arrSelectedModels.append(model)
  499. nav?.selectImageBlock?()
  500. }
  501. }
  502. self.present(vc, animated: false, completion: nil)
  503. }
  504. }
  505. extension ZLPhotoPreviewController: UINavigationControllerDelegate {
  506. func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  507. if operation == .push {
  508. return nil
  509. }
  510. return self.popInteractiveTransition?.interactive == true ? ZLPhotoPreviewAnimatedTransition() : nil
  511. }
  512. func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
  513. return self.popInteractiveTransition?.interactive == true ? self.popInteractiveTransition : nil
  514. }
  515. }
  516. // scroll view delegate
  517. extension ZLPhotoPreviewController {
  518. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  519. guard scrollView == self.collectionView else {
  520. return
  521. }
  522. NotificationCenter.default.post(name: ZLPhotoPreviewController.previewVCScrollNotification, object: nil)
  523. let offset = scrollView.contentOffset
  524. var page = Int(round(offset.x / (self.view.bounds.width + ZLPhotoPreviewController.colItemSpacing)))
  525. page = max(0, min(page, self.arrDataSources.count-1))
  526. if page == self.currentIndex {
  527. return
  528. }
  529. self.currentIndex = page
  530. self.resetSubViewStatus()
  531. self.selPhotoPreview?.currentShowModelChanged(model: self.arrDataSources[self.currentIndex])
  532. }
  533. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  534. self.indexBeforOrientationChanged = self.currentIndex
  535. let cell = self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0))
  536. if let cell = cell as? ZLGifPreviewCell {
  537. cell.loadGifWhenCellDisplaying()
  538. } else if let cell = cell as? ZLLivePhotoPreviewCell {
  539. cell.loadLivePhotoData()
  540. }
  541. }
  542. }
  543. extension ZLPhotoPreviewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
  544. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
  545. return ZLPhotoPreviewController.colItemSpacing
  546. }
  547. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  548. return ZLPhotoPreviewController.colItemSpacing
  549. }
  550. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
  551. return UIEdgeInsets(top: 0, left: ZLPhotoPreviewController.colItemSpacing / 2, bottom: 0, right: ZLPhotoPreviewController.colItemSpacing / 2)
  552. }
  553. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  554. return CGSize(width: self.view.bounds.width, height: self.view.bounds.height)
  555. }
  556. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  557. return self.arrDataSources.count
  558. }
  559. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  560. let config = ZLPhotoConfiguration.default()
  561. let model = self.arrDataSources[indexPath.row]
  562. let baseCell: ZLPreviewBaseCell
  563. if config.allowSelectGif, model.type == .gif {
  564. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLGifPreviewCell.zl_identifier(), for: indexPath) as! ZLGifPreviewCell
  565. cell.singleTapBlock = { [weak self] in
  566. self?.tapPreviewCell()
  567. }
  568. cell.model = model
  569. baseCell = cell
  570. } else if config.allowSelectLivePhoto, model.type == .livePhoto {
  571. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLLivePhotoPreviewCell.zl_identifier(), for: indexPath) as! ZLLivePhotoPreviewCell
  572. cell.model = model
  573. baseCell = cell
  574. } else if config.allowSelectVideo, model.type == .video {
  575. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLVideoPreviewCell.zl_identifier(), for: indexPath) as! ZLVideoPreviewCell
  576. cell.model = model
  577. baseCell = cell
  578. } else {
  579. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLPhotoPreviewCell.zl_identifier(), for: indexPath) as! ZLPhotoPreviewCell
  580. cell.singleTapBlock = { [weak self] in
  581. self?.tapPreviewCell()
  582. }
  583. cell.model = model
  584. baseCell = cell
  585. }
  586. baseCell.singleTapBlock = { [weak self] in
  587. self?.tapPreviewCell()
  588. }
  589. return baseCell
  590. }
  591. func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  592. if let c = cell as? ZLPreviewBaseCell {
  593. c.resetSubViewStatusWhenCellEndDisplay()
  594. }
  595. }
  596. }
  597. /// 下方显示的已选择照片列表
  598. class ZLPhotoPreviewSelectedView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDragDelegate, UICollectionViewDropDelegate {
  599. var bottomBlurView: UIVisualEffectView?
  600. var collectionView: UICollectionView!
  601. var arrSelectedModels: [ZLPhotoModel]
  602. var currentShowModel: ZLPhotoModel
  603. var selectBlock: ( (ZLPhotoModel) -> Void )?
  604. var endSortBlock: ( ([ZLPhotoModel]) -> Void )?
  605. var isDraging = false
  606. init(selModels: [ZLPhotoModel], currentShowModel: ZLPhotoModel) {
  607. self.arrSelectedModels = selModels
  608. self.currentShowModel = currentShowModel
  609. super.init(frame: .zero)
  610. self.setupUI()
  611. }
  612. func setupUI() {
  613. let layout = UICollectionViewFlowLayout()
  614. layout.itemSize = CGSize(width: 60, height: 60)
  615. layout.minimumLineSpacing = 10
  616. layout.minimumInteritemSpacing = 10
  617. layout.scrollDirection = .horizontal
  618. layout.sectionInset = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
  619. self.collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
  620. self.collectionView.backgroundColor = .clear
  621. self.collectionView.dataSource = self
  622. self.collectionView.delegate = self
  623. self.collectionView.showsHorizontalScrollIndicator = false
  624. self.collectionView.alwaysBounceHorizontal = true
  625. self.addSubview(self.collectionView)
  626. ZLPhotoPreviewSelectedViewCell.zl_register(self.collectionView)
  627. if #available(iOS 11.0, *) {
  628. self.collectionView.dragDelegate = self
  629. self.collectionView.dropDelegate = self
  630. self.collectionView.dragInteractionEnabled = true
  631. self.collectionView.isSpringLoaded = true
  632. } else {
  633. let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction))
  634. self.collectionView.addGestureRecognizer(longPressGesture)
  635. }
  636. }
  637. required init?(coder: NSCoder) {
  638. fatalError("init(coder:) has not been implemented")
  639. }
  640. override func layoutSubviews() {
  641. super.layoutSubviews()
  642. self.bottomBlurView?.frame = self.bounds
  643. self.collectionView.frame = CGRect(x: 0, y: 10, width: self.bounds.width, height: 80)
  644. if let index = self.arrSelectedModels.firstIndex(where: { $0 == self.currentShowModel }) {
  645. self.collectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true)
  646. }
  647. }
  648. func currentShowModelChanged(model: ZLPhotoModel) {
  649. guard self.currentShowModel != model else {
  650. return
  651. }
  652. self.currentShowModel = model
  653. if let index = self.arrSelectedModels.firstIndex(where: { $0 == self.currentShowModel }) {
  654. self.collectionView.performBatchUpdates({
  655. self.collectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true)
  656. }) { (_) in
  657. self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)
  658. }
  659. } else {
  660. self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)
  661. }
  662. }
  663. func addSelModel(model: ZLPhotoModel) {
  664. self.arrSelectedModels.append(model)
  665. let ip = IndexPath(row: self.arrSelectedModels.count-1, section: 0)
  666. self.collectionView.insertItems(at: [ip])
  667. self.collectionView.scrollToItem(at: ip, at: .centeredHorizontally, animated: true)
  668. }
  669. func removeSelModel(model: ZLPhotoModel) {
  670. guard let index = self.arrSelectedModels.firstIndex(where: { $0 == model }) else {
  671. return
  672. }
  673. self.arrSelectedModels.remove(at: index)
  674. self.collectionView.deleteItems(at: [IndexPath(row: index, section: 0)])
  675. }
  676. func refreshCell(for model: ZLPhotoModel) {
  677. guard let index = self.arrSelectedModels.firstIndex(where: { $0 == model }) else {
  678. return
  679. }
  680. self.collectionView.reloadItems(at: [IndexPath(row: index, section: 0)])
  681. }
  682. // MARK: iOS10 拖动
  683. @objc func longPressAction(_ gesture: UILongPressGestureRecognizer) {
  684. if gesture.state == .began {
  685. guard let indexPath = self.collectionView.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
  686. return
  687. }
  688. self.isDraging = true
  689. self.collectionView.beginInteractiveMovementForItem(at: indexPath)
  690. } else if gesture.state == .changed {
  691. self.collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: self.collectionView))
  692. } else if gesture.state == .ended {
  693. self.isDraging = false
  694. self.collectionView.endInteractiveMovement()
  695. self.endSortBlock?(self.arrSelectedModels)
  696. } else {
  697. self.isDraging = false
  698. self.collectionView.cancelInteractiveMovement()
  699. }
  700. }
  701. func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
  702. return true
  703. }
  704. func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
  705. let moveModel = self.arrSelectedModels[sourceIndexPath.row]
  706. self.arrSelectedModels.remove(at: sourceIndexPath.row)
  707. self.arrSelectedModels.insert(moveModel, at: destinationIndexPath.row)
  708. }
  709. // MARK: iOS11 拖动
  710. @available(iOS 11.0, *)
  711. func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
  712. self.isDraging = true
  713. let itemProvider = NSItemProvider()
  714. let item = UIDragItem(itemProvider: itemProvider)
  715. return [item]
  716. }
  717. @available(iOS 11.0, *)
  718. func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
  719. if collectionView.hasActiveDrag {
  720. return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
  721. }
  722. return UICollectionViewDropProposal(operation: .forbidden)
  723. }
  724. @available(iOS 11.0, *)
  725. func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
  726. self.isDraging = false
  727. guard let destinationIndexPath = coordinator.destinationIndexPath else {
  728. return
  729. }
  730. guard let item = coordinator.items.first else {
  731. return
  732. }
  733. guard let sourceIndexPath = item.sourceIndexPath else {
  734. return
  735. }
  736. if coordinator.proposal.operation == .move {
  737. collectionView.performBatchUpdates({
  738. let moveModel = self.arrSelectedModels[sourceIndexPath.row]
  739. self.arrSelectedModels.remove(at: sourceIndexPath.row)
  740. self.arrSelectedModels.insert(moveModel, at: destinationIndexPath.row)
  741. collectionView.deleteItems(at: [sourceIndexPath])
  742. collectionView.insertItems(at: [destinationIndexPath])
  743. }, completion: nil)
  744. coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
  745. self.endSortBlock?(self.arrSelectedModels)
  746. }
  747. }
  748. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  749. return self.arrSelectedModels.count
  750. }
  751. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  752. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLPhotoPreviewSelectedViewCell.zl_identifier(), for: indexPath) as! ZLPhotoPreviewSelectedViewCell
  753. let m = self.arrSelectedModels[indexPath.row]
  754. cell.model = m
  755. if m == self.currentShowModel {
  756. cell.layer.borderWidth = 4
  757. } else {
  758. cell.layer.borderWidth = 0
  759. }
  760. return cell
  761. }
  762. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  763. guard !self.isDraging else {
  764. return
  765. }
  766. let m = self.arrSelectedModels[indexPath.row]
  767. self.currentShowModel = m
  768. self.collectionView.performBatchUpdates({
  769. self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
  770. }) { (_) in
  771. self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)
  772. }
  773. self.selectBlock?(m)
  774. }
  775. }
  776. class ZLPhotoPreviewSelectedViewCell: UICollectionViewCell {
  777. var imageView: UIImageView!
  778. var imageRequestID: PHImageRequestID = PHInvalidImageRequestID
  779. var imageIdentifier: String = ""
  780. var tagImageView: UIImageView!
  781. var tagLabel: UILabel!
  782. var model: ZLPhotoModel! {
  783. didSet {
  784. self.configureCell()
  785. }
  786. }
  787. override init(frame: CGRect) {
  788. super.init(frame: frame)
  789. self.layer.borderColor = UIColor.bottomToolViewBtnNormalBgColor.cgColor
  790. self.imageView = UIImageView()
  791. self.imageView.contentMode = .scaleAspectFill
  792. self.imageView.clipsToBounds = true
  793. self.contentView.addSubview(self.imageView)
  794. self.tagImageView = UIImageView()
  795. self.tagImageView.contentMode = .scaleAspectFit
  796. self.tagImageView.clipsToBounds = true
  797. self.contentView.addSubview(self.tagImageView)
  798. self.tagLabel = UILabel()
  799. self.tagLabel.font = getFont(13)
  800. self.tagLabel.textColor = .white
  801. self.contentView.addSubview(self.tagLabel)
  802. }
  803. required init?(coder: NSCoder) {
  804. fatalError("init(coder:) has not been implemented")
  805. }
  806. override func layoutSubviews() {
  807. super.layoutSubviews()
  808. self.imageView.frame = self.bounds
  809. self.tagImageView.frame = CGRect(x: 5, y: self.bounds.height-25, width: 20, height: 20)
  810. self.tagLabel.frame = CGRect(x: 5, y: self.bounds.height - 25, width: self.bounds.width-10, height: 20)
  811. }
  812. func configureCell() {
  813. let size = CGSize(width: self.bounds.width * 1.5, height: self.bounds.height * 1.5)
  814. if self.imageRequestID > PHInvalidImageRequestID {
  815. PHImageManager.default().cancelImageRequest(self.imageRequestID)
  816. }
  817. if self.model.type == .video {
  818. self.tagImageView.isHidden = false
  819. self.tagImageView.image = getImage("zl_video")
  820. self.tagLabel.isHidden = true
  821. } else if ZLPhotoConfiguration.default().allowSelectGif, self.model.type == .gif {
  822. self.tagImageView.isHidden = true
  823. self.tagLabel.isHidden = false
  824. self.tagLabel.text = "GIF"
  825. } else if ZLPhotoConfiguration.default().allowSelectLivePhoto, self.model.type == .livePhoto {
  826. self.tagImageView.isHidden = false
  827. self.tagImageView.image = getImage("zl_livePhoto")
  828. self.tagLabel.isHidden = true
  829. } else {
  830. if let _ = self.model.editImage {
  831. self.tagImageView.isHidden = false
  832. self.tagImageView.image = getImage("zl_editImage_tag")
  833. } else {
  834. self.tagImageView.isHidden = true
  835. self.tagLabel.isHidden = true
  836. }
  837. }
  838. self.imageIdentifier = self.model.ident
  839. self.imageView.image = nil
  840. if let ei = self.model.editImage {
  841. self.imageView.image = ei
  842. } else {
  843. self.imageRequestID = ZLPhotoManager.fetchImage(for: self.model.asset, size: size, completion: { [weak self] (image, isDegraded) in
  844. if self?.imageIdentifier == self?.model.ident {
  845. self?.imageView.image = image
  846. }
  847. })
  848. }
  849. }
  850. }