MediaPreviewViewController.swift 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import UIKit
  21. import CocoaLumberjackSwift
  22. class MediaPreviewViewController: UIViewController, UIGestureRecognizerDelegate {
  23. @IBOutlet weak var largeCollectionView: UICollectionView!
  24. @IBOutlet weak var smallCollectionView: UICollectionView!
  25. @IBOutlet weak var middeStackView: UIStackView!
  26. @IBOutlet weak var bottomLayoutConstraint: NSLayoutConstraint!
  27. @IBOutlet weak var textField: UITextField!
  28. @IBOutlet weak var navigationBar: UINavigationBar!
  29. @IBOutlet weak var backButton: UIBarButtonItem!
  30. @IBOutlet weak var addButton: UIButton!
  31. @IBOutlet weak var sendButton: UIButton!
  32. @IBOutlet weak var deleteButton: UIBarButtonItem!
  33. @IBOutlet weak var moreButton: UIBarButtonItem!
  34. @IBOutlet weak var largeCollectionViewContainerView: MediaPreviewCarouselContainerView!
  35. var keyboardResize: KeyboardResizeCenterY?
  36. var mediaData: [MediaPreviewItem] = []
  37. let mediaFetchQueue = DispatchQueue(label: "MediaDataFetchQueue", qos: .userInitiated, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
  38. var completion: (([Any], Bool, [String]) -> Void)?
  39. var returnToMe: (([DKAsset], [MediaPreviewItem]) -> Void)?
  40. var addMore: (([DKAsset], [MediaPreviewItem]) -> Void)?
  41. weak var delegate: SendMediaAction?
  42. @objc var backIsCancel : Bool = false
  43. var mainCollectionViewController : MainCollectionViewController?
  44. var miniController: ThumbnailCollectionViewController?
  45. var currentItem : IndexPath = IndexPath(item: 0, section: 0)
  46. var errorList : [PhotosPickerError] = []
  47. var selection : IndexPath?
  48. override func viewDidLoad() {
  49. super.viewDidLoad()
  50. self.hideKeyboardOnTap()
  51. self.textField.placeholder = BundleUtil.localizedString(forKey:"add_caption_to_image")
  52. self.textField.delegate = self
  53. if backIsCancel {
  54. self.backButton.title = BundleUtil.localizedString(forKey:"cancel")
  55. } else {
  56. self.backButton.title = BundleUtil.localizedString(forKey:"back")
  57. }
  58. self.sendButton.setTitle(BundleUtil.localizedString(forKey:"send"), for: .normal)
  59. NotificationCenter.default.addObserver(self,
  60. selector: #selector(self.updateLayoutForKeyboard(notification:)),
  61. name: UIResponder.keyboardWillChangeFrameNotification,
  62. object: nil)
  63. self.largeCollectionView.delegate = mainCollectionViewController
  64. self.largeCollectionView.dataSource = mainCollectionViewController
  65. let layout = MediaPreviewFlowLayout()
  66. layout.scrollDirection = .horizontal
  67. self.largeCollectionView.collectionViewLayout = layout
  68. self.largeCollectionView.isPagingEnabled = true
  69. self.largeCollectionView.allowsMultipleSelection = false
  70. self.smallCollectionView.delegate = miniController!
  71. self.smallCollectionView.dataSource = miniController!
  72. self.smallCollectionView.allowsMultipleSelection = false
  73. self.largeCollectionView.selectItem(at: self.currentItem, animated: true, scrollPosition: .centeredHorizontally)
  74. self.smallCollectionView.selectItem(at: self.currentItem, animated: true, scrollPosition: .left)
  75. if #available(iOS 11.0, *) {
  76. self.smallCollectionView.dragInteractionEnabled = true
  77. self.smallCollectionView.dragDelegate = miniController!
  78. self.smallCollectionView.dropDelegate = miniController!
  79. }
  80. self.largeCollectionViewContainerView.delegate = self
  81. self.addAccessibilityLabels()
  82. self.updateTextForIndex(indexPath: IndexPath(item: 0, section: 0), animated: false)
  83. }
  84. override func viewWillAppear(_ animated: Bool) {
  85. self.largeCollectionView.collectionViewLayout.invalidateLayout()
  86. if self.errorList.count > 0 {
  87. showError(errorList: self.errorList)
  88. self.errorList = []
  89. }
  90. if self.mediaData.count > 1 {
  91. self.navigationBar.topItem?.title = String(format: BundleUtil.localizedString(forKey:"multiple_media_items"), self.mediaData.count)
  92. } else {
  93. self.navigationBar.topItem?.title = BundleUtil.localizedString(forKey:"media_item")
  94. }
  95. }
  96. func hideKeyboardOnTap() {
  97. let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
  98. tap.cancelsTouchesInView = false
  99. tap.delegate = self
  100. view.addGestureRecognizer(tap)
  101. }
  102. func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
  103. if touch.view == self.sendButton {
  104. return false
  105. }
  106. if touch.view == self.addButton {
  107. return false
  108. }
  109. return true
  110. }
  111. @objc override func dismissKeyboard() {
  112. self.textField.resignFirstResponder()
  113. }
  114. func addAccessibilityLabels() {
  115. self.sendButton.accessibilityLabel = BundleUtil.localizedString(forKey:"send")
  116. self.addButton.accessibilityLabel = BundleUtil.localizedString(forKey:"add_more_images")
  117. self.textField.accessibilityLabel = BundleUtil.localizedString(forKey:"add_caption_to_image")
  118. self.deleteButton.accessibilityLabel = BundleUtil.localizedString(forKey:"remove_current_image_from_selected_images")
  119. self.moreButton.accessibilityLabel = BundleUtil.localizedString(forKey:"send_options")
  120. if backIsCancel {
  121. self.backButton.accessibilityLabel = BundleUtil.localizedString(forKey:"back_to_media_selection")
  122. } else {
  123. self.backButton.accessibilityLabel = BundleUtil.localizedString(forKey:"cancel")
  124. }
  125. }
  126. deinit {
  127. NotificationCenter.default.removeObserver(self)
  128. }
  129. @objc public func initWithMedia(dataArray: [Any], delegate: SendMediaAction, completion: (([Any], Bool, [String]) -> Void)?, returnToMe: (([DKAsset], [MediaPreviewItem]) -> Void)?, addMore: (([DKAsset], [MediaPreviewItem]) -> Void)?) {
  130. self.completion = completion
  131. self.returnToMe = returnToMe
  132. self.addMore = addMore
  133. self.delegate = delegate
  134. mainCollectionViewController = MainCollectionViewController(delegate: self)
  135. miniController = ThumbnailCollectionViewController()
  136. miniController?.parent = self
  137. self.resetMediaTo(dataArray: dataArray, reloadData: false)
  138. }
  139. private func addDataItemFrom(url: URL) {
  140. let mimeType = UTIConverter.mimeType(fromUTI: UTIConverter.uti(forFileURL: url))
  141. if UTIConverter.isImageMimeType(mimeType) {
  142. let item = ImagePreviewItem(itemUrl: url)
  143. self.mediaData.append(item)
  144. } else if UTIConverter.isMovieMimeType(mimeType) || UTIConverter.isVideoMimeType(mimeType) {
  145. let item = VideoPreviewItem(itemUrl: url)
  146. self.mediaData.append(item)
  147. } else {
  148. self.errorList.append(PhotosPickerError.unknown)
  149. }
  150. }
  151. private func requestAssets() {
  152. self.mediaFetchQueue.async {
  153. for index in 0..<self.mediaData.count {
  154. self.mediaData[index].requestAsset()
  155. }
  156. }
  157. }
  158. @objc func resetMediaTo(dataArray: [Any], reloadData : Bool) {
  159. self.mediaData = []
  160. for index in 0..<dataArray.count {
  161. switch dataArray[index] {
  162. case is MediaPreviewItem:
  163. let mediaItem = dataArray[index] as! MediaPreviewItem
  164. self.mediaData.append(mediaItem)
  165. case is DKAsset:
  166. let data = dataArray[index] as! DKAsset
  167. let mediaItem = self.mediaPreviewItemFromDKAsset(asset: data)
  168. self.mediaData.append(mediaItem)
  169. case is PHAsset:
  170. guard let phasset = dataArray[index] as? PHAsset else {
  171. continue
  172. }
  173. self.mediaData.append(self.mediaPreviewItemFromDKAsset(asset: DKAsset(originalAsset: phasset)))
  174. case is URL, is NSURL:
  175. let url = dataArray[index] as! URL
  176. self.addDataItemFrom(url: url)
  177. case is PhotosPickerError:
  178. guard let err = dataArray[index] as? PhotosPickerError else {
  179. continue
  180. }
  181. self.errorList.append(err)
  182. default:
  183. continue
  184. }
  185. }
  186. self.requestAssets()
  187. if reloadData {
  188. self.reloadData()
  189. if self.errorList.count > 0 {
  190. showError(errorList: self.errorList)
  191. self.errorList = []
  192. }
  193. }
  194. }
  195. private func showError(errorList : [PhotosPickerError]) {
  196. let items = errorList.count
  197. var title = BundleUtil.localizedString(forKey:"could_not_add_items_title")
  198. var message = String(format: BundleUtil.localizedString(forKey:"multiple_media_items_could_not_be_processed"), items)
  199. if items == 1 {
  200. title = BundleUtil.localizedString(forKey:"could_not_add_all_items_title")
  201. message = BundleUtil.localizedString(forKey:"one_media_item_could_not_be_processed")
  202. }
  203. UIAlertTemplate.showAlert(owner: self, title: title, message: message, actionOk: {_ in
  204. if self.mediaData.count == 0 {
  205. self.backButtonPressed(self)
  206. }
  207. })
  208. }
  209. func reloadData() {
  210. self.largeCollectionView.reloadData()
  211. self.smallCollectionView.reloadData()
  212. self.updateSelection()
  213. }
  214. func mediaPreviewItemFromDKAsset(asset : DKAsset) -> MediaPreviewItem {
  215. var mediaItem : MediaPreviewItem
  216. if asset.isVideo {
  217. mediaItem = VideoPreviewItem.init(originalAsset: asset)
  218. } else {
  219. mediaItem = ImagePreviewItem.init(originalAsset: asset)
  220. }
  221. return mediaItem
  222. }
  223. func reloadCollectionViewData() {
  224. self.largeCollectionView.reloadData()
  225. self.smallCollectionView.reloadData()
  226. DispatchQueue.main.async {
  227. self.smallCollectionView.selectItem(at: IndexPath(item: 0, section: 0), animated: false, scrollPosition: .left)
  228. }
  229. }
  230. func initProgress() {
  231. DispatchQueue.main.async(execute: {
  232. let hud = MBProgressHUD.showAdded(to: self.view, animated: true)
  233. if hud.progressObject == nil {
  234. hud.mode = .annularDeterminate
  235. let po = Progress(totalUnitCount: Int64(self.mediaData.count))
  236. hud.progressObject = po
  237. hud.label.text = String(format: BundleUtil.localizedString(forKey:"processing_items_progress"), po.completedUnitCount, po.totalUnitCount)
  238. }
  239. })
  240. }
  241. func incrementProgress() {
  242. DispatchQueue.main.async(execute: {
  243. guard let hud = MBProgressHUD(for: self.view) else {
  244. return
  245. }
  246. if hud.progressObject != nil {
  247. guard let po = hud.progressObject else {
  248. return
  249. }
  250. hud.mode = .annularDeterminate
  251. po.completedUnitCount += 1
  252. hud.label.text = String(format: BundleUtil.localizedString(forKey:"processing_items_progress"), po.completedUnitCount, po.totalUnitCount)
  253. }
  254. })
  255. }
  256. func presentSizeAlertWithSize(size : Int64) {
  257. let size = ByteCountFormatter.string(fromByteCount: size, countStyle: .file)
  258. let allowed = ByteCountFormatter.string(fromByteCount: Int64(kMaxFileSize), countStyle: .file)
  259. let title = BundleUtil.localizedString(forKey:"item_too_large_title")
  260. let message = String(format: BundleUtil.localizedString(forKey:"maximum_file_size_exceeded"), allowed, size)
  261. UIAlertTemplate.showAlert(owner: self, title: title, message: message)
  262. }
  263. @IBAction func sendButtonPressed(_ sender: Any) {
  264. self.initProgress()
  265. DispatchQueue.global(qos: .userInitiated).async {
  266. var returnVal: [Any] = []
  267. var captions: [String] = []
  268. for item in self.mediaData {
  269. if item is ImagePreviewItem {
  270. if item.originalAsset != nil {
  271. guard let originalAsset = item.originalAsset else {
  272. continue
  273. }
  274. guard let asset = originalAsset.originalAsset else {
  275. DDLogError("Original Asset is unavailable.")
  276. continue
  277. }
  278. returnVal.append(asset)
  279. } else {
  280. guard let assetUrl = item.itemUrl else {
  281. continue
  282. }
  283. returnVal.append(assetUrl)
  284. }
  285. }
  286. if item is VideoPreviewItem {
  287. guard let videoItem = item as? VideoPreviewItem else {
  288. continue
  289. }
  290. guard let assetUrl : URL = videoItem.getTranscodedItem() else {
  291. continue
  292. }
  293. returnVal.append(assetUrl)
  294. }
  295. captions.append(item.caption ?? "")
  296. self.incrementProgress()
  297. }
  298. let deadlineTime = DispatchTime.now() + .seconds(1)
  299. DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
  300. self.hideProgressHud()
  301. self.completion?(returnVal, self.mediaData[0].sendAsFile, captions)
  302. }
  303. }
  304. }
  305. func hideProgressHud() {
  306. DispatchQueue.main.async {
  307. MBProgressHUD.hide(for: self.view, animated: true)
  308. }
  309. }
  310. @IBAction func trashTapped(_ sender: Any) {
  311. guard let indexPath = self.getCurrentlyVisibleItem() else {
  312. return
  313. }
  314. self.mediaData[indexPath.item].removeItem()
  315. _ = self.mediaData.remove(at: indexPath.item)
  316. self.largeCollectionView.deleteItems(at: [indexPath])
  317. self.smallCollectionView.deleteItems(at: [indexPath])
  318. if self.mediaData.count == 0 {
  319. self.backButtonPressed(self)
  320. } else {
  321. let newItem = min(indexPath.item, self.mediaData.count - 1)
  322. self.currentItem = IndexPath(item:newItem, section: indexPath.section)
  323. self.updateSelection()
  324. }
  325. }
  326. override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
  327. selection = self.getCurrentlyVisibleItem()
  328. DispatchQueue.main.asyncAfter(deadline: .now() + duration / 2, execute: {
  329. self.largeCollectionView.collectionViewLayout.invalidateLayout()
  330. })
  331. }
  332. func updateSelection() {
  333. guard let indexPath = self.getCurrentlyVisibleItem() else {
  334. return
  335. }
  336. self.updateTextForIndex(indexPath: indexPath, animated: true)
  337. self.largeCollectionViewContainerView.currentImage = self.mediaData[min(indexPath.item, self.mediaData.count - 1)]
  338. DispatchQueue.main.async {
  339. self.smallCollectionView.selectItem(at: indexPath, animated: true, scrollPosition: UICollectionView.ScrollPosition.centeredHorizontally)
  340. NotificationCenter.default.post(name: NSNotification.Name(rawValue: kMediaPreviewPauseVideo), object: nil)
  341. }
  342. }
  343. @IBAction func moreButtonPressed(_ sender: Any) {
  344. let sb = UIStoryboard(name: "MediaShareStoryboard", bundle: nil)
  345. let moreOptionsNavigationController = sb.instantiateViewController(withIdentifier: "moreOptionsNavigationController")
  346. (moreOptionsNavigationController.children.first as? MediaShareOptionsViewController)?.setupOptions(options: MediaShareOptionsViewController.ImageSendOptions(sendAsFile: self.mediaData[0].sendAsFile , imageQuality: ""))
  347. self.present(moreOptionsNavigationController, animated: true, completion: {
  348. (moreOptionsNavigationController.children.first as? MediaShareOptionsViewController)?.delegate = self
  349. })
  350. }
  351. func updateOptions(imageSendOptions: MediaShareOptionsViewController.ImageSendOptions) {
  352. for index in 0...self.mediaData.count - 1 {
  353. let item = self.mediaData[index]
  354. item.sendAsFile = imageSendOptions.sendAsFile
  355. }
  356. }
  357. @IBAction func smallAddButtonPressed(_ sender: Any) {
  358. var returnVal: [DKAsset] = []
  359. for item in self.mediaData {
  360. guard let originalAsset = item.originalAsset else {
  361. continue
  362. }
  363. returnVal.append(originalAsset)
  364. }
  365. self.addMore?(returnVal, self.mediaData)
  366. }
  367. @IBAction func backButtonPressed(_ sender: Any) {
  368. var returnVal: [DKAsset] = []
  369. for item in self.mediaData {
  370. guard let originalAsset = item.originalAsset else {
  371. continue
  372. }
  373. returnVal.append(originalAsset)
  374. }
  375. self.returnToMe?(returnVal, self.mediaData)
  376. }
  377. @objc static func equals(asset: DKAsset, item: MediaPreviewItem) -> Bool {
  378. return item.originalAsset == asset
  379. }
  380. @objc static func isURLItem(item : MediaPreviewItem) -> Bool {
  381. return item.itemUrl != nil
  382. }
  383. @objc static func contains(asset: DKAsset, itemList: [MediaPreviewItem]) -> Int {
  384. for index in 0..<itemList.count {
  385. if equals(asset: asset, item: itemList[index]) {
  386. return index
  387. }
  388. }
  389. return -1
  390. }
  391. func shouldScrollTo(indexPath : IndexPath, animated : Bool = true) {
  392. self.currentItem = indexPath
  393. DispatchQueue.main.async {
  394. self.largeCollectionView.scrollToItem(at: self.currentItem, at: .centeredHorizontally, animated: animated)
  395. self.smallCollectionView.scrollToItem(at: self.currentItem, at: .centeredHorizontally, animated: animated)
  396. self.smallCollectionView.selectItem(at: self.currentItem, animated: true, scrollPosition: .centeredHorizontally)
  397. }
  398. self.updateTextForIndex(indexPath: self.currentItem, animated: true)
  399. UIAccessibility.post(notification: .pageScrolled, argument: "Item \(self.currentItem.item) of \(mediaData.count)")
  400. }
  401. func getCurrentlyVisibleItem() -> IndexPath? {
  402. return self.currentItem
  403. }
  404. @IBAction func captionEditingChanged(_ sender: Any) {
  405. guard let indexPath = self.getCurrentlyVisibleItem() else {
  406. return
  407. }
  408. self.mediaData[indexPath.item].caption = self.textField.text
  409. }
  410. func updateTextForIndex(indexPath: IndexPath, animated: Bool) {
  411. if self.mediaData.count - 1 < indexPath.item {
  412. return
  413. }
  414. DispatchQueue.main.async {
  415. let index = indexPath.item
  416. let textColor = Colors.fontNormal()
  417. let tintColor = Colors.main()
  418. if !animated {
  419. self.textField.text = self.mediaData[index].caption
  420. } else {
  421. self.textField.text = self.mediaData[index].caption
  422. let fadeOut = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: {
  423. self.textField.textColor = self.textField.backgroundColor
  424. self.textField.tintColor = .clear
  425. self.textField.text = ""
  426. })
  427. let fadeIn = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: {
  428. self.textField.textColor = textColor
  429. self.textField.tintColor = tintColor
  430. let index = indexPath.item
  431. self.textField.text = self.mediaData[index].caption
  432. })
  433. fadeOut.addCompletion({_ in
  434. fadeIn.startAnimation()
  435. })
  436. fadeOut.startAnimation()
  437. }
  438. }
  439. }
  440. @objc func updateLayoutForKeyboard(notification: NSNotification) {
  441. let prevConst = self.bottomLayoutConstraint?.constant
  442. if let userInfo = notification.userInfo {
  443. let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
  444. let endFrameY = endFrame?.origin.y ?? 0
  445. let duration: TimeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
  446. let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
  447. let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
  448. let animationCurve: UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
  449. if endFrameY >= UIScreen.main.bounds.size.height {
  450. self.bottomLayoutConstraint?.constant = 0.0
  451. } else {
  452. if let keyBoardHeight = endFrame?.size.height {
  453. let safeInset: CGFloat
  454. if #available(iOS 11.0, *) {
  455. safeInset = self.view.safeAreaInsets.bottom
  456. } else {
  457. safeInset = 0.0
  458. }
  459. self.bottomLayoutConstraint?.constant = -(keyBoardHeight - smallCollectionView.frame.height - safeInset)
  460. } else {
  461. self.bottomLayoutConstraint?.constant = 0.0
  462. }
  463. }
  464. let layout = UICollectionViewFlowLayout()
  465. layout.minimumLineSpacing = 0.0
  466. layout.minimumInteritemSpacing = 0.0
  467. layout.scrollDirection = .horizontal
  468. layout.itemSize = self.largeCollectionView.frame.size
  469. if self.bottomLayoutConstraint.constant != 0.0 {
  470. layout.itemSize.height = self.largeCollectionView.frame.height + self.bottomLayoutConstraint.constant
  471. } else {
  472. layout.itemSize.height = self.largeCollectionView.frame.height - (prevConst ?? 0.0)
  473. }
  474. UIView.animate(withDuration: duration,
  475. delay: TimeInterval(0),
  476. options: animationCurve,
  477. animations: {
  478. self.view.layoutIfNeeded()
  479. self.largeCollectionView.setCollectionViewLayout(layout, animated: false)
  480. },
  481. completion:nil)
  482. }
  483. }
  484. }
  485. extension MediaPreviewViewController : UITextFieldDelegate {
  486. func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  487. textField.resignFirstResponder()
  488. }
  489. }