// // 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 // video和livephoto没必要处理长图和宽图 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() } }