INVideoExportController.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. //
  2. // INVideoExportController.swift
  3. // Introduce
  4. //
  5. // Created by 胡志强 on 2021/11/29.
  6. //
  7. import BFAnalyzeKit
  8. import BFCommonKit
  9. import BFRecordScreenKit
  10. import BFUIKit
  11. import Foundation
  12. import Photos
  13. import SwiftUI
  14. import UIKit
  15. class INVideoExportController: BFBaseViewController {
  16. var avplayerTimeObserver: NSKeyValueObservation?
  17. let backV = UIView()
  18. var hasExportAll = false
  19. var hasExportOnly = false
  20. var hasSaveAll = false
  21. var hasSaveOnly = false
  22. var saveAllUlr = URL(fileURLWithPath: "aaa")
  23. var saveOnlyUlr = URL(fileURLWithPath: "aaa")
  24. var playViewFrame: CGRect = CGRect.zero
  25. var isExporting = false {
  26. didSet {
  27. if isExporting {
  28. avplayer.pause()
  29. }
  30. }
  31. }
  32. // 预览播放进度
  33. var sliderView: BFVideoPlayerSliderView?
  34. // 合成进度指示条
  35. lazy var progressView: UIView = {
  36. let v = UIView()
  37. v.backgroundColor = ThemeStyleColor
  38. return v
  39. }()
  40. // 合成进度百分比lable
  41. lazy var progressL: UILabel = {
  42. let la = UILabel()
  43. la.textColor = .white
  44. la.textAlignment = .center
  45. la.text = "0%"
  46. la.font = UIFont.systemFont(ofSize: 28)
  47. la.shadowColor = .black
  48. la.shadowOffset = CGSize(width: 1, height: 1)
  49. return la
  50. }()
  51. lazy var saveAllBtn: UIButton = {
  52. let btn = UIButton()
  53. btn.setImage(UIImage(named: "export_saveall_n"), for: .normal)
  54. btn.setImage(UIImage(named: "export_saveall_h"), for: .selected)
  55. btn.addTarget(self, action: #selector(saveAllAction(btn:)), for: .touchUpInside)
  56. return btn
  57. }()
  58. lazy var saveOnlyBtn: UIButton = {
  59. let btn = UIButton()
  60. btn.setImage(UIImage(named: "export_saveonly_n"), for: .normal)
  61. btn.setImage(UIImage(named: "export_saveonly_h"), for: .selected)
  62. btn.addTarget(self, action: #selector(saveOnlyAction(btn:)), for: .touchUpInside)
  63. return btn
  64. }()
  65. lazy var completeBtn: UIButton = {
  66. let btn = UIButton()
  67. btn.setTitle("完成", for: .normal)
  68. // btn.setTitleColor(.white, for: .normal)
  69. btn.setTitleColor(UIColor.hexColor(hexadecimal: "#B1B1B1"), for: .normal)
  70. btn.setTitleColor(.gray, for: .disabled)
  71. btn.addTarget(self, action: #selector(completeAction), for: .touchUpInside)
  72. return btn
  73. }()
  74. lazy var saveToPhotoBtn: UIButton = {
  75. let btn = UIButton()
  76. btn.setTitle(" 保存到相册", for: .normal)
  77. btn.setTitleColor(.white, for: .normal)
  78. btn.setTitleColor(.lightGray, for: .disabled)
  79. btn.setImage(UIImage(named: "export_btn"), for: .normal)
  80. btn.backgroundColor = ThemeStyleColor
  81. btn.addCorner(roundingCorners: .allCorners, corner: 8)
  82. btn.addTarget(self, action: #selector(saveToPhotoNow), for: .touchUpInside)
  83. return btn
  84. }()
  85. lazy var bottomView: UIView = {
  86. let vv = UIView()
  87. vv.backgroundColor = .black
  88. // vv.isHidden = true
  89. return vv
  90. }()
  91. lazy var errorView : UIView = {
  92. let backV = UIView()
  93. backV.backgroundColor = .black
  94. backV.isHidden = true
  95. let titleL = UILabel()
  96. titleL.text = "合成失败"
  97. titleL.font = UIFont.systemFont(ofSize: 36)
  98. titleL.textColor = .white
  99. titleL.textAlignment = .center
  100. backV.addSubview(titleL)
  101. let subTitleL = UILabel()
  102. subTitleL.text = "请重新尝试"
  103. subTitleL.tag = 33000
  104. subTitleL.font = UIFont.systemFont(ofSize: 36)
  105. subTitleL.textColor = UIColor.hexColor(hexadecimal: "#A6A6A6")
  106. subTitleL.textAlignment = .center
  107. backV.addSubview(subTitleL)
  108. let retryBtn = UIButton()
  109. retryBtn.backgroundColor = ThemeStyleColor
  110. retryBtn.addCorner(corner:5)
  111. retryBtn.addTarget(self, action: #selector(retryAction), for: .touchUpInside)
  112. backV.addSubview(retryBtn)
  113. retryBtn.snp.makeConstraints { make in
  114. make.width.equalTo(245)
  115. make.height.equalTo(50)
  116. make.center.equalToSuperview()
  117. }
  118. subTitleL.snp.makeConstraints { make in
  119. make.left.right.equalToSuperview()
  120. make.height.equalTo(40)
  121. make.bottom.equalTo(retryBtn.snp.top).offset(30)
  122. }
  123. titleL.snp.makeConstraints { make in
  124. make.left.right.equalToSuperview()
  125. make.height.equalTo(70)
  126. make.bottom.equalTo(subTitleL.snp.top)
  127. }
  128. return backV
  129. }()
  130. var playerLayer: AVPlayerLayer!
  131. lazy var avplayer: AVPlayer = {
  132. let avplayer = AVPlayer()
  133. avplayerTimeObserver = avplayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 100), queue: DispatchQueue.global()) { [weak self, weak avplayer] _ in
  134. // 进度监控
  135. if let item = avplayer?.currentItem, !(self?.sliderView?.isDragingProgressSlder ?? false) {
  136. DispatchQueue.main.async { [weak self, weak avplayer] in
  137. if avplayer?.currentItem?.status != .readyToPlay {
  138. self?.sliderView?.progress = 0
  139. }else{
  140. self?.sliderView?.progress = CMTimeGetSeconds(item.currentTime()) / CMTimeGetSeconds(item.duration)
  141. }
  142. }
  143. }
  144. } as? NSKeyValueObservation
  145. NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: avplayer.currentItem, queue: .main) { [weak avplayer, weak self] _ in
  146. avplayer?.seek(to: CMTime.zero)
  147. self?.sliderView?.playEnd()
  148. self?.changeToOriginalFrame()
  149. }
  150. return avplayer
  151. }()
  152. lazy var export: BFRecordExport = {
  153. let export = BFRecordExport()
  154. export.progress = { [weak self] progress in
  155. DispatchQueue.main.async { [weak self] in
  156. if let sself = self {
  157. // let width = export.data?.first?.width ?? 0
  158. sself.progressL.text = String(format: "%d%%", Int(progress * 100))
  159. sself.progressView.snp.updateConstraints { make in
  160. make.width.equalTo(cScreenWidth * CGFloat(progress))
  161. }
  162. }
  163. }
  164. }
  165. export.exportCompletion = { [weak self] error, url in
  166. DispatchQueue.main.async { [weak self] in
  167. guard let sself = self else {
  168. return
  169. }
  170. UIApplication.shared.isIdleTimerDisabled = false
  171. sself.isExporting = false
  172. // sself.bottomView.isHidden = false
  173. sself.saveAllBtn.isEnabled = true
  174. sself.saveOnlyBtn.isEnabled = true
  175. sself.saveToPhotoBtn.isEnabled = true
  176. sself.completeBtn.isEnabled = true
  177. sself.sliderView?.isHidden = false
  178. sself.progressView.isHidden = true
  179. sself.progressL.isHidden = true
  180. if let fileUrl = url {
  181. let item = AVPlayerItem(url: fileUrl)
  182. sself.avplayer.replaceCurrentItem(with: item)
  183. sself.avplayer.play()
  184. if sself.saveAllBtn.isSelected {
  185. sself.saveAllUlr = fileUrl
  186. sself.hasExportAll = true
  187. }
  188. if sself.saveOnlyBtn.isSelected {
  189. sself.saveOnlyUlr = fileUrl
  190. sself.hasExportOnly = true
  191. }
  192. // 添加播放进度视图
  193. self?.addVideoSliderView()
  194. }else {
  195. if let l = sself.errorView.viewWithTag(33000) as? UILabel, let err = error as NSError? {
  196. switch err.code {
  197. case ExportError.FileNotExist.rawValue :
  198. l.text = "原视频/图片文件被删除"
  199. case ExportError.DiskNoSpace.rawValue :
  200. l.text = "手机存储空间不足"
  201. case ExportError.VoiceLost.rawValue :
  202. l.text = "手机存储空间不足"
  203. default:
  204. l.text = "请重新尝试"
  205. }
  206. }
  207. sself.errorView.isHidden = false
  208. }
  209. }
  210. // 合成成功上报
  211. BFEventTrackAdaptor.baseReportUpload(businessType: nil, objectType: .ot_composeSuccess, pageSource: .sp_composePage, commonParams: commonParams())
  212. }
  213. return export
  214. }()
  215. deinit {
  216. avplayerTimeObserver?.invalidate()
  217. NotificationCenter.default.removeObserver(self)
  218. }
  219. override func viewWillAppear(_ animated: Bool) {
  220. super.viewWillAppear(animated)
  221. showNavigation()
  222. // 曝光上报
  223. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_pageView, objectType: nil, pageSource: .sp_composePage, commonParams: commonParams())
  224. }
  225. override func viewWillDisappear(_ animated: Bool) {
  226. super.viewWillDisappear(animated)
  227. UIApplication.shared.isIdleTimerDisabled = false
  228. }
  229. override func backBtnClick() {
  230. if isExporting {
  231. whetherCancelExport {
  232. self.export.cancelExport()
  233. super.backBtnClick()
  234. }
  235. return
  236. }
  237. if !(hasExportAll || hasExportOnly) {}
  238. export.cancelExport()
  239. super.backBtnClick()
  240. }
  241. override func viewDidLoad() {
  242. super.viewDidLoad()
  243. view.backgroundColor = .black
  244. navHeadImageView?.backgroundColor = .black
  245. leftButton(image: nil, imageName: nil, tintColor: UIColor.white)
  246. // backV.frame = CGRect(x: 0, y: navHeadImageView?.bottomY ?? 0, width: cScreenWidth, height: cScreenWidth)
  247. backV.backgroundColor = .black
  248. // backV.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(play)))
  249. addSubviews()
  250. // export.startExprot()
  251. // 默认保留录音合成
  252. saveAllBtn.isSelected = true
  253. DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
  254. self?.exportNow()
  255. }
  256. }
  257. func addSubviews() {
  258. view.addSubview(bottomView)
  259. view.addSubview(backV)
  260. playerLayer = AVPlayerLayer(player: avplayer)
  261. backV.layer.addSublayer(playerLayer)
  262. backV.addSubview(progressView)
  263. backV.addSubview(progressL)
  264. backV.addSubview(errorView)
  265. bottomView.addSubview(saveAllBtn)
  266. bottomView.addSubview(saveOnlyBtn)
  267. bottomView.addSubview(saveToPhotoBtn)
  268. bottomView.addSubview(completeBtn)
  269. progressView.snp.makeConstraints { make in
  270. make.left.top.height.equalToSuperview()
  271. make.width.equalTo(0)
  272. }
  273. progressL.snp.makeConstraints { make in
  274. make.center.width.equalToSuperview()
  275. make.height.equalTo(28)
  276. }
  277. bottomView.snp.makeConstraints { make in
  278. make.left.right.equalToSuperview()
  279. if #available(iOS 11.0, *) {
  280. make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
  281. } else {
  282. make.bottom.equalToSuperview()
  283. }
  284. }
  285. completeBtn.snp.makeConstraints { make in
  286. make.left.equalTo(16)
  287. make.right.equalTo(-16)
  288. make.bottom.equalTo(-1 * cSafeAreaHeight - 3)
  289. make.height.equalTo(20)
  290. }
  291. saveToPhotoBtn.snp.makeConstraints { make in
  292. make.left.right.equalTo(completeBtn)
  293. make.height.equalTo(50)
  294. make.bottom.equalTo(completeBtn.snp.top).offset(-10)
  295. }
  296. saveAllBtn.snp.makeConstraints { make in
  297. make.left.equalTo(completeBtn)
  298. make.bottom.equalTo(saveToPhotoBtn.snp.top).offset(-21)
  299. make.height.equalTo(70)
  300. make.top.equalTo(28)
  301. }
  302. saveOnlyBtn.snp.makeConstraints { make in
  303. make.left.equalTo(saveAllBtn.snp.right).offset(16)
  304. make.top.height.width.equalTo(saveAllBtn)
  305. make.right.equalTo(completeBtn)
  306. }
  307. DispatchQueue.main.asyncAfter(deadline: .now() + 0) { [weak self] in
  308. self?.backV.frame = CGRect(x: 0, y: self?.navHeadImageView?.frame.maxY ?? 0, width: self?.view.width ?? 0, height: (self?.bottomView.frame.minY ?? 0) - (self?.navHeadImageView?.frame.maxY ?? 0))
  309. self?.playerLayer.frame = self!.backV.bounds
  310. }
  311. }
  312. /// 添加播放进度视图
  313. func addVideoSliderView() {
  314. if sliderView == nil {
  315. sliderView = BFVideoPlayerSliderView(frame: CGRect(x: 0, y: backV.frame.height - 60, width: backV.frame.width, height: 50))
  316. sliderView?.valueChangeBloc = { [weak self] sender in
  317. let cmtime = CMTime(value: CMTimeValue(Float64(sender.value) * Float64(self?.avplayer.currentItem?.asset.duration.seconds ?? 0) * 1000.0), timescale: CMTimeScale(1000.0))
  318. BFLog(message: "cmtime == \(cmtime),\(cmtime.seconds)")
  319. self?.avplayer.seek(to: cmtime, toleranceBefore: CMTime(seconds: 1, preferredTimescale: 1000), toleranceAfter: CMTime(seconds: 1, preferredTimescale: 1000))
  320. }
  321. sliderView?.btnClickBloc = { [weak self] sender in
  322. // 按钮点击
  323. if sender.tag == 1 {
  324. self?.play(sender: sender)
  325. } else if sender.tag == 2 {
  326. if sender.isSelected {
  327. self?.changeToFullScreen()
  328. } else {
  329. self?.changeToOriginalFrame()
  330. }
  331. }
  332. }
  333. }
  334. sliderView?.duration = avplayer.currentItem?.asset.duration.seconds ?? 0
  335. if sliderView?.superview == nil {
  336. backV.addSubview(sliderView!)
  337. }
  338. }
  339. // MARK: - 按钮事件
  340. @objc func retryAction(){
  341. exportNow()
  342. }
  343. func exportNow() {
  344. if isExporting {
  345. cShowHUB(superView: nil, msg: "正在合成中。。。")
  346. return
  347. }
  348. isExporting = true
  349. // bottomView.isHidden = true
  350. saveAllBtn.isEnabled = false
  351. saveOnlyBtn.isEnabled = false
  352. saveToPhotoBtn.isEnabled = false
  353. completeBtn.isEnabled = false
  354. progressView.isHidden = false
  355. progressL.isHidden = false
  356. sliderView?.isHidden = true
  357. export.startExprot(synthesisAll: saveAllBtn.isSelected)
  358. UIApplication.shared.isIdleTimerDisabled = true
  359. // 开始合成上报
  360. BFEventTrackAdaptor.baseReportUpload(businessType: nil, objectType: .ot_startCompose, pageSource: .sp_composePage, commonParams: commonParams())
  361. }
  362. @objc func saveToPhotoNow() {
  363. if (saveAllBtn.isSelected && hasSaveAll) || saveOnlyBtn.isSelected && hasSaveOnly {
  364. cShowHUB(superView: nil, msg: "已保存过了")
  365. return
  366. }
  367. if let url = (avplayer.currentItem?.asset as? AVURLAsset)?.url {
  368. PHPhotoLibrary.shared().performChanges {
  369. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
  370. } completionHandler: { [weak self] isFinished, _ in
  371. guard let sself = self else {
  372. return
  373. }
  374. if isFinished {
  375. DispatchQueue.main.async {
  376. cShowHUB(superView: nil, msg: "保存成功")
  377. }
  378. if sself.saveAllBtn.isSelected {
  379. sself.hasSaveAll = true
  380. } else if sself.saveOnlyBtn.isSelected {
  381. sself.hasSaveOnly = true
  382. }
  383. }
  384. }
  385. }
  386. // 点击保存至相册上报
  387. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_saveToAblum, pageSource: .sp_composePage, extParams: saveAllBtn.isSelected ? ["saveAll": true] : ["saveRecord": true], commonParams: commonParams())
  388. }
  389. @objc func saveAllAction(btn: UIButton) {
  390. if btn.isSelected {
  391. return
  392. }
  393. btn.isSelected = true
  394. saveOnlyBtn.isSelected = false
  395. avplayer.pause()
  396. if !hasExportAll {
  397. exportNow()
  398. } else {
  399. avplayer.pause()
  400. let item = AVPlayerItem(url: saveAllUlr)
  401. avplayer.replaceCurrentItem(with: item)
  402. if let sbtn = sliderView?.viewWithTag(1) as? UIButton {
  403. sbtn.isSelected = false
  404. avplayer.play()
  405. sliderView?.duration = avplayer.currentItem?.asset.duration.seconds ?? 0
  406. }
  407. }
  408. }
  409. @objc func saveOnlyAction(btn: UIButton) {
  410. if btn.isSelected {
  411. return
  412. }
  413. btn.isSelected = true
  414. saveAllBtn.isSelected = false
  415. avplayer.pause()
  416. if !hasExportOnly {
  417. exportNow()
  418. } else {
  419. avplayer.pause()
  420. let item = AVPlayerItem(url: saveOnlyUlr)
  421. avplayer.replaceCurrentItem(with: item)
  422. if let sbtn = sliderView?.viewWithTag(1) as? UIButton {
  423. sbtn.isSelected = false
  424. avplayer.play()
  425. sliderView?.duration = avplayer.currentItem?.asset.duration.seconds ?? 0
  426. }
  427. }
  428. }
  429. @objc func completeAction() {
  430. // MARK: 删除所有录制资源. 现在放在了选择相册展示时清理cache
  431. if (!hasSaveOnly && saveOnlyUlr.absoluteString != "aaa")
  432. || (!hasSaveAll && saveAllUlr.absoluteString != "aaa")
  433. {
  434. let remindData = BFBaseModel()
  435. remindData.summary = "合成的视频尚未保存到相册"
  436. let alertV = BFRemindView(frame: view.bounds)
  437. alertV.isBanned = true
  438. alertV.confirmBtn.setTitle("不保存", for: .normal)
  439. alertV.cancelBtn.setTitle("确认保存", for: .normal)
  440. alertV.remindData = remindData
  441. alertV.remindBlock = { [weak self] item, _ in
  442. guard let sself = self else {
  443. return
  444. }
  445. if item.tag == 1 { // 确定返回到上一层
  446. if sself.hasExportOnly {
  447. PHPhotoLibrary.shared().performChanges {
  448. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: sself.saveOnlyUlr)
  449. } completionHandler: { _, _ in
  450. }
  451. }
  452. if sself.hasExportAll {
  453. PHPhotoLibrary.shared().performChanges {
  454. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: sself.saveAllUlr)
  455. } completionHandler: { _, _ in
  456. }
  457. }
  458. }
  459. sself.export.cancelExport()
  460. sself.navigationController?.popToRootViewController(animated: true)
  461. }
  462. UIApplication.shared.keyWindow?.addSubview(alertV)
  463. } else {
  464. export.cancelExport()
  465. navigationController?.popToRootViewController(animated: true)
  466. }
  467. }
  468. @objc func play(sender: UIButton) {
  469. if isExporting {
  470. avplayer.pause()
  471. return
  472. }
  473. if avplayer.currentItem != nil {
  474. if avplayer.timeControlStatus == .playing || sender.isSelected {
  475. avplayer.pause()
  476. } else if avplayer.timeControlStatus == .paused {
  477. avplayer.play()
  478. }
  479. }
  480. }
  481. func whetherCancelExport(comfirm: (() -> Void)?) {
  482. let remindData = BFBaseModel()
  483. remindData.summary = "正在合成中,是否取消?"
  484. let alertV = BFRemindView(frame: view.bounds)
  485. alertV.isBanned = true
  486. alertV.confirmBtn.setTitle("继续合成", for: .normal)
  487. alertV.cancelBtn.setTitle("取消合成", for: .normal)
  488. alertV.remindData = remindData
  489. alertV.remindBlock = { [weak self] item, _ in
  490. guard let sself = self else {
  491. return
  492. }
  493. if item.tag == 1 { // 确定返回到上一层
  494. sself.export.cancelExport()
  495. comfirm?()
  496. }
  497. }
  498. UIApplication.shared.keyWindow?.addSubview(alertV)
  499. }
  500. }
  501. extension INVideoExportController {
  502. @objc func changeToOriginalFrame() {
  503. if !(sliderView?.isFullScreen ?? false) {
  504. isHiddenStatus = false // (0.0, 64.0, 375.0, 401.0)
  505. navHeadImageView?.isHidden = false
  506. backV.frame = CGRect(x: 0, y: navHeadImageView?.frame.maxY ?? 0, width: view.width, height: bottomView.frame.minY - (navHeadImageView?.frame.maxY ?? 0))
  507. playerLayer.frame = CGRect(x: 0, y: 0, width: view.width, height: bottomView.frame.minY - (navHeadImageView?.frame.maxY ?? 0))
  508. sliderView?.frame = CGRect(x: 0, y: backV.frame.height - 60, width: view.frame.width, height: 50)
  509. UIView.animate(withDuration: 0.2, animations: { [weak self] in
  510. self?.changeOrientation(orientation: .portrait)
  511. self?.backV.center = CGPoint(x: (self?.backV.width ?? 0) / 2, y: (self?.backV.height ?? 0) / 2 + (self?.navHeadImageView?.frame.maxY ?? 0))
  512. }) { [weak self] _ in
  513. }
  514. }
  515. }
  516. func changeToFullScreen() {
  517. if sliderView?.isFullScreen ?? false {
  518. isHiddenStatus = true
  519. navHeadImageView?.isHidden = true
  520. backV.frame = CGRect(x: 0, y: 0, width: view.frame.height, height: view.frame.width)
  521. playerLayer.frame = backV.bounds
  522. sliderView?.frame = CGRect(x: 0, y: backV.frame.height - 60, width: backV.frame.width, height: 50)
  523. UIView.animate(withDuration: 0.2, animations: { [weak self] in
  524. let orientation = UIDevice.current.orientation
  525. if orientation == .landscapeRight {
  526. self?.changeOrientation(orientation: .landscapeLeft)
  527. } else {
  528. self?.changeOrientation(orientation: .landscapeRight)
  529. }
  530. self?.backV.center = CGPoint(x: (self?.view.frame.width ?? 0) / 2, y: (self?.view.frame.height ?? 0) / 2)
  531. }) { [weak self] _ in
  532. }
  533. }
  534. }
  535. func changeOrientation(orientation: UIInterfaceOrientation) {
  536. UIView.animate(withDuration: 0.2, animations: { [weak self] in
  537. self?.backV.transform = self?.transformRotation(orientation: orientation) as! CGAffineTransform
  538. }) { [weak self] _ in
  539. }
  540. }
  541. @objc func transformRotation(orientation: UIInterfaceOrientation) -> CGAffineTransform {
  542. if orientation == .portrait {
  543. return .identity
  544. } else if orientation == .landscapeLeft {
  545. return CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
  546. } else if orientation == .landscapeRight {
  547. return CGAffineTransform(rotationAngle: CGFloat.pi / 2)
  548. }
  549. return .identity
  550. }
  551. }