Files
OrderScheduling/Pods/ZLPhotoBrowser/Sources/General/ZLPhotoPreviewController.swift
DDIsFriend f0e8a1709d initial
2023-08-18 17:28:57 +08:00

1173 lines
46 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ZLPhotoPreviewController.swift
// ZLPhotoBrowser
//
// Created by long on 2020/8/20.
//
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
import Photos
class ZLPhotoPreviewController: UIViewController {
static let colItemSpacing: CGFloat = 40
static let selPhotoPreviewH: CGFloat = 100
static let previewVCScrollNotification = Notification.Name("previewVCScrollNotification")
let arrDataSources: [ZLPhotoModel]
var currentIndex: Int
lazy var collectionView: UICollectionView = {
let layout = ZLCollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.backgroundColor = .clear
view.dataSource = self
view.delegate = self
view.isPagingEnabled = true
view.showsHorizontalScrollIndicator = false
ZLPhotoPreviewCell.zl.register(view)
ZLGifPreviewCell.zl.register(view)
ZLLivePhotoPreviewCell.zl.register(view)
ZLVideoPreviewCell.zl.register(view)
return view
}()
private let showBottomViewAndSelectBtn: Bool
private var indexBeforOrientationChanged: Int
private lazy var navView: UIView = {
let view = UIView()
view.backgroundColor = .zl.navBarColorOfPreviewVC
return view
}()
private var navBlurView: UIVisualEffectView?
private lazy var backBtn: UIButton = {
let btn = UIButton(type: .custom)
var image = UIImage.zl.getImage("zl_navBack")
if isRTL() {
image = image?.imageFlippedForRightToLeftLayoutDirection()
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: -10)
} else {
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
}
btn.setImage(image, for: .normal)
btn.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside)
return btn
}()
private lazy var selectBtn: ZLEnlargeButton = {
let btn = ZLEnlargeButton(type: .custom)
btn.setImage(.zl.getImage("zl_btn_circle"), for: .normal)
btn.setImage(.zl.getImage("zl_btn_selected"), for: .selected)
btn.enlargeInset = 10
btn.addTarget(self, action: #selector(selectBtnClick), for: .touchUpInside)
return btn
}()
private lazy var indexLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .zl.indexLabelBgColor
label.font = .zl.font(ofSize: 14)
label.textColor = .white
label.textAlignment = .center
label.layer.cornerRadius = 25.0 / 2
label.layer.masksToBounds = true
label.isHidden = true
return label
}()
private lazy var bottomView: UIView = {
let view = UIView()
view.backgroundColor = .zl.bottomToolViewBgColorOfPreviewVC
return view
}()
private var bottomBlurView: UIVisualEffectView?
private lazy var editBtn: UIButton = {
let btn = createBtn(localLanguageTextValue(.edit), #selector(editBtnClick))
btn.titleLabel?.lineBreakMode = .byCharWrapping
btn.titleLabel?.numberOfLines = 0
btn.contentHorizontalAlignment = .left
return btn
}()
private lazy var originalBtn: UIButton = {
let btn = createBtn(localLanguageTextValue(.originalPhoto), #selector(originalPhotoClick))
btn.titleLabel?.lineBreakMode = .byCharWrapping
btn.titleLabel?.numberOfLines = 2
btn.contentHorizontalAlignment = .left
btn.setImage(.zl.getImage("zl_btn_original_circle"), for: .normal)
btn.setImage(.zl.getImage("zl_btn_original_selected"), for: .selected)
btn.setImage(.zl.getImage("zl_btn_original_selected"), for: [.selected, .highlighted])
btn.adjustsImageWhenHighlighted = false
if isRTL() {
btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 5)
} else {
btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
}
return btn
}()
private lazy var doneBtn: UIButton = {
let btn = createBtn(localLanguageTextValue(.done), #selector(doneBtnClick), true)
btn.backgroundColor = .zl.bottomToolViewBtnNormalBgColorOfPreviewVC
btn.layer.masksToBounds = true
btn.layer.cornerRadius = ZLLayout.bottomToolBtnCornerRadius
return btn
}()
private var selPhotoPreview: ZLPhotoPreviewSelectedView?
private var isFirstAppear = true
private var hideNavView = false
private var popInteractiveTransition: ZLPhotoPreviewPopInteractiveTransition?
private var orientation: UIInterfaceOrientation = .unknown
/// index
var autoSelectCurrentIfNotSelectAnyone = true
///
var backBlock: (() -> Void)?
override var prefersStatusBarHidden: Bool {
return !ZLPhotoUIConfiguration.default().showStatusBarInPreviewInterface
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return ZLPhotoUIConfiguration.default().statusBarStyle
}
deinit {
zl_debugPrint("ZLPhotoPreviewController deinit")
}
init(photos: [ZLPhotoModel], index: Int, showBottomViewAndSelectBtn: Bool = true) {
arrDataSources = photos
self.showBottomViewAndSelectBtn = showBottomViewAndSelectBtn
currentIndex = min(index, photos.count - 1)
indexBeforOrientationChanged = currentIndex
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
addPopInteractiveTransition()
resetSubViewStatus()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.isHidden = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.delegate = self
guard isFirstAppear else { return }
isFirstAppear = false
reloadCurrentCell()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
if #available(iOS 11.0, *) {
insets = self.view.safeAreaInsets
}
insets.top = max(20, insets.top)
collectionView.frame = CGRect(
x: -ZLPhotoPreviewController.colItemSpacing / 2,
y: 0,
width: view.zl.width + ZLPhotoPreviewController.colItemSpacing,
height: view.zl.height
)
let navH = insets.top + 44
navView.frame = CGRect(x: 0, y: 0, width: view.zl.width, height: navH)
navBlurView?.frame = navView.bounds
if isRTL() {
backBtn.frame = CGRect(x: view.zl.width - insets.right - 60, y: insets.top, width: 60, height: 44)
selectBtn.frame = CGRect(x: insets.left + 15, y: insets.top + (44 - 25) / 2, width: 25, height: 25)
} else {
backBtn.frame = CGRect(x: insets.left, y: insets.top, width: 60, height: 44)
selectBtn.frame = CGRect(x: view.zl.width - 40 - insets.right, y: insets.top + (44 - 25) / 2, width: 25, height: 25)
}
indexLabel.frame = selectBtn.bounds
refreshBottomViewFrame()
let ori = UIApplication.shared.statusBarOrientation
if ori != orientation {
orientation = ori
collectionView.performBatchUpdates(nil) { _ in
self.collectionView.setContentOffset(
CGPoint(
x: (self.view.zl.width + ZLPhotoPreviewController.colItemSpacing) * CGFloat(self.indexBeforOrientationChanged),
y: 0
),
animated: false
)
}
}
}
private func reloadCurrentCell() {
guard let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) else {
return
}
if let cell = cell as? ZLGifPreviewCell {
cell.loadGifWhenCellDisplaying()
} else if let cell = cell as? ZLLivePhotoPreviewCell {
cell.loadLivePhotoData()
}
}
private func refreshBottomViewFrame() {
var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
if #available(iOS 11.0, *) {
insets = view.safeAreaInsets
}
var bottomViewH = ZLLayout.bottomToolViewH
var showSelPhotoPreview = false
if ZLPhotoConfiguration.default().showSelectedPhotoPreview,
let nav = navigationController as? ZLImageNavController,
!nav.arrSelectedModels.isEmpty {
showSelPhotoPreview = true
bottomViewH += ZLPhotoPreviewController.selPhotoPreviewH
selPhotoPreview?.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: ZLPhotoPreviewController.selPhotoPreviewH)
}
let btnH = ZLLayout.bottomToolBtnH
bottomView.frame = CGRect(x: 0, y: view.frame.height - insets.bottom - bottomViewH, width: view.frame.width, height: bottomViewH + insets.bottom)
bottomBlurView?.frame = bottomView.bounds
let btnY: CGFloat = showSelPhotoPreview ? ZLPhotoPreviewController.selPhotoPreviewH + ZLLayout.bottomToolBtnY : ZLLayout.bottomToolBtnY
let btnMaxWidth = (bottomView.bounds.width - 30) / 3
let editTitle = localLanguageTextValue(.edit)
let editBtnW = editTitle.zl.boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width
editBtn.frame = CGRect(x: 15, y: btnY, width: min(btnMaxWidth, editBtnW), height: btnH)
let originalTitle = localLanguageTextValue(.originalPhoto)
let originBtnW = originalTitle.zl.boundingRect(
font: ZLLayout.bottomToolTitleFont,
limitSize: CGSize(
width: CGFloat.greatestFiniteMagnitude,
height: 30
)
).width + (originalBtn.currentImage?.size.width ?? 19) + 12
let originBtnMaxW = min(btnMaxWidth, originBtnW)
originalBtn.frame = CGRect(x: (bottomView.bounds.width - originBtnMaxW) / 2 - 5, y: btnY, width: originBtnMaxW, height: btnH)
let selCount = (navigationController as? ZLImageNavController)?.arrSelectedModels.count ?? 0
var doneTitle = localLanguageTextValue(.done)
if ZLPhotoConfiguration.default().showSelectCountOnDoneBtn, selCount > 0 {
doneTitle += "(" + String(selCount) + ")"
}
let doneBtnW = doneTitle.zl.boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width + 20
doneBtn.frame = CGRect(x: bottomView.bounds.width - doneBtnW - 15, y: btnY, width: doneBtnW, height: btnH)
}
private func setupUI() {
view.backgroundColor = .zl.previewVCBgColor
automaticallyAdjustsScrollViewInsets = false
let config = ZLPhotoConfiguration.default()
view.addSubview(navView)
if let effect = ZLPhotoUIConfiguration.default().navViewBlurEffectOfPreview {
navBlurView = UIVisualEffectView(effect: effect)
navView.addSubview(navBlurView!)
}
navView.addSubview(backBtn)
navView.addSubview(selectBtn)
selectBtn.addSubview(indexLabel)
view.addSubview(collectionView)
view.addSubview(bottomView)
if let effect = ZLPhotoUIConfiguration.default().bottomViewBlurEffectOfPreview {
bottomBlurView = UIVisualEffectView(effect: effect)
bottomView.addSubview(bottomBlurView!)
}
if config.showSelectedPhotoPreview {
let selModels = (navigationController as? ZLImageNavController)?.arrSelectedModels ?? []
selPhotoPreview = ZLPhotoPreviewSelectedView(selModels: selModels, currentShowModel: arrDataSources[currentIndex])
selPhotoPreview?.selectBlock = { [weak self] model in
self?.scrollToSelPreviewCell(model)
}
selPhotoPreview?.endSortBlock = { [weak self] models in
self?.refreshCurrentCellIndex(models)
}
bottomView.addSubview(selPhotoPreview!)
}
editBtn.isHidden = (!config.allowEditImage && !config.allowEditVideo)
bottomView.addSubview(editBtn)
originalBtn.isHidden = !(config.allowSelectOriginal && config.allowSelectImage)
originalBtn.isSelected = (navigationController as? ZLImageNavController)?.isSelectedOriginal ?? false
bottomView.addSubview(originalBtn)
bottomView.addSubview(doneBtn)
view.bringSubviewToFront(navView)
}
private func createBtn(_ title: String, _ action: Selector, _ isDone: Bool = false) -> UIButton {
let btn = UIButton(type: .custom)
btn.titleLabel?.font = ZLLayout.bottomToolTitleFont
btn.setTitle(title, for: .normal)
btn.setTitleColor(
isDone ? .zl.bottomToolViewDoneBtnNormalTitleColorOfPreviewVC : .zl.bottomToolViewBtnNormalTitleColorOfPreviewVC,
for: .normal
)
btn.setTitleColor(
isDone ? .zl.bottomToolViewDoneBtnDisableTitleColorOfPreviewVC : .zl.bottomToolViewBtnDisableTitleColorOfPreviewVC,
for: .disabled
)
btn.addTarget(self, action: action, for: .touchUpInside)
return btn
}
private func addPopInteractiveTransition() {
guard (navigationController?.viewControllers.count ?? 0) > 1 else {
// vc
return
}
popInteractiveTransition = ZLPhotoPreviewPopInteractiveTransition(viewController: self)
popInteractiveTransition?.shouldStartTransition = { [weak self] point -> Bool in
guard let `self` = self else { return false }
if !self.hideNavView, self.navView.frame.contains(point) || self.bottomView.frame.contains(point) {
return false
}
guard self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) != nil else {
return false
}
return true
}
popInteractiveTransition?.startTransition = { [weak self] in
guard let `self` = self else { return }
self.navView.alpha = 0
self.bottomView.alpha = 0
guard let cell = self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) else {
return
}
if cell is ZLVideoPreviewCell {
(cell as! ZLVideoPreviewCell).pauseWhileTransition()
} else if cell is ZLLivePhotoPreviewCell {
(cell as! ZLLivePhotoPreviewCell).livePhotoView.stopPlayback()
} else if cell is ZLGifPreviewCell {
(cell as! ZLGifPreviewCell).pauseGif()
}
}
popInteractiveTransition?.cancelTransition = { [weak self] in
guard let `self` = self else { return }
self.hideNavView = false
self.navView.isHidden = false
self.bottomView.isHidden = false
UIView.animate(withDuration: 0.5) {
self.navView.alpha = 1
self.bottomView.alpha = 1
}
guard let cell = self.collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) else {
return
}
if cell is ZLGifPreviewCell {
(cell as! ZLGifPreviewCell).resumeGif()
}
}
}
private func resetSubViewStatus() {
guard let nav = navigationController as? ZLImageNavController else {
zlLoggerInDebug("Navigation controller is null")
return
}
let config = ZLPhotoConfiguration.default()
let currentModel = arrDataSources[currentIndex]
if (!config.allowMixSelect && currentModel.type == .video) || (!config.showSelectBtnWhenSingleSelect && config.maxSelectCount == 1) {
selectBtn.isHidden = true
} else {
selectBtn.isHidden = false
}
selectBtn.isSelected = arrDataSources[currentIndex].isSelected
resetIndexLabelStatus()
guard showBottomViewAndSelectBtn else {
selectBtn.isHidden = true
bottomView.isHidden = true
return
}
let selCount = nav.arrSelectedModels.count
var doneTitle = localLanguageTextValue(.done)
if ZLPhotoConfiguration.default().showSelectCountOnDoneBtn, selCount > 0 {
doneTitle += "(" + String(selCount) + ")"
}
doneBtn.setTitle(doneTitle, for: .normal)
selPhotoPreview?.isHidden = selCount == 0
refreshBottomViewFrame()
var hideEditBtn = true
if selCount < config.maxSelectCount || nav.arrSelectedModels.contains(where: { $0 == currentModel }) {
if config.allowEditImage,
currentModel.type == .image || (currentModel.type == .gif && !config.allowSelectGif) || (currentModel.type == .livePhoto && !config.allowSelectLivePhoto) {
hideEditBtn = false
}
if config.allowEditVideo,
currentModel.type == .video,
selCount == 0 || (selCount == 1 && nav.arrSelectedModels.first == currentModel) {
hideEditBtn = false
}
}
editBtn.isHidden = hideEditBtn
if ZLPhotoConfiguration.default().allowSelectOriginal,
ZLPhotoConfiguration.default().allowSelectImage {
originalBtn.isHidden = !((currentModel.type == .image) || (currentModel.type == .livePhoto && !config.allowSelectLivePhoto) || (currentModel.type == .gif && !config.allowSelectGif))
}
}
private func resetIndexLabelStatus() {
guard ZLPhotoConfiguration.default().showSelectedIndex else {
indexLabel.isHidden = true
return
}
guard let nav = navigationController as? ZLImageNavController else {
zlLoggerInDebug("Navigation controller is null")
return
}
if let index = nav.arrSelectedModels.firstIndex(where: { $0 == self.arrDataSources[self.currentIndex] }) {
indexLabel.isHidden = false
indexLabel.text = String(index + 1)
} else {
indexLabel.isHidden = true
}
}
// MARK: btn actions
@objc private func backBtnClick() {
backBlock?()
let vc = navigationController?.popViewController(animated: true)
if vc == nil {
navigationController?.dismiss(animated: true, completion: nil)
}
}
@objc private func selectBtnClick() {
guard let nav = navigationController as? ZLImageNavController else {
zlLoggerInDebug("Navigation controller is null")
return
}
let config = ZLPhotoConfiguration.default()
let currentModel = arrDataSources[currentIndex]
selectBtn.layer.removeAllAnimations()
if currentModel.isSelected {
currentModel.isSelected = false
nav.arrSelectedModels.removeAll { $0 == currentModel }
selPhotoPreview?.removeSelModel(model: currentModel)
config.didDeselectAsset?(currentModel.asset)
} else {
if config.animateSelectBtnWhenSelect {
selectBtn.layer.add(ZLAnimationUtils.springAnimation(), forKey: nil)
}
if !canAddModel(currentModel, currentSelectCount: nav.arrSelectedModels.count, sender: self) {
return
}
currentModel.isSelected = true
nav.arrSelectedModels.append(currentModel)
selPhotoPreview?.addSelModel(model: currentModel)
config.didSelectAsset?(currentModel.asset)
}
resetSubViewStatus()
}
@objc private func editBtnClick() {
let config = ZLPhotoConfiguration.default()
let model = arrDataSources[currentIndex]
var requestAvAssetID: PHImageRequestID?
let hud = ZLProgressHUD(style: ZLPhotoUIConfiguration.default().hudStyle)
hud.timeoutBlock = { [weak self] in
showAlertView(localLanguageTextValue(.timeout), self)
if let requestAvAssetID = requestAvAssetID {
PHImageManager.default().cancelImageRequest(requestAvAssetID)
}
}
if model.type == .image || (!config.allowSelectGif && model.type == .gif) || (!config.allowSelectLivePhoto && model.type == .livePhoto) {
hud.show(timeout: ZLPhotoConfiguration.default().timeout)
requestAvAssetID = ZLPhotoManager.fetchImage(for: model.asset, size: model.previewSize) { [weak self] image, isDegraded in
if !isDegraded {
if let image = image {
self?.showEditImageVC(image: image)
} else {
showAlertView(localLanguageTextValue(.imageLoadFailed), self)
}
hud.hide()
}
}
} else if model.type == .video || config.allowEditVideo {
hud.show(timeout: ZLPhotoConfiguration.default().timeout)
// fetch avasset
requestAvAssetID = ZLPhotoManager.fetchAVAsset(forVideo: model.asset) { [weak self] avAsset, _ in
hud.hide()
if let avAsset = avAsset {
self?.showEditVideoVC(model: model, avAsset: avAsset)
} else {
showAlertView(localLanguageTextValue(.timeout), self)
}
}
}
}
@objc private func originalPhotoClick() {
originalBtn.isSelected.toggle()
let config = ZLPhotoConfiguration.default()
let nav = (navigationController as? ZLImageNavController)
nav?.isSelectedOriginal = originalBtn.isSelected
if nav?.arrSelectedModels.isEmpty == true {
selectBtnClick()
} else if config.maxSelectCount == 1,
!config.showSelectBtnWhenSingleSelect,
!originalBtn.isSelected,
nav?.arrSelectedModels.count == 1,
let currentModel = nav?.arrSelectedModels.first {
currentModel.isSelected = false
currentModel.editImage = nil
currentModel.editImageModel = nil
nav?.arrSelectedModels.removeAll { $0 == currentModel }
selPhotoPreview?.removeSelModel(model: currentModel)
resetSubViewStatus()
let index = config.sortAscending ? arrDataSources.lastIndex { $0 == currentModel } : arrDataSources.firstIndex { $0 == currentModel }
if let index = index {
collectionView.reloadItems(at: [IndexPath(row: index, section: 0)])
}
}
}
@objc private func doneBtnClick() {
guard let nav = navigationController as? ZLImageNavController else {
zlLoggerInDebug("Navigation controller is null")
return
}
func callBackBeforeDone() {
if let block = ZLPhotoConfiguration.default().operateBeforeDoneAction {
block(self) { [weak nav] in
nav?.selectImageBlock?()
}
} else {
nav.selectImageBlock?()
}
}
let currentModel = arrDataSources[currentIndex]
if autoSelectCurrentIfNotSelectAnyone {
if nav.arrSelectedModels.isEmpty, canAddModel(currentModel, currentSelectCount: nav.arrSelectedModels.count, sender: self) {
nav.arrSelectedModels.append(currentModel)
ZLPhotoConfiguration.default().didSelectAsset?(currentModel.asset)
}
if !nav.arrSelectedModels.isEmpty {
callBackBeforeDone()
}
} else {
callBackBeforeDone()
}
}
private func scrollToSelPreviewCell(_ model: ZLPhotoModel) {
guard let index = arrDataSources.lastIndex(of: model) else {
return
}
collectionView.performBatchUpdates({
self.collectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: false)
}) { _ in
self.indexBeforOrientationChanged = self.currentIndex
self.reloadCurrentCell()
}
}
private func refreshCurrentCellIndex(_ models: [ZLPhotoModel]) {
let nav = navigationController as? ZLImageNavController
nav?.arrSelectedModels.removeAll()
nav?.arrSelectedModels.append(contentsOf: models)
guard ZLPhotoConfiguration.default().showSelectedIndex else {
return
}
resetIndexLabelStatus()
}
private func tapPreviewCell() {
hideNavView.toggle()
let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0))
if let cell = cell as? ZLVideoPreviewCell, cell.isPlaying {
hideNavView = true
}
navView.isHidden = hideNavView
bottomView.isHidden = showBottomViewAndSelectBtn ? hideNavView : true
}
private func showEditImageVC(image: UIImage) {
let model = arrDataSources[currentIndex]
let nav = navigationController as? ZLImageNavController
ZLEditImageViewController.showEditImageVC(parentVC: self, image: image, editModel: model.editImageModel) { [weak self, weak nav] editImage, editImageModel in
guard let `self` = self else { return }
model.editImage = editImage
model.editImageModel = editImageModel
if nav?.arrSelectedModels.contains(where: { $0 == model }) == false {
model.isSelected = true
nav?.arrSelectedModels.append(model)
self.resetSubViewStatus()
self.selPhotoPreview?.addSelModel(model: model)
} else {
self.selPhotoPreview?.refreshCell(for: model)
}
self.collectionView.reloadItems(at: [IndexPath(row: self.currentIndex, section: 0)])
}
}
private func showEditVideoVC(model: ZLPhotoModel, avAsset: AVAsset) {
let nav = navigationController as? ZLImageNavController
let vc = ZLEditVideoViewController(avAsset: avAsset)
vc.modalPresentationStyle = .fullScreen
vc.editFinishBlock = { [weak self, weak nav] url in
if let url = url {
ZLPhotoManager.saveVideoToAlbum(url: url) { [weak self, weak nav] suc, asset in
if suc, asset != nil {
let m = ZLPhotoModel(asset: asset!)
nav?.arrSelectedModels.removeAll()
nav?.arrSelectedModels.append(m)
nav?.selectImageBlock?()
} else {
showAlertView(localLanguageTextValue(.saveVideoError), self)
}
}
} else {
nav?.arrSelectedModels.removeAll()
nav?.arrSelectedModels.append(model)
nav?.selectImageBlock?()
}
}
present(vc, animated: false, completion: nil)
}
}
extension ZLPhotoPreviewController: UINavigationControllerDelegate {
func navigationController(_: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from _: UIViewController, to _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push {
return nil
}
return popInteractiveTransition?.interactive == true ? ZLPhotoPreviewAnimatedTransition() : nil
}
func navigationController(_: UINavigationController, interactionControllerFor _: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return popInteractiveTransition?.interactive == true ? popInteractiveTransition : nil
}
}
// MARK: scroll view delegate
extension ZLPhotoPreviewController {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == collectionView else {
return
}
NotificationCenter.default.post(name: ZLPhotoPreviewController.previewVCScrollNotification, object: nil)
let offset = scrollView.contentOffset
var page = Int(round(offset.x / (view.bounds.width + ZLPhotoPreviewController.colItemSpacing)))
page = max(0, min(page, arrDataSources.count - 1))
if page == currentIndex {
return
}
currentIndex = page
resetSubViewStatus()
selPhotoPreview?.currentShowModelChanged(model: arrDataSources[currentIndex])
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
indexBeforOrientationChanged = currentIndex
let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0))
if let cell = cell as? ZLGifPreviewCell {
cell.loadGifWhenCellDisplaying()
} else if let cell = cell as? ZLLivePhotoPreviewCell {
cell.loadLivePhotoData()
}
}
}
extension ZLPhotoPreviewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return ZLPhotoPreviewController.colItemSpacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return ZLPhotoPreviewController.colItemSpacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: ZLPhotoPreviewController.colItemSpacing / 2, bottom: 0, right: ZLPhotoPreviewController.colItemSpacing / 2)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.zl.width, height: view.zl.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrDataSources.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let config = ZLPhotoConfiguration.default()
let model = arrDataSources[indexPath.row]
let baseCell: ZLPreviewBaseCell
if config.allowSelectGif, model.type == .gif {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLGifPreviewCell.zl.identifier, for: indexPath) as! ZLGifPreviewCell
cell.singleTapBlock = { [weak self] in
self?.tapPreviewCell()
}
cell.model = model
baseCell = cell
} else if config.allowSelectLivePhoto, model.type == .livePhoto {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLLivePhotoPreviewCell.zl.identifier, for: indexPath) as! ZLLivePhotoPreviewCell
cell.model = model
baseCell = cell
} else if config.allowSelectVideo, model.type == .video {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLVideoPreviewCell.zl.identifier, for: indexPath) as! ZLVideoPreviewCell
cell.model = model
baseCell = cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLPhotoPreviewCell.zl.identifier, for: indexPath) as! ZLPhotoPreviewCell
cell.singleTapBlock = { [weak self] in
self?.tapPreviewCell()
}
cell.model = model
baseCell = cell
}
baseCell.singleTapBlock = { [weak self] in
self?.tapPreviewCell()
}
return baseCell
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
(cell as? ZLPreviewBaseCell)?.resetSubViewStatusWhenCellEndDisplay()
}
}
// MARK:
class ZLPhotoPreviewSelectedView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDragDelegate, UICollectionViewDropDelegate {
private lazy var collectionView: UICollectionView = {
let layout = ZLCollectionViewFlowLayout()
layout.itemSize = CGSize(width: 60, height: 60)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.backgroundColor = .clear
view.dataSource = self
view.delegate = self
view.showsHorizontalScrollIndicator = false
view.alwaysBounceHorizontal = true
ZLPhotoPreviewSelectedViewCell.zl.register(view)
if #available(iOS 11.0, *) {
view.dragDelegate = self
view.dropDelegate = self
view.dragInteractionEnabled = true
view.isSpringLoaded = true
} else {
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction))
view.addGestureRecognizer(longPressGesture)
}
return view
}()
private var arrSelectedModels: [ZLPhotoModel]
private var currentShowModel: ZLPhotoModel
private var isDraging = false
var selectBlock: ((ZLPhotoModel) -> Void)?
var endSortBlock: (([ZLPhotoModel]) -> Void)?
init(selModels: [ZLPhotoModel], currentShowModel: ZLPhotoModel) {
arrSelectedModels = selModels
self.currentShowModel = currentShowModel
super.init(frame: .zero)
setupUI()
}
private func setupUI() {
addSubview(collectionView)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.frame = CGRect(x: 0, y: 10, width: bounds.width, height: 80)
if let index = arrSelectedModels.firstIndex(where: { $0 == self.currentShowModel }) {
collectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true)
}
}
func currentShowModelChanged(model: ZLPhotoModel) {
guard currentShowModel != model else {
return
}
currentShowModel = model
if let index = arrSelectedModels.firstIndex(where: { $0 == self.currentShowModel }) {
collectionView.performBatchUpdates({
self.collectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true)
}) { _ in
self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)
}
} else {
collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems)
}
}
func addSelModel(model: ZLPhotoModel) {
arrSelectedModels.append(model)
let indexPath = IndexPath(row: arrSelectedModels.count - 1, section: 0)
collectionView.insertItems(at: [indexPath])
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
func removeSelModel(model: ZLPhotoModel) {
guard let index = arrSelectedModels.firstIndex(where: { $0 == model }) else {
return
}
arrSelectedModels.remove(at: index)
collectionView.deleteItems(at: [IndexPath(row: index, section: 0)])
}
func refreshCell(for model: ZLPhotoModel) {
guard let index = arrSelectedModels.firstIndex(where: { $0 == model }) else {
return
}
collectionView.reloadItems(at: [IndexPath(row: index, section: 0)])
}
// MARK: iOS10
@objc func longPressAction(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
guard let indexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
return
}
isDraging = true
collectionView.beginInteractiveMovementForItem(at: indexPath)
} else if gesture.state == .changed {
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: collectionView))
} else if gesture.state == .ended {
isDraging = false
collectionView.endInteractiveMovement()
endSortBlock?(arrSelectedModels)
} else {
isDraging = false
collectionView.cancelInteractiveMovement()
}
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let moveModel = arrSelectedModels[sourceIndexPath.row]
arrSelectedModels.remove(at: sourceIndexPath.row)
arrSelectedModels.insert(moveModel, at: destinationIndexPath.row)
}
// MARK: iOS11
@available(iOS 11.0, *)
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
isDraging = true
let itemProvider = NSItemProvider()
let item = UIDragItem(itemProvider: itemProvider)
return [item]
}
@available(iOS 11.0, *)
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
if collectionView.hasActiveDrag {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
return UICollectionViewDropProposal(operation: .forbidden)
}
@available(iOS 11.0, *)
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
isDraging = false
guard let destinationIndexPath = coordinator.destinationIndexPath else {
return
}
guard let item = coordinator.items.first else {
return
}
guard let sourceIndexPath = item.sourceIndexPath else {
return
}
if coordinator.proposal.operation == .move {
collectionView.performBatchUpdates({
let moveModel = self.arrSelectedModels[sourceIndexPath.row]
self.arrSelectedModels.remove(at: sourceIndexPath.row)
self.arrSelectedModels.insert(moveModel, at: destinationIndexPath.row)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
}, completion: nil)
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
endSortBlock?(arrSelectedModels)
}
}
@available(iOS 11.0, *)
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {
isDraging = false
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrSelectedModels.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLPhotoPreviewSelectedViewCell.zl.identifier, for: indexPath) as! ZLPhotoPreviewSelectedViewCell
let m = arrSelectedModels[indexPath.row]
cell.model = m
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard !isDraging else {
return
}
let m = arrSelectedModels[indexPath.row]
currentShowModel = m
collectionView.performBatchUpdates({
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}) { _ in
self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)
}
selectBlock?(m)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let m = arrSelectedModels[indexPath.row]
if m == currentShowModel {
cell.layer.borderWidth = 4
} else {
cell.layer.borderWidth = 0
}
}
}
class ZLPhotoPreviewSelectedViewCell: UICollectionViewCell {
private lazy var imageView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
return view
}()
private lazy var tagImageView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFit
view.clipsToBounds = true
return view
}()
private lazy var tagLabel: UILabel = {
let label = UILabel()
label.font = .zl.font(ofSize: 13)
label.textColor = .white
return label
}()
private var imageRequestID: PHImageRequestID = PHInvalidImageRequestID
private var imageIdentifier = ""
var model: ZLPhotoModel! {
didSet {
self.configureCell()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
layer.borderColor = UIColor.zl.bottomToolViewBtnNormalBgColorOfPreviewVC.cgColor
contentView.addSubview(imageView)
contentView.addSubview(tagImageView)
contentView.addSubview(tagLabel)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = bounds
tagImageView.frame = CGRect(x: 5, y: bounds.height - 25, width: 20, height: 20)
tagLabel.frame = CGRect(x: 5, y: bounds.height - 25, width: bounds.width - 10, height: 20)
}
private func configureCell() {
let size = CGSize(width: bounds.width * 1.5, height: bounds.height * 1.5)
if imageRequestID > PHInvalidImageRequestID {
PHImageManager.default().cancelImageRequest(imageRequestID)
}
if model.type == .video {
tagImageView.isHidden = false
tagImageView.image = .zl.getImage("zl_video")
tagLabel.isHidden = true
} else if ZLPhotoConfiguration.default().allowSelectGif, model.type == .gif {
tagImageView.isHidden = true
tagLabel.isHidden = false
tagLabel.text = "GIF"
} else if ZLPhotoConfiguration.default().allowSelectLivePhoto, model.type == .livePhoto {
tagImageView.isHidden = false
tagImageView.image = .zl.getImage("zl_livePhoto")
tagLabel.isHidden = true
} else {
if let _ = model.editImage {
tagImageView.isHidden = false
tagImageView.image = .zl.getImage("zl_editImage_tag")
} else {
tagImageView.isHidden = true
tagLabel.isHidden = true
}
}
imageIdentifier = model.ident
imageView.image = nil
if let ei = model.editImage {
imageView.image = ei
} else {
imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: size, completion: { [weak self] image, _ in
if self?.imageIdentifier == self?.model.ident {
self?.imageView.image = image
}
})
}
}
}