TSLiveWallpaperBrowseVC.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. //
  2. // TSLiveWallpaperBrowseVC.swift
  3. // TSLiveWallpaper
  4. //
  5. // Created by 100Years on 2024/12/24.
  6. //
  7. import Photos
  8. import PhotosUI
  9. private let topLineH = k_Height_statusBar()
  10. class TSLiveWallpaperBrowseItemModel {
  11. var style:ImageDataStyple = .homeLiveList
  12. var imageUrl:String = ""
  13. var videoUrl:String = ""
  14. var livePhoto:PHLivePhoto? = nil
  15. var livePhotoResources:(pairedImage: URL, pairedVideo: URL)?
  16. }
  17. class TSLiveWallpaperBrowseVC: TSBaseVC {
  18. private var isPanningDown: Bool?
  19. lazy var isPreview = false {
  20. didSet {
  21. self.previewView.isHidden = !isPreview
  22. self.btnsAllView.isHidden = isPreview
  23. }
  24. }
  25. private var dataModelArray = [TSLiveWallpaperBrowseItemModel]()
  26. var currentIndex:Int
  27. init(itemModels: [TSImageDataItemModel],currentIndex:Int) {
  28. self.currentIndex = currentIndex
  29. for itemModel in itemModels {
  30. let model = TSLiveWallpaperBrowseItemModel()
  31. model.style = itemModel.style
  32. model.imageUrl = itemModel.imageUrl
  33. model.videoUrl = itemModel.videoUrl
  34. dataModelArray.append(model)
  35. }
  36. super.init()
  37. }
  38. @MainActor required init?(coder: NSCoder) {
  39. fatalError("init(coder:) has not been implemented")
  40. }
  41. lazy var backBtn: UIButton = {
  42. return UIButton.createButton(image: UIImage(named: "navi_back_white"),backgroundColor: UIColor.fromHex("#111111", alpha: 0.2),corner: 16.0) { [weak self] in
  43. self?.pop()
  44. }
  45. }()
  46. //MARK: btnsAllView
  47. lazy var saveBtn: UIButton = {
  48. let saveBtn = TSViewTool.createNormalSubmitBtn(title: "save".localized) { [weak self] in
  49. guard let self = self else { return }
  50. if let cell = collectionView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? TSLiveWallpaperBrowseCell {
  51. cell.saveLivePhoto { [weak self] success in
  52. guard let self = self else { return }
  53. if success {
  54. kSavePhotoSuccesswShared.show(atView: self.view)
  55. }else{
  56. TSToastShared.showToast(message: "Save Fail".localized)
  57. }
  58. }
  59. }
  60. }
  61. saveBtn.cornerRadius = 24
  62. return saveBtn
  63. }()
  64. lazy var btnsAllView: UIView = {
  65. let btnsAllView = UIView()
  66. //版权信息按钮
  67. let copyrightBtn = UIButton.createButton(image: UIImage(named: "info_white"),backgroundColor: UIColor.fromHex("#111111", alpha: 0.2),corner: 16.0) { [weak self] in
  68. guard let self = self else { return }
  69. navigationController?.pushViewController(TSLiveWallpaperCopyrightVC(), animated: true)
  70. }
  71. btnsAllView.addSubview(copyrightBtn)
  72. copyrightBtn.snp.makeConstraints { make in
  73. make.width.height.equalTo(44)
  74. make.trailing.equalTo(-16)
  75. make.top.equalTo(topLineH)
  76. }
  77. //预览按钮
  78. let previewBtn = UIButton.createButton(image: UIImage(named: "random_preview"),backgroundColor: UIColor.fromHex("#000000", alpha: 0.5),corner: 24) { [weak self] in
  79. guard let self = self else { return }
  80. self.isPreview = !self.isPreview
  81. }
  82. btnsAllView.addSubview(previewBtn)
  83. previewBtn.snp.makeConstraints { make in
  84. make.trailing.equalTo(-42)
  85. make.bottom.equalTo(-16-k_Height_safeAreaInsetsBottom())
  86. make.width.height.equalTo(48)
  87. }
  88. btnsAllView.addSubview(saveBtn)
  89. saveBtn.snp.makeConstraints { make in
  90. make.trailing.equalTo(previewBtn.snp.leading).offset(-18)
  91. make.leading.equalTo(42)
  92. make.bottom.equalTo(-16-k_Height_safeAreaInsetsBottom())
  93. make.height.equalTo(48)
  94. }
  95. return btnsAllView
  96. }()
  97. //MARK: previewView
  98. lazy var previewView: UIView = {
  99. let previewView = UIView()
  100. previewView.isHidden = true
  101. let imageView = UIImageView.createImageView(imageName:"iPhone_lock_screen_preview")
  102. previewView.addSubview(imageView)
  103. imageView.snp.makeConstraints { make in
  104. make.edges.equalToSuperview()
  105. }
  106. let tap = UITapGestureRecognizer(target: self, action: #selector(onPreviewButton))
  107. previewView.addGestureRecognizer(tap)
  108. return previewView
  109. }()
  110. lazy var collectionView: UICollectionView = {
  111. let collectionView = UICollectionView.createCommon(delegate: self, cellReuseIds: ["TSLiveWallpaperBrowseCell"])
  112. if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
  113. flowLayout.minimumInteritemSpacing = 0
  114. flowLayout.minimumLineSpacing = 0
  115. flowLayout.itemSize = UIScreen.main.bounds.size
  116. flowLayout.scrollDirection = .horizontal
  117. }
  118. return collectionView
  119. }()
  120. override func createView() {
  121. setNavBarViewHidden(true)
  122. view.backgroundColor = .clear
  123. contentView.addSubview(collectionView)
  124. contentView.addSubview(btnsAllView)
  125. contentView.addSubview(previewView)
  126. contentView.addSubview(backBtn)
  127. collectionView.snp.makeConstraints { make in
  128. make.edges.equalToSuperview()
  129. }
  130. btnsAllView.snp.makeConstraints { make in
  131. make.edges.equalToSuperview()
  132. }
  133. previewView.snp.makeConstraints { make in
  134. make.edges.equalToSuperview()
  135. }
  136. backBtn.snp.makeConstraints { make in
  137. make.leading.equalTo(16)
  138. make.top.equalTo(topLineH)
  139. make.width.height.equalTo(44)
  140. }
  141. let pan = UIPanGestureRecognizer(target: self, action: #selector(onPanGesture(_:)))
  142. view.addGestureRecognizer(pan)
  143. kDelayMainShort {
  144. self.collectionView.setContentOffset(CGPoint(x: CGFloat(self.currentIndex) * self.collectionView.frame.size.width, y: 0), animated: false)
  145. }
  146. }
  147. }
  148. //MARK: 点击操作
  149. extension TSLiveWallpaperBrowseVC{
  150. @objc func onPreviewButton() {
  151. isPreview = !isPreview
  152. }
  153. @objc func onPanGesture(_ pan: UIPanGestureRecognizer) {
  154. let trans = pan.translation(in: self.view)
  155. let velocity = pan.velocity(in: nil)
  156. switch pan.state {
  157. case .began:
  158. if abs(trans.x) > abs(trans.y) {
  159. isPanningDown = false
  160. }
  161. else if trans.y > 0 {
  162. isPanningDown = true
  163. }
  164. case .changed:
  165. switch isPanningDown {
  166. case .none:
  167. if abs(trans.x) > abs(trans.y) {
  168. isPanningDown = false
  169. }
  170. else if trans.y > 0 {
  171. isPanningDown = true
  172. }
  173. case .some(true):
  174. var viewTrans = self.view.transform
  175. viewTrans = viewTrans.translatedBy(x: 0, y: trans.y)
  176. if viewTrans.ty >= 0 {
  177. self.view.transform = viewTrans
  178. }
  179. case .some(false):
  180. let newOffsetX = self.collectionView.contentOffset.x - trans.x
  181. if newOffsetX >= 0 &&
  182. newOffsetX <= (self.collectionView.contentSize.width - self.collectionView.bounds.width) {
  183. self.collectionView.contentOffset.x -= trans.x
  184. }
  185. }
  186. pan.setTranslation(.zero, in: pan.view)
  187. case .ended:
  188. switch isPanningDown {
  189. case .none:
  190. debugPrint("no thing to do ")
  191. self.view.transform = .identity
  192. case .some(true):
  193. if self.view.transform.ty > 80 ||
  194. velocity.y >= 500 {
  195. UIView.animate(withDuration: 0.2) { [weak self] in
  196. self?.view.transform.ty = k_ScreenHeight
  197. } completion: { [weak self] finished in
  198. if finished {
  199. self?.dismiss(animated: false)
  200. }
  201. }
  202. }
  203. else {
  204. self.view.transform = .identity
  205. }
  206. case .some(false):
  207. let velocity = pan.velocity(in: pan.view)
  208. let page: CGFloat
  209. if velocity.x >= 500 {
  210. page = (collectionView.contentOffset.x / collectionView.bounds.width).rounded(.down)
  211. }
  212. else if velocity.x <= 500 {
  213. page = (collectionView.contentOffset.x / collectionView.bounds.width).rounded(.up)
  214. }
  215. else {
  216. page = (collectionView.contentOffset.x / collectionView.bounds.width).rounded()
  217. }
  218. let newOffsetX = page * collectionView.bounds.width
  219. collectionView.setContentOffset(CGPoint(x: newOffsetX, y: 0), animated: true)
  220. }
  221. isPanningDown = nil
  222. case .cancelled, .failed:
  223. self.view.transform = CGAffineTransform.identity
  224. isPanningDown = nil
  225. default:
  226. debugPrint(pan.state)
  227. debugPrint(pan.translation(in: self.view))
  228. }
  229. }
  230. }
  231. //MARK: UICollectionViewDataSource
  232. extension TSLiveWallpaperBrowseVC:UICollectionViewDataSource,UICollectionViewDelegate {
  233. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  234. resetIndexWithOffset(scrollView)
  235. }
  236. func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
  237. resetIndexWithOffset(scrollView)
  238. }
  239. private func resetIndexWithOffset(_ scrollView: UIScrollView) {
  240. let item = Int((scrollView.contentOffset.x / scrollView.bounds.width).rounded())
  241. currentIndex = item
  242. }
  243. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  244. return dataModelArray.count
  245. }
  246. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  247. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TSLiveWallpaperBrowseCell", for: indexPath) as! TSLiveWallpaperBrowseCell
  248. debugPrint("collectionView cellForItemAt=\(indexPath)")
  249. if let wallpaperModel = dataModelArray.safeObj(At: indexPath.item){
  250. cell.itemModel = wallpaperModel
  251. }
  252. return cell
  253. }
  254. func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  255. debugPrint("collectionView didEndDisplaying=\(indexPath)")
  256. if let cell = cell as? TSLiveWallpaperBrowseCell {
  257. cell.stopPlayLive()
  258. }
  259. }
  260. func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){
  261. debugPrint("collectionView willDisplay=\(indexPath)")
  262. if let cell = cell as? TSLiveWallpaperBrowseCell {
  263. cell.stratPlayLive()
  264. }
  265. }
  266. }
  267. class TSLiveWallpaperBrowseCell : TSBaseCollectionCell,PHLivePhotoViewDelegate{
  268. private let showImageViewW = k_ScreenWidth - 32
  269. private let livePhotoTool = LivePhoto()
  270. lazy var livePhotoView: PHLivePhotoView = {
  271. let liveIv = PHLivePhotoView()
  272. liveIv.delegate = self
  273. liveIv.contentMode = .scaleAspectFill
  274. liveIv.isHidden = true
  275. return liveIv
  276. }()
  277. lazy var loading: UIActivityIndicatorView = {
  278. let loading = UIActivityIndicatorView(style: .large)
  279. loading.hidesWhenStopped = true
  280. loading.color = .white
  281. return loading
  282. }()
  283. var itemModel:TSLiveWallpaperBrowseItemModel? {
  284. didSet {
  285. if let livePhoto = itemModel?.livePhoto {
  286. self.livePhotoView.livePhoto = livePhoto
  287. self.livePhotoView.isHidden = false
  288. self.livePhotoView.startPlayback(with: .full)
  289. return
  290. }
  291. if let wallpaperModel = itemModel, wallpaperModel.imageUrl.count > 0 {
  292. loading.startAnimating()
  293. var imageCachePath = ""
  294. var videoCachePath = ""
  295. let group = DispatchGroup()
  296. group.enter()
  297. TSCommonTool.downloadAndCacheFile(from: wallpaperModel.imageUrl,fileEx: "jpeg") { path, error in
  298. if let path = path { imageCachePath = path }
  299. group.leave()
  300. }
  301. group.enter()
  302. TSCommonTool.downloadAndCacheFile(from: wallpaperModel.videoUrl,fileEx: "mov") { path, error in
  303. if let path = path { videoCachePath = path }
  304. group.leave()
  305. }
  306. group.notify(queue: .main) { [self] in
  307. loading.stopAnimating()
  308. if imageCachePath.count == 0 || videoCachePath.count == 0 {
  309. return
  310. }
  311. let imageCacheUrl = URL(fileURLWithPath: imageCachePath)
  312. let videoCacheUrl = URL(fileURLWithPath: videoCachePath)
  313. livePhotoTool.generate(from: imageCacheUrl, videoURL: videoCacheUrl, progress: { (percent) in
  314. debugPrint(percent)
  315. }) { [weak self] (livePhoto, resources) in
  316. guard let self = self else { return }
  317. itemModel?.livePhoto = livePhoto
  318. itemModel?.livePhotoResources = resources
  319. if let livePhoto = livePhoto {
  320. self.livePhotoView.livePhoto = livePhoto
  321. self.livePhotoView.isHidden = false
  322. self.livePhotoView.startPlayback(with: .full)
  323. }else{
  324. debugPrint("livePhoto.generate fail")
  325. }
  326. }
  327. }
  328. }
  329. }
  330. }
  331. override func creatUI() {
  332. self.backgroundColor = UIColor.clear
  333. self.contentView.backgroundColor = UIColor.black
  334. contentView.addSubview(livePhotoView)
  335. livePhotoView.snp.makeConstraints { make in
  336. make.edges.equalToSuperview()
  337. }
  338. contentView.addSubview(loading)
  339. loading.snp.makeConstraints { make in
  340. make.center.equalToSuperview()
  341. }
  342. }
  343. func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
  344. if !livePhotoView.isHidden {
  345. kDelayOnMainThread(1.0) {
  346. livePhotoView.startPlayback(with: PHLivePhotoViewPlaybackStyle.full)
  347. }
  348. }
  349. }
  350. func saveLivePhoto(completion: @escaping (Bool) -> Void){
  351. if let resources = itemModel?.livePhotoResources {
  352. LivePhoto.saveToLibrary(resources, completion: { (success) in
  353. kExecuteOnMainThread {
  354. if success {
  355. debugPrint("Live Photo Saved,The live photo was successfully saved to Photos.")
  356. completion(true)
  357. }else {
  358. debugPrint("Live Photo Not Saved,The live photo was not saved to Photos.")
  359. completion(false)
  360. }
  361. }
  362. })
  363. }
  364. }
  365. func stopPlayLive() {
  366. livePhotoView.isHidden = true
  367. livePhotoView.stopPlayback()
  368. }
  369. func stratPlayLive() {
  370. self.livePhotoView.isHidden = false
  371. self.livePhotoView.startPlayback(with: .full)
  372. }
  373. }