// // PQUploadViewController.swift // PQSpeed // // Created by SanW on 2020/8/1. // Copyright © 2020 BytesFlow. All rights reserved. // import MobileCoreServices import Photos import UIKit let playerHeaderH: CGFloat = cScreenWidth * (250 / 375) class PQUploadController: PQBaseViewController { // 最大的宽度 var maxWidth: CGFloat = cScreenWidth // 最大的高度 var maxHeight: CGFloat = adapterWidth(width: 300) // 画面比例 var aspectRatio: aspectRatio? var preViewSize: CGSize { if aspectRatio == nil, (isMember(of: PQUploadController.self) && selectedData == nil) || (!isMember(of: PQUploadController.self) && uploadData == nil) { return CGSize(width: maxHeight, height: maxHeight) } if selectedData != nil { aspectRatio = .origin(width: CGFloat(videoData[lastSeletedIndex?.item ?? 0].videoWidth), height: CGFloat(videoData[lastSeletedIndex?.item ?? 0].videoHeight)) } else if aspectRatio == nil { aspectRatio = .origin(width: CGFloat(uploadData?.videoWidth ?? maxHeight), height: CGFloat(uploadData?.videoHeight ?? maxHeight)) } switch aspectRatio { case let .origin(width, height): var tempHeight: CGFloat = 0 var tempWidth: CGFloat = 0 if width > height { tempHeight = (maxWidth * height / width) tempWidth = maxWidth } else { tempHeight = maxHeight tempWidth = (maxHeight * width / height) } if tempHeight.isNaN || tempWidth.isNaN { return CGSize.zero } else { return CGSize(width: tempWidth, height: tempHeight) } case .oneToOne: if maxWidth > maxHeight { return CGSize(width: maxHeight, height: maxHeight) } else { return CGSize(width: maxWidth, height: maxWidth) } case .sixteenToNine: return CGSize(width: maxWidth, height: maxWidth * 9.0 / 16.0) case .nineToSixteen: return CGSize(width: maxHeight * 9.0 / 16.0, height: maxHeight) default: break } return CGSize(width: maxHeight, height: maxHeight) } // 上传入口类型 var sourceType: videoUploadSourceType = .videoUpload // 制作视频项目Id var makeVideoProjectId: String? // 再创作数据 var reCreateData: PQReCreateModel? // 视频创作埋点数据 var eventTrackData: PQVideoMakeEventTrackModel? // 制作视频草稿Id var makeVideoDraftboxId: String? // 制作视频草稿结构化数据 var makeVideoSdata: String? var isAssetImage: Bool = false // 是否请求的是图片 var videoWidth: CGFloat = 0 // 视频宽 var videoHeight: CGFloat = 0 // 视频高 var isLoop: Bool = true // 是否循环播放 var playerItem: AVPlayerItem? // 当前播放item var videoOutput: AVPlayerItemVideoOutput? let itemSize = CGSize(width: ((cScreenWidth - cDefaultMargin) / 3) * UIScreen.main.scale, height: ((cScreenWidth - cDefaultMargin) / 3) * UIScreen.main.scale) var categoryH: CGFloat = cDefaultMargin * 40 // 相簿高度 var videoData: [PQUploadModel] = Array.init() var categoryData: [PQUploadModel] = Array.init() // 当前选中的数据 var selectedData: PQUploadModel? // 确定上传的数据 var uploadData: PQUploadModel? var catagerySelectedIndex: IndexPath = IndexPath(item: 0, section: 0) // 更多图库选择 var isJumpToAuthorization: Bool = false // 是否跳到授权 var lastSeletedIndex: IndexPath? // 上次选中的index lazy var imageManager: PHCachingImageManager = { (PHCachingImageManager.default() as? PHCachingImageManager) ?? PHCachingImageManager() }() var allPhotos: PHFetchResult! var previousPreheatRect = CGRect.zero var smartAlbums: PHFetchResult! var userCollections: PHFetchResult! let sectionLocalizedTitles = ["", NSLocalizedString("Smart Albums", comment: ""), NSLocalizedString("Albums", comment: "")] lazy var fetchOptions: PHFetchOptions = { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] fetchOptions.predicate = NSPredicate(format: "mediaType = %d", !isAssetImage ? PHAssetMediaType.video.rawValue : PHAssetMediaType.image.rawValue) return fetchOptions }() lazy var imagesOptions: PHImageRequestOptions = { let imagesOptions = PHImageRequestOptions() imagesOptions.isSynchronous = true imagesOptions.isNetworkAccessAllowed = false imagesOptions.deliveryMode = .opportunistic imagesOptions.resizeMode = .fast imagesOptions.progressHandler = { _, _, _, info in BFLog(message: "progressHandler = \(info)") } return imagesOptions }() lazy var singleImageOptions: PHImageRequestOptions = { let singleImageOptions = PHImageRequestOptions() singleImageOptions.isSynchronous = true singleImageOptions.isNetworkAccessAllowed = false singleImageOptions.deliveryMode = .highQualityFormat return singleImageOptions }() lazy var backBtn: UIButton = { let backBtn = UIButton(type: .custom) backBtn.frame = CGRect(x: 0, y: cDevice_iPhoneStatusBarHei, width: cDefaultMargin * 4, height: cDefaultMargin * 4) backBtn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: -5, right: 0) backBtn.backgroundColor = .yellow backBtn.setImage(UIImage().BF_Image(named: "icon_blanc_back"), for: .normal) backBtn.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside) return backBtn }() var anthorEmptyData: PQEmptyModel? = { let anthorEmptyData = PQEmptyModel() anthorEmptyData.title = "开始上传" anthorEmptyData.summary = "要开始上传视频,请先授予相册使用权限" anthorEmptyData.emptyImage = "icon_authorError" return anthorEmptyData }() var emptyData: PQEmptyModel? = { let emptyData = PQEmptyModel() emptyData.title = "哦呜~ 你没有可上传的视频~" emptyData.emptyImage = "video_empty" return emptyData }() lazy var emptyRemindView: PQEmptyRemindView = { let remindView = PQEmptyRemindView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei)) remindView.isHidden = true remindView.emptyData = anthorEmptyData view.addSubview(remindView) remindView.fullRefreshBloc = { [weak self] _, _ in self?.isJumpToAuthorization = true if self?.emptyRemindView.refreshBtn.currentTitle == "授予权限" { openAppSetting() } } return remindView }() lazy var collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.sectionInset = UIEdgeInsets.zero let collectionView = UICollectionView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei), collectionViewLayout: layout) collectionView.showsVerticalScrollIndicator = false collectionView.register(PQSelecteVideoItemCell.self, forCellWithReuseIdentifier: "PQSelecteVideoItemCell") collectionView.delegate = self collectionView.dataSource = self if #available(iOS 11.0, *) { collectionView.contentInsetAdjustmentBehavior = .never } else { automaticallyAdjustsScrollViewInsets = false } collectionView.backgroundColor = .clear return collectionView }() lazy var categoryCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.sectionInset = UIEdgeInsets.zero let categoryCollectionView = UICollectionView(frame: CGRect(x: 0, y: -categoryH, width: cScreenWidth, height: categoryH), collectionViewLayout: layout) categoryCollectionView.showsVerticalScrollIndicator = false categoryCollectionView.register(PQAssetCategoryCell.self, forCellWithReuseIdentifier: "PQAssetCategoryCell") categoryCollectionView.delegate = self categoryCollectionView.dataSource = self if #available(iOS 11.0, *) { categoryCollectionView.contentInsetAdjustmentBehavior = .never } else { automaticallyAdjustsScrollViewInsets = false } categoryCollectionView.backgroundColor = .white return categoryCollectionView }() lazy var categoryView: UIView = { let categoryView = UIView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei)) categoryView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6) let bgView = UIView(frame: CGRect(x: 0, y: categoryH, width: categoryView.frame.width, height: categoryView.frame.height - categoryH)) let ges = UITapGestureRecognizer(target: self, action: #selector(cancelClick)) bgView.addGestureRecognizer(ges) categoryView.addSubview(bgView) categoryView.isHidden = true return categoryView }() lazy var playBtn: UIButton = { let playBtn = UIButton(type: .custom) playBtn.frame = CGRect(x: (preViewSize.width - cDefaultMargin * 5) / 2, y: (preViewSize.height - cDefaultMargin * 5) / 2, width: cDefaultMargin * 5, height: cDefaultMargin * 5) playBtn.setImage(UIImage(named: "icon_video_play_big"), for: .normal) playBtn.tag = 4 playBtn.isHidden = true playBtn.isUserInteractionEnabled = false // playBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside) return playBtn }() var playerHeaderView: UIImageView = { let playerHeaderView = UIImageView(frame: CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: 0)) playerHeaderView.isUserInteractionEnabled = true playerHeaderView.contentMode = .scaleAspectFit playerHeaderView.clipsToBounds = true playerHeaderView.backgroundColor = UIColor.yellow return playerHeaderView }() lazy var selecteBtn: UIButton = { let selecteBtn = UIButton(frame: CGRect(x: deleteBtn.frame.maxX + cDefaultMargin, y: 0, width: cScreenWidth - nextBtn.frame.width - deleteBtn.frame.maxX - cDefaultMargin * 5, height: cDevice_iPhoneTabBarHei)) selecteBtn.titleLabel?.lineBreakMode = .byTruncatingTail selecteBtn.setTitle("全部", for: .normal) selecteBtn.setImage(UIImage().BF_Image(named: "icon_uploadVideo_more"), for: .normal) selecteBtn.setTitleColor(UIColor.black, for: .normal) selecteBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium) selecteBtn.tag = 2 selecteBtn.imagePosition(at: .right, space: cDefaultMargin / 2) selecteBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside) return selecteBtn }() lazy var deleteBtn: UIButton = { let deleteBtn = UIButton(frame: CGRect(x: cDefaultMargin, y: 0, width: cDefaultMargin * 4, height: cDevice_iPhoneTabBarHei)) deleteBtn.setImage( UIImage().BF_Image(named: "icon_blanc_back"), for: .normal) deleteBtn.tag = 1 deleteBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside) return deleteBtn }() lazy var nextBtn: UIButton = { let nextBtn = UIButton(frame: CGRect(x: 0, y: (cDevice_iPhoneTabBarHei - cDefaultMargin * 3) / 2, width: cDefaultMargin * 6, height: cDefaultMargin * 3)) nextBtn.addCorner(corner: 3) nextBtn.setTitle("下一步", for: .normal) nextBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .medium) nextBtn.tag = 3 nextBtn.addTarget(self, action: #selector(btnClick(sender:)), for: .touchUpInside) nextBtn.backgroundColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue) nextBtn.setTitleColor(.white, for: .normal) return nextBtn }() lazy var bottomView: UIView = { let bottomView = UIView(frame: CGRect(x: 0, y: cDefaultMargin * 2, width: cScreenWidth, height: cDevice_iPhoneNavBarHei)) bottomView.addSubview(selecteBtn) bottomView.backgroundColor = PQBFConfig.shared.styleBackGroundColor selecteBtn.center.y = nextBtn.center.y bottomView.addSubview(deleteBtn) bottomView.addSubview(nextBtn) nextBtn.frame.origin.x = bottomView.frame.width - nextBtn.frame.width - cDefaultMargin * 2 return bottomView }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white navHeadImageView?.backgroundColor = .white lineView?.backgroundColor = .white addSubViews() loadLocalData() } deinit { // PHPhotoLibrary.shared().unregisterChangeObserver(self) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) } override func viewWillAppear(_ animated: Bool) { super.viewDidAppear(animated) addPlayerItemObserver() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) removePlayerItemObserver() } func addSubViews() { view.addSubview(collectionView) navHeadImageView?.addSubview(bottomView) PQNotification.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil) } func loadLocalData() { let authStatus = PHPhotoLibrary.authorizationStatus() if authStatus == .notDetermined { // 第一次触发授权 alert PHPhotoLibrary.requestAuthorization { [weak self] (status: PHAuthorizationStatus) -> Void in if status == .authorized { if (self?.allPhotos == nil) || (self?.allPhotos.count ?? 0) <= 0 { self?.loadPhotoData() } if self?.backBtn != nil { self?.backBtn.removeFromSuperview() } } else { DispatchQueue.main.async { [weak self] in self?.emptyRemindView.isHidden = false self?.emptyRemindView.refreshBtn.isHidden = false self?.emptyRemindView.refreshBtn.setTitle("授予权限", for: .normal) if self?.backBtn.superview == nil { self?.view.addSubview((self?.backBtn)!) } else { self?.backBtn.isHidden = false } } } } } else if authStatus == .authorized { if allPhotos == nil || allPhotos.count <= 0 { loadPhotoData() } if backBtn.superview != nil { backBtn.removeFromSuperview() } } else { emptyRemindView.isHidden = false emptyRemindView.refreshBtn.isHidden = false emptyRemindView.refreshBtn.setTitle("授予权限", for: .normal) if backBtn.superview == nil { view.addSubview(backBtn) } else { backBtn.isHidden = false } } } func loadPhotoData() { DispatchQueue.main.async { [weak self] in PQLoadingHUB.shared.showHUB(superView: self!.view, isVerticality: false) } DispatchQueue.global().async { [weak self] in self?.allPhotos = PHAsset.fetchAssets(with: self?.fetchOptions) DispatchQueue.main.async { [weak self] in if self?.view != nil { PQLoadingHUB.shared.dismissHUB(superView: self!.view) } self?.collectionView.reloadData() if (self?.allPhotos.count ?? 0) <= 0 { self?.emptyRemindView.frame = CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei - cDevice_iPhoneTabBarHei) self?.emptyRemindView.emptyData = self?.emptyData self?.emptyRemindView.isHidden = false } else { self?.emptyRemindView.isHidden = true } if (self?.allPhotos.count ?? 0) > 0 { let tempData = PQUploadModel() tempData.title = "全部" tempData.categoryList = self!.allPhotos self?.categoryData.insert(tempData, at: 0) } self?.updateCachedAssets() } } PHPhotoLibrary.shared().register(self) DispatchQueue.global().async { [weak self] in self?.smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil) self?.smartAlbums?.enumerateObjects { [weak self] assCollection, _, _ in if assCollection.localizedTitle != "最近删除" { self?.convertCollection(collection: assCollection) } } self?.userCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil) self?.userCollections.enumerateObjects { assCollection, index, point in BFLog(message: "userCollections == \(assCollection),index = \(index),point = \(point)") if assCollection is PHAssetCollection { if assCollection.localizedTitle != "最近删除" { self?.convertCollection(collection: assCollection as? PHAssetCollection) } } } } } // 转化处理获取到的相簿 func convertCollection(collection: PHAssetCollection?) { if collection == nil { return } DispatchQueue.global().async { [weak self] in let assetsFetchResult = PHAsset.fetchAssets(in: collection!, options: self?.fetchOptions) if assetsFetchResult.count > 0 { let tempData = PQUploadModel() tempData.title = collection?.localizedTitle tempData.categoryList = assetsFetchResult if tempData.categoryList.count > 0 { self?.categoryData.append(tempData) } BFLog(message: "assetsFetchResult = \(assetsFetchResult)") } } } @objc func btnClick(sender: UIButton) { switch sender.tag { case 1: // 返回 navigationController?.popViewController(animated: true) case 2: // 筛选 showCollects() case 3: // 下一步 if selectedData == nil { cShowHUB(superView: nil, msg: isAssetImage ? "请选择图片" : "请选择视频") return } imageManager.requestImage(for: (selectedData?.asset)!, targetSize: itemSize, contentMode: .aspectFill, options: nil) { [weak self] image, _ in self?.selectedData?.image = image let vc = PQImageCropVC(image: (self?.selectedData?.image)!, aspectWidth: self?.videoWidth ?? 0.0, aspectHeight: self?.videoHeight ?? 0.0) vc.uploadData = self?.selectedData self?.navigationController?.pushViewController(vc, animated: true) } default: break } } @objc func showCollects() { if categoryData.count <= 0 { return } categoryCollectionView.reloadData() if categoryView.superview == nil { view.insertSubview(categoryView, belowSubview: navHeadImageView!) categoryView.addSubview(categoryCollectionView) showCategoryView() return } if categoryView.isHidden { showCategoryView() } else { cancelClick() } } @objc func showCategoryView() { categoryView.isHidden = false categoryView.alpha = 0 view.bringSubviewToFront(categoryView) view.bringSubviewToFront(bottomView) UIView.animate(withDuration: 0.3, animations: { self.categoryCollectionView.frame = CGRect(x: 0, y: 0, width: cScreenWidth, height: self.categoryH) self.categoryView.alpha = 1 }) { _ in } } @objc func cancelClick() { UIView.animate(withDuration: 0.3, animations: { self.categoryCollectionView.frame = CGRect(x: 0, y: -self.categoryH, width: cScreenWidth, height: self.categoryH) self.categoryView.alpha = 0 }) { _ in self.categoryView.isHidden = true } } @objc func didBecomeActiveNotification() { if isJumpToAuthorization { loadLocalData() isJumpToAuthorization = false } } } extension PQUploadController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection _: Int) -> Int { if collectionView == self.collectionView { return allPhotos == nil ? videoData.count : allPhotos.count } return categoryData.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if collectionView == self.collectionView { let cell = PQSelecteVideoItemCell.selecteVideoItemCell(collectionView: collectionView, indexPath: indexPath) if videoData.count <= indexPath.item, allPhotos != nil { let itemData = PQUploadModel() // itemData.image = UIImage.init(named: "cut_place") let asset = allPhotos.object(at: indexPath.item) itemData.asset = asset itemData.duration = asset.duration videoData.append(itemData) } if videoData.count > indexPath.item { let itemData = videoData[indexPath.item] cell.uploadData = itemData if itemData.image == nil, itemData.asset != nil { cell.representedAssetIdentifier = itemData.asset?.localIdentifier imageManager.requestImage(for: itemData.asset!, targetSize: itemSize, contentMode: .aspectFill, options: nil) { image, info in if info?.keys.contains("PHImageResultIsDegradedKey") ?? false, "\(info?["PHImageResultIsDegradedKey"] ?? "0")" == "0", cell.representedAssetIdentifier == itemData.asset?.localIdentifier { if image != nil { itemData.image = image cell.videoImageView.image = image } else if image == nil, info?.keys.contains("PHImageResultIsInCloudKey") ?? false { let option = PHImageRequestOptions() option.isNetworkAccessAllowed = true option.resizeMode = .fast self.imageManager.requestImageData(for: itemData.asset!, options: option) { data, _, _, _ in if data != nil { let image = UIImage(data: data!) itemData.image = image cell.videoImageView.image = image } } } } } } } else { cell.uploadData = PQUploadModel() } return cell } else { let cell = PQAssetCategoryCell.assetCategoryCell(collectionView: collectionView, indexPath: indexPath) let itemData = categoryData[indexPath.item] let asset = itemData.categoryList.object(at: 0) if itemData.image == nil { cell.representedAssetIdentifier = asset.localIdentifier imageManager.requestImage(for: asset, targetSize: itemSize, contentMode: .aspectFill, options: nil) { image, info in if info?.keys.contains("PHImageResultIsDegradedKey") ?? false, "\(info?["PHImageResultIsDegradedKey"] ?? "0")" == "0", cell.representedAssetIdentifier == asset.localIdentifier { itemData.image = image cell.uploadData = itemData } } } else { cell.uploadData = itemData } cell.isSelected = indexPath == catagerySelectedIndex return cell } } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if collectionView == self.collectionView { if videoData.count <= indexPath.item { return } let itemData = videoData[indexPath.item] let ratio = Float(itemData.asset?.pixelWidth ?? 0) / Float(itemData.asset?.pixelHeight ?? 1) if ratio < 0.4 || ratio > 4.2 { cShowHUB(superView: nil, msg: "暂不支持该比例的素材") return } if lastSeletedIndex != nil, lastSeletedIndex != indexPath { let lastData = videoData[lastSeletedIndex!.item] lastData.isSelected = false collectionView.reloadItems(at: [lastSeletedIndex!]) } lastSeletedIndex = indexPath if itemData.isSelected { itemData.isSelected = false selectedData = nil } else { itemData.isSelected = true let itemData = videoData[indexPath.item] selectedData = itemData } collectionView.reloadItems(at: [indexPath]) } else { videoData.removeAll() self.collectionView.setContentOffset(CGPoint.zero, animated: true) self.collectionView.reloadData() allPhotos = categoryData[indexPath.item].categoryList catagerySelectedIndex = indexPath selecteBtn.setTitle(categoryData[indexPath.item].title, for: .normal) selecteBtn.imagePosition(at: .right, space: cDefaultMargin / 2) lastSeletedIndex = nil if allPhotos.count <= 0 { emptyRemindView.frame = CGRect(x: 0, y: cDevice_iPhoneNavBarAndStatusBarHei, width: cScreenWidth, height: cScreenHeigth - cDevice_iPhoneNavBarAndStatusBarHei - cDevice_iPhoneTabBarHei) emptyRemindView.emptyData = emptyData emptyRemindView.isHidden = false } else { emptyRemindView.isHidden = true } self.collectionView.reloadData() cancelClick() } } func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, sizeForItemAt _: IndexPath) -> CGSize { if collectionView == self.collectionView { return CGSize(width: (cScreenWidth - cDefaultMargin) / 3, height: (cScreenWidth - cDefaultMargin) / 3) } return CGSize(width: collectionView.frame.width, height: cDefaultMargin * 8) } func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, minimumLineSpacingForSectionAt _: Int) -> CGFloat { if collectionView == self.collectionView { return cDefaultMargin / 2 } return 0 } func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, minimumInteritemSpacingForSectionAt _: Int) -> CGFloat { if collectionView == self.collectionView { return cDefaultMargin / 2 } return 0 } func scrollViewDidScroll(_ scrollView: UIScrollView) { //这里是不是有用的? // if currentController() is PQUploadController || currentController() is PQImageSelectedController { // if scrollView == collectionView { // updateCachedAssets() // } else { // BFLog(message: "contentOffset = \(scrollView.contentOffset),contentSize = \(scrollView.contentSize)") // if scrollView.contentOffset.y > ((scrollView.contentSize.height - scrollView.frame.height) + cDefaultMargin * 10) { // cancelClick() // } // } // } } } extension PQUploadController: PHPhotoLibraryChangeObserver { func photoLibraryDidChange(_ changeInstance: PHChange) { // Change notifications may be made on a background queue. Re-dispatch to the // main queue before acting on the change as we'll be updating the UI. DispatchQueue.main.sync { [weak self] in // Check each of the three top-level fetches for changes. if allPhotos != nil, changeInstance.changeDetails(for: allPhotos) != nil { self?.categoryData.removeAll() self?.loadPhotoData() } } } } extension PQUploadController { /// 添加监听 /// - Parameter playerItem: <#playerItem description#> /// - Returns: <#description#> func addPlayerItemObserver() { playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil) } /// 移除监听 /// - Returns: <#description#> func removePlayerItemObserver() { playerItem?.removeObserver(self, forKeyPath: "status") } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) { if object is AVPlayerItem, keyPath == "status" { BFLog(message: "(object as! AVPlayerItem).status = \((object as! AVPlayerItem).status.rawValue)") PQLoadingHUB.shared.dismissHUB(superView: playerHeaderView) switch (object as! AVPlayerItem).status { case .unknown: break case .readyToPlay: break case .failed: break default: break } } } private func resetCachedAssets() { imageManager.stopCachingImagesForAllAssets() previousPreheatRect = .zero } private func updateCachedAssets() { if allPhotos != nil && allPhotos.count <= 0 { return } guard isViewLoaded, view.window != nil else { return } let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) let preheatRect = visibleRect.insetBy(dx: 0, dy: -0.5 * visibleRect.height) let delta = abs(preheatRect.midY - previousPreheatRect.midY) guard delta > view.bounds.height / 3 else { return } let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect) let addedAssets = addedRects .flatMap { rect in collectionView.indexPathsForElements(in: rect) } .map { indexPath in allPhotos.object(at: indexPath.item) } let removedAssets = removedRects .flatMap { rect in collectionView.indexPathsForElements(in: rect) } .map { indexPath in allPhotos.object(at: indexPath.item) } imageManager.startCachingImages(for: addedAssets, targetSize: itemSize, contentMode: .aspectFill, options: nil) imageManager.stopCachingImages(for: removedAssets, targetSize: itemSize, contentMode: .aspectFill, options: nil) previousPreheatRect = preheatRect } private func differencesBetweenRects(_ old: CGRect, _ new: CGRect) -> (added: [CGRect], removed: [CGRect]) { if old.intersects(new) { var added = [CGRect]() if new.maxY > old.maxY { added += [CGRect(x: new.origin.x, y: old.maxY, width: new.width, height: new.maxY - old.maxY)] } if old.minY > new.minY { added += [CGRect(x: new.origin.x, y: new.minY, width: new.width, height: old.minY - new.minY)] } var removed = [CGRect]() if new.maxY < old.maxY { removed += [CGRect(x: new.origin.x, y: new.maxY, width: new.width, height: old.maxY - new.maxY)] } if old.minY < new.minY { removed += [CGRect(x: new.origin.x, y: old.minY, width: new.width, height: new.minY - old.minY)] } return (added, removed) } else { return ([new], [old]) } } }