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

1190 lines
36 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.

//
// ZLPhotoPreviewCell.swift
// ZLPhotoBrowser
//
// Created by long on 2020/8/21.
//
// 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
import PhotosUI
class ZLPreviewBaseCell: UICollectionViewCell {
var singleTapBlock: (() -> Void)?
var currentImage: UIImage? {
return nil
}
override init(frame: CGRect) {
super.init(frame: frame)
NotificationCenter.default.addObserver(self, selector: #selector(previewVCScroll), name: ZLPhotoPreviewController.previewVCScrollNotification, object: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func previewVCScroll() {}
func resetSubViewStatusWhenCellEndDisplay() {}
func resizeImageView(imageView: UIImageView, asset: PHAsset) {
let size = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
var frame: CGRect = .zero
let viewW = bounds.width
let viewH = bounds.height
var width = viewW
// videolivephoto
if UIApplication.shared.statusBarOrientation.isLandscape {
let height = viewH
frame.size.height = height
let imageWHRatio = size.width / size.height
let viewWHRatio = viewW / viewH
if imageWHRatio > viewWHRatio {
frame.size.width = floor(height * imageWHRatio)
if frame.size.width > viewW {
frame.size.width = viewW
frame.size.height = viewW / imageWHRatio
}
} else {
width = floor(height * imageWHRatio)
if width < 1 || width.isNaN {
width = viewW
}
frame.size.width = width
}
} else {
frame.size.width = width
let imageHWRatio = size.height / size.width
let viewHWRatio = viewH / viewW
if imageHWRatio > viewHWRatio {
frame.size.height = floor(width * imageHWRatio)
} else {
var height = floor(width * imageHWRatio)
if height < 1 || height.isNaN {
height = viewH
}
frame.size.height = height
}
}
imageView.frame = frame
if UIApplication.shared.statusBarOrientation.isLandscape {
if frame.height < viewH {
imageView.center = CGPoint(x: viewW / 2, y: viewH / 2)
} else {
imageView.frame = CGRect(origin: CGPoint(x: (viewW - frame.width) / 2, y: 0), size: frame.size)
}
} else {
if frame.width < viewW || frame.height < viewH {
imageView.center = CGPoint(x: viewW / 2, y: viewH / 2)
}
}
}
func animateImageFrame(convertTo view: UIView) -> CGRect {
return .zero
}
}
// MARK: local image preview cell
class ZLLocalImagePreviewCell: ZLPreviewBaseCell {
override var currentImage: UIImage? {
return preview.image
}
lazy var preview: ZLPreviewView = {
let view = ZLPreviewView()
view.singleTapBlock = { [weak self] in
self?.singleTapBlock?()
}
return view
}()
var image: UIImage? {
didSet {
preview.image = image
preview.resetSubViewSize()
}
}
var longPressBlock: (() -> Void)?
deinit {
zl_debugPrint("ZLLocalImagePreviewCell deinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
preview.frame = bounds
}
private func setupUI() {
contentView.addSubview(preview)
let longGes = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
longGes.minimumPressDuration = 0.5
addGestureRecognizer(longGes)
}
override func resetSubViewStatusWhenCellEndDisplay() {
preview.scrollView.zoomScale = 1
}
@objc func longPressAction(_ ges: UILongPressGestureRecognizer) {
guard currentImage != nil else {
return
}
if ges.state == .began {
longPressBlock?()
}
}
}
// MARK: net image preview cell
class ZLNetImagePreviewCell: ZLLocalImagePreviewCell {
private lazy var progressView: ZLProgressView = {
let view = ZLProgressView()
view.isHidden = true
return view
}()
var progress: CGFloat = 0 {
didSet {
progressView.progress = progress
progressView.isHidden = progress >= 1
}
}
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(progressView)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
bringSubviewToFront(progressView)
progressView.frame = CGRect(x: bounds.width / 2 - 20, y: bounds.height / 2 - 20, width: 40, height: 40)
}
override func resetSubViewStatusWhenCellEndDisplay() {
progressView.isHidden = true
preview.scrollView.zoomScale = 1
}
}
// MARK: static image preview cell
class ZLPhotoPreviewCell: ZLPreviewBaseCell {
override var currentImage: UIImage? {
return preview.image
}
private lazy var preview: ZLPreviewView = {
let view = ZLPreviewView()
view.singleTapBlock = { [weak self] in
self?.singleTapBlock?()
}
return view
}()
var model: ZLPhotoModel! {
didSet {
preview.model = model
}
}
deinit {
zl_debugPrint("ZLPhotoPreviewCell deinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
preview.frame = bounds
}
private func setupUI() {
contentView.addSubview(preview)
}
override func resetSubViewStatusWhenCellEndDisplay() {
preview.scrollView.zoomScale = 1
}
override func animateImageFrame(convertTo view: UIView) -> CGRect {
let rect = preview.scrollView.convert(preview.containerView.frame, to: self)
return convert(rect, to: view)
}
}
// MARK: gif preview cell
class ZLGifPreviewCell: ZLPreviewBaseCell {
override var currentImage: UIImage? {
return preview.image
}
private lazy var preview: ZLPreviewView = {
let view = ZLPreviewView()
view.singleTapBlock = { [weak self] in
self?.singleTapBlock?()
}
return view
}()
var model: ZLPhotoModel! {
didSet {
preview.model = model
}
}
deinit {
zl_debugPrint("ZLGifPreviewCell deinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
preview.frame = bounds
}
private func setupUI() {
contentView.addSubview(preview)
}
override func previewVCScroll() {
preview.pauseGif()
}
func resumeGif() {
preview.resumeGif()
}
func pauseGif() {
preview.pauseGif()
}
/// gif线willdisplay
func loadGifWhenCellDisplaying() {
preview.loadGifData()
}
override func resetSubViewStatusWhenCellEndDisplay() {
preview.scrollView.zoomScale = 1
}
override func animateImageFrame(convertTo view: UIView) -> CGRect {
let rect = preview.scrollView.convert(preview.containerView.frame, to: self)
return convert(rect, to: view)
}
}
// MARK: live photo preview cell
class ZLLivePhotoPreviewCell: ZLPreviewBaseCell {
private lazy var imageView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFit
return view
}()
private var imageRequestID = PHInvalidImageRequestID
private var livePhotoRequestID = PHInvalidImageRequestID
private var onFetchingLivePhoto = false
private var fetchLivePhotoDone = false
var model: ZLPhotoModel! {
didSet {
loadNormalImage()
}
}
lazy var livePhotoView: PHLivePhotoView = {
let view = PHLivePhotoView()
view.contentMode = .scaleAspectFit
return view
}()
override var currentImage: UIImage? {
return imageView.image
}
deinit {
zl_debugPrint("ZLLivePhotoPewviewCell deinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
livePhotoView.frame = bounds
resizeImageView(imageView: imageView, asset: model.asset)
}
override func previewVCScroll() {
livePhotoView.stopPlayback()
}
override func animateImageFrame(convertTo view: UIView) -> CGRect {
return convert(imageView.frame, to: view)
}
override func resetSubViewStatusWhenCellEndDisplay() {
PHImageManager.default().cancelImageRequest(livePhotoRequestID)
}
private func setupUI() {
contentView.addSubview(livePhotoView)
contentView.addSubview(imageView)
}
private func loadNormalImage() {
if imageRequestID > PHInvalidImageRequestID {
PHImageManager.default().cancelImageRequest(imageRequestID)
}
if livePhotoRequestID > PHInvalidImageRequestID {
PHImageManager.default().cancelImageRequest(livePhotoRequestID)
}
onFetchingLivePhoto = false
imageView.isHidden = false
// livephoto
var size = model.previewSize
size.width /= 4
size.height /= 4
resizeImageView(imageView: imageView, asset: model.asset)
imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: size, completion: { [weak self] image, _ in
self?.imageView.image = image
})
}
private func startPlayLivePhoto() {
imageView.isHidden = true
livePhotoView.startPlayback(with: .full)
}
func loadLivePhotoData() {
guard !onFetchingLivePhoto else {
if fetchLivePhotoDone {
startPlayLivePhoto()
}
return
}
onFetchingLivePhoto = true
fetchLivePhotoDone = false
livePhotoRequestID = ZLPhotoManager.fetchLivePhoto(for: model.asset, completion: { livePhoto, _, isDegraded in
if !isDegraded {
self.fetchLivePhotoDone = true
self.livePhotoView.livePhoto = livePhoto
self.startPlayLivePhoto()
}
})
}
}
// MARK: video preview cell
class ZLVideoPreviewCell: ZLPreviewBaseCell {
override var currentImage: UIImage? {
return imageView.image
}
private var player: AVPlayer?
private var playerLayer: AVPlayerLayer?
private lazy var progressView = ZLProgressView()
private lazy var imageView: UIImageView = {
let view = UIImageView()
view.clipsToBounds = true
view.contentMode = .scaleAspectFill
return view
}()
private lazy var playBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
btn.addTarget(self, action: #selector(playBtnClick), for: .touchUpInside)
return btn
}()
private lazy var syncErrorLabel: UILabel = {
let attStr = NSMutableAttributedString()
let attach = NSTextAttachment()
attach.image = .zl.getImage("zl_videoLoadFailed")
attach.bounds = CGRect(x: 0, y: -10, width: 30, height: 30)
attStr.append(NSAttributedString(attachment: attach))
let errorText = NSAttributedString(
string: localLanguageTextValue(.iCloudVideoLoadFaild),
attributes: [
NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: UIFont.zl.font(ofSize: 12)
]
)
attStr.append(errorText)
let label = UILabel()
label.attributedText = attStr
return label
}()
private var imageRequestID = PHInvalidImageRequestID
private var videoRequestID = PHInvalidImageRequestID
private var onFetchingVideo = false
private var fetchVideoDone = false
private let operationQueue = DispatchQueue(label: "com.ZLPhotoBrowser.ZLVideoPreviewCell")
var isPlaying: Bool {
if player != nil, player?.rate != 0 {
return true
}
return false
}
var model: ZLPhotoModel! {
didSet {
configureCell()
}
}
deinit {
zl_debugPrint("ZLVideoPreviewCell deinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer?.frame = bounds
resizeImageView(imageView: imageView, asset: model.asset)
let insets = deviceSafeAreaInsets()
playBtn.frame = CGRect(x: 0, y: insets.top, width: bounds.width, height: bounds.height - insets.top - insets.bottom)
syncErrorLabel.frame = CGRect(x: 10, y: insets.top + 60, width: bounds.width - 20, height: 35)
progressView.frame = CGRect(x: bounds.width / 2 - 30, y: bounds.height / 2 - 30, width: 60, height: 60)
}
override func previewVCScroll() {
if player != nil, player?.rate != 0 {
pausePlayer(seekToZero: false)
}
}
override func resetSubViewStatusWhenCellEndDisplay() {
imageView.isHidden = false
player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
}
override func animateImageFrame(convertTo view: UIView) -> CGRect {
return convert(imageView.frame, to: view)
}
private func setupUI() {
contentView.addSubview(imageView)
contentView.addSubview(syncErrorLabel)
contentView.addSubview(progressView)
contentView.addSubview(playBtn)
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
}
private func configureCell() {
imageView.image = nil
imageView.isHidden = false
syncErrorLabel.isHidden = true
playBtn.isEnabled = false
player = nil
playerLayer?.removeFromSuperlayer()
playerLayer = nil
if imageRequestID > PHInvalidImageRequestID {
PHImageManager.default().cancelImageRequest(imageRequestID)
}
if videoRequestID > PHInvalidImageRequestID {
PHImageManager.default().cancelImageRequest(videoRequestID)
}
//
var size = model.previewSize
size.width /= 2
size.height /= 2
resizeImageView(imageView: imageView, asset: model.asset)
imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: size, completion: { image, _ in
self.imageView.image = image
})
videoRequestID = ZLPhotoManager.fetchVideo(for: model.asset, progress: { [weak self] progress, _, _, _ in
self?.progressView.progress = progress
zl_debugPrint("video progress \(progress)")
if progress >= 1 {
zl_debugPrint("video load finished")
self?.progressView.isHidden = true
} else {
self?.progressView.isHidden = false
}
}, completion: { [weak self] item, info, isDegraded in
let error = info?[PHImageErrorKey] as? Error
let isFetchError = ZLPhotoManager.isFetchImageError(error)
if isFetchError {
self?.syncErrorLabel.isHidden = false
self?.playBtn.setImage(nil, for: .normal)
}
if !isDegraded, item != nil {
self?.fetchVideoDone = true
self?.configurePlayerLayer(item!)
}
})
}
private func configurePlayerLayer(_ item: AVPlayerItem) {
playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
playBtn.isEnabled = true
player = AVPlayer(playerItem: item)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = bounds
layer.insertSublayer(playerLayer!, at: 0)
NotificationCenter.default.addObserver(self, selector: #selector(playFinish), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
}
@objc private func playBtnClick() {
let currentTime = player?.currentItem?.currentTime()
let duration = player?.currentItem?.duration
if player?.rate == 0 {
if currentTime?.value == duration?.value {
player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
}
imageView.isHidden = true
player?.play()
playBtn.setImage(nil, for: .normal)
singleTapBlock?()
} else {
pausePlayer(seekToZero: false)
}
}
@objc private func playFinish() {
pausePlayer(seekToZero: true)
}
@objc private func appWillResignActive() {
if player != nil, player?.rate != 0 {
pausePlayer(seekToZero: false)
}
}
private func pausePlayer(seekToZero: Bool) {
player?.pause()
if seekToZero {
player?.seek(to: .zero)
}
playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
singleTapBlock?()
operationQueue.async {
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
}
func pauseWhileTransition() {
player?.pause()
playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
}
}
// MARK: net video preview cell
class ZLNetVideoPreviewCell: ZLPreviewBaseCell {
private var player: AVPlayer?
private var playerLayer: AVPlayerLayer?
private lazy var playBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
btn.addTarget(self, action: #selector(playBtnClick), for: .touchUpInside)
return btn
}()
var isPlaying: Bool {
if player != nil, player?.rate != 0 {
return true
}
return false
}
private let operationQueue = DispatchQueue(label: "com.ZLPhotoBrowser.ZLNetVideoPreviewCell")
deinit {
zl_debugPrint("v deinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer?.frame = bounds
let insets = deviceSafeAreaInsets()
playBtn.frame = CGRect(x: 0, y: insets.top, width: bounds.width, height: bounds.height - insets.top - insets.bottom)
}
override func resetSubViewStatusWhenCellEndDisplay() {
player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
}
private func setupUI() {
contentView.addSubview(playBtn)
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
}
@objc private func playBtnClick() {
let currentTime = player?.currentItem?.currentTime()
let duration = player?.currentItem?.duration
if player?.rate == 0 {
if currentTime?.value == duration?.value {
player?.currentItem?.seek(to: CMTimeMake(value: 0, timescale: 1))
}
player?.play()
playBtn.setImage(nil, for: .normal)
singleTapBlock?()
} else {
pausePlayer(seekToZero: false)
}
}
@objc private func playFinish() {
pausePlayer(seekToZero: true)
}
@objc private func appWillResignActive() {
if player != nil, player?.rate != 0 {
pausePlayer(seekToZero: false)
}
}
override func previewVCScroll() {
if player != nil, player?.rate != 0 {
pausePlayer(seekToZero: false)
}
}
private func pausePlayer(seekToZero: Bool) {
player?.pause()
if seekToZero {
player?.seek(to: .zero)
}
playBtn.setImage(.zl.getImage("zl_playVideo"), for: .normal)
singleTapBlock?()
operationQueue.async {
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
}
func configureCell(videoUrl: URL, httpHeader: [String: Any]?) {
player = nil
playerLayer?.removeFromSuperlayer()
playerLayer = nil
var options: [String: Any] = [:]
options["AVURLAssetHTTPHeaderFieldsKey"] = httpHeader
let asset = AVURLAsset(url: videoUrl, options: options)
let item = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: item)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = bounds
layer.insertSublayer(playerLayer!, at: 0)
NotificationCenter.default.addObserver(self, selector: #selector(playFinish), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
}
}
// MARK: class ZLPreviewView
class ZLPreviewView: UIView {
private static let defaultMaxZoomScale: CGFloat = 3
private lazy var progressView = ZLProgressView()
private var imageRequestID = PHInvalidImageRequestID
private var gifImageRequestID = PHInvalidImageRequestID
private var imageIdentifier = ""
private var onFetchingGif = false
private var fetchGifDone = false
lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.maximumZoomScale = ZLPreviewView.defaultMaxZoomScale
view.minimumZoomScale = 1
view.isMultipleTouchEnabled = true
view.delegate = self
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = false
view.delaysContentTouches = false
return view
}()
lazy var containerView = UIView()
lazy var imageView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
return view
}()
var image: UIImage? {
get {
return imageView.image
}
set {
imageView.image = newValue
}
}
var singleTapBlock: (() -> Void)?
var doubleTapBlock: (() -> Void)?
var model: ZLPhotoModel! {
didSet {
self.configureView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
scrollView.frame = bounds
progressView.frame = CGRect(x: bounds.width / 2 - 20, y: bounds.height / 2 - 20, width: 40, height: 40)
scrollView.zoomScale = 1
resetSubViewSize()
}
private func setupUI() {
addSubview(scrollView)
scrollView.addSubview(containerView)
containerView.addSubview(imageView)
addSubview(progressView)
let singleTap = UITapGestureRecognizer(target: self, action: #selector(singleTapAction(_:)))
addGestureRecognizer(singleTap)
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapAction(_:)))
doubleTap.numberOfTapsRequired = 2
addGestureRecognizer(doubleTap)
singleTap.require(toFail: doubleTap)
}
@objc private func singleTapAction(_ tap: UITapGestureRecognizer) {
singleTapBlock?()
}
@objc private func doubleTapAction(_ tap: UITapGestureRecognizer) {
let scale: CGFloat = scrollView.zoomScale != scrollView.maximumZoomScale ? scrollView.maximumZoomScale : 1
let tapPoint = tap.location(in: self)
var rect = CGRect.zero
rect.size.width = scrollView.frame.width / scale
rect.size.height = scrollView.frame.height / scale
rect.origin.x = tapPoint.x - (rect.size.width / 2)
rect.origin.y = tapPoint.y - (rect.size.height / 2)
scrollView.zoom(to: rect, animated: true)
}
private func configureView() {
if imageRequestID > PHInvalidImageRequestID {
PHImageManager.default().cancelImageRequest(imageRequestID)
}
if gifImageRequestID > PHInvalidImageRequestID {
PHImageManager.default().cancelImageRequest(gifImageRequestID)
}
scrollView.zoomScale = 1
imageIdentifier = model.ident
if ZLPhotoConfiguration.default().allowSelectGif, model.type == .gif {
loadGifFirstFrame()
} else {
loadPhoto()
}
}
private func requestPhotoSize(gif: Bool) -> CGSize {
// gif
var size = model.previewSize
if gif {
size.width /= 2
size.height /= 2
}
return size
}
private func loadPhoto() {
if let editImage = model.editImage {
imageView.image = editImage
resetSubViewSize()
} else {
imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: requestPhotoSize(gif: false), progress: { [weak self] progress, _, _, _ in
self?.progressView.progress = progress
if progress >= 1 {
self?.progressView.isHidden = true
} else {
self?.progressView.isHidden = false
}
}, completion: { [weak self] image, isDegraded in
guard self?.imageIdentifier == self?.model.ident else {
return
}
self?.imageView.image = image
self?.resetSubViewSize()
if !isDegraded {
self?.progressView.isHidden = true
self?.imageRequestID = PHInvalidImageRequestID
}
})
}
}
private func loadGifFirstFrame() {
onFetchingGif = false
fetchGifDone = false
if ZLPhotoConfiguration.default().gifPlayBlock != nil {
imageView.subviews.forEach { $0.removeFromSuperview() }
}
imageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: requestPhotoSize(gif: true), completion: { [weak self] image, _ in
guard self?.imageIdentifier == self?.model.ident else {
return
}
if self?.fetchGifDone == false {
self?.imageView.image = image
self?.resetSubViewSize()
}
})
}
func loadGifData() {
guard !onFetchingGif else {
if fetchGifDone {
resumeGif()
}
return
}
onFetchingGif = true
fetchGifDone = false
imageView.layer.speed = 1
imageView.layer.timeOffset = 0
imageView.layer.beginTime = 0
gifImageRequestID = ZLPhotoManager.fetchOriginalImageData(for: model.asset, progress: { [weak self] progress, _, _, _ in
self?.progressView.progress = progress
if progress >= 1 {
self?.progressView.isHidden = true
} else {
self?.progressView.isHidden = false
}
}, completion: { [weak self] data, info, isDegraded in
guard let `self` = self else { return }
guard self.imageIdentifier == self.model.ident else {
return
}
if !isDegraded {
self.fetchGifDone = true
if let gifPlayBlock = ZLPhotoConfiguration.default().gifPlayBlock {
gifPlayBlock(self.imageView, data, info)
} else {
self.imageView.image = UIImage.zl.animateGifImage(data: data)
}
self.resetSubViewSize()
}
})
}
func resetSubViewSize() {
let size: CGSize
if let model = model {
if let ei = model.editImage {
size = ei.size
} else {
size = CGSize(width: model.asset.pixelWidth, height: model.asset.pixelHeight)
}
} else {
size = imageView.image?.size ?? bounds.size
}
var frame: CGRect = .zero
let viewW = bounds.width
let viewH = bounds.height
var width = viewW
if UIApplication.shared.statusBarOrientation.isLandscape {
let height = viewH
frame.size.height = height
let imageWHRatio = size.width / size.height
let viewWHRatio = viewW / viewH
if imageWHRatio > viewWHRatio {
frame.size.width = floor(height * imageWHRatio)
if frame.size.width > viewW {
//
frame.size.width = viewW
frame.size.height = viewW / imageWHRatio
}
} else {
width = floor(height * imageWHRatio)
if width < 1 || width.isNaN {
width = viewW
}
frame.size.width = width
}
} else {
frame.size.width = width
let imageHWRatio = size.height / size.width
let viewHWRatio = viewH / viewW
if imageHWRatio > viewHWRatio {
//
frame.size.width = min(size.width, viewW)
frame.size.height = floor(frame.size.width * imageHWRatio)
} else {
var height = floor(frame.size.width * imageHWRatio)
if height < 1 || height.isNaN {
height = viewH
}
frame.size.height = height
}
}
// scroll view zoom scale
if frame.width < frame.height {
scrollView.maximumZoomScale = max(ZLPreviewView.defaultMaxZoomScale, viewW / frame.width)
} else {
scrollView.maximumZoomScale = max(ZLPreviewView.defaultMaxZoomScale, viewH / frame.height)
}
containerView.frame = frame
var contenSize: CGSize = .zero
if UIApplication.shared.statusBarOrientation.isLandscape {
contenSize = CGSize(width: width, height: max(viewH, frame.height))
if frame.height < viewH {
containerView.center = CGPoint(x: viewW / 2, y: viewH / 2)
} else {
containerView.frame = CGRect(origin: CGPoint(x: (viewW - frame.width) / 2, y: 0), size: frame.size)
}
} else {
contenSize = frame.size
if frame.height < viewH {
containerView.center = CGPoint(x: viewW / 2, y: viewH / 2)
} else {
containerView.frame = CGRect(origin: CGPoint(x: (viewW - frame.width) / 2, y: 0), size: frame.size)
}
}
ZLMainAsync(after: 0.01) {
self.scrollView.contentSize = contenSize
self.imageView.frame = self.containerView.bounds
self.scrollView.contentOffset = .zero
}
}
func resumeGif() {
guard let m = model else { return }
guard ZLPhotoConfiguration.default().allowSelectGif, m.type == .gif else { return }
let config = ZLPhotoConfiguration.default()
if config.gifPlayBlock != nil, let resumeGIFBlock = config.resumeGIFBlock {
resumeGIFBlock(imageView)
return
}
guard imageView.layer.speed != 1 else { return }
let pauseTime = imageView.layer.timeOffset
imageView.layer.speed = 1
imageView.layer.timeOffset = 0
imageView.layer.beginTime = 0
let timeSincePause = imageView.layer.convertTime(CACurrentMediaTime(), from: nil) - pauseTime
imageView.layer.beginTime = timeSincePause
}
func pauseGif() {
guard let m = model else { return }
guard ZLPhotoConfiguration.default().allowSelectGif, m.type == .gif else { return }
let config = ZLPhotoConfiguration.default()
if config.gifPlayBlock != nil, let pauseGIFBlock = config.pauseGIFBlock {
pauseGIFBlock(imageView)
return
}
guard imageView.layer.speed != 0 else { return }
let pauseTime = imageView.layer.convertTime(CACurrentMediaTime(), from: nil)
imageView.layer.speed = 0
imageView.layer.timeOffset = pauseTime
}
}
extension ZLPreviewView: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return containerView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let offsetX = (scrollView.frame.width > scrollView.contentSize.width) ? (scrollView.frame.width - scrollView.contentSize.width) * 0.5 : 0
let offsetY = (scrollView.frame.height > scrollView.contentSize.height) ? (scrollView.frame.height - scrollView.contentSize.height) * 0.5 : 0
containerView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX, y: scrollView.contentSize.height * 0.5 + offsetY)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
resumeGif()
}
}