diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 415b64c..4148789 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ FC68E2A12B023326001AABFF /* EndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC68E2A02B023326001AABFF /* EndPoint.swift */; }; FC68E2A32B0233BC001AABFF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC68E2A22B0233BC001AABFF /* NetworkError.swift */; }; FC68E2A52B0233D3001AABFF /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC68E2A42B0233D3001AABFF /* Provider.swift */; }; + FC70BB2B2B20718A00B37CBE /* VideoPickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC70BB2A2B20718A00B37CBE /* VideoPickerManager.swift */; }; FC767F842B1214A80088CF9B /* MockUserWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC767F832B1214A70088CF9B /* MockUserWorker.swift */; }; FC767F862B1214C10088CF9B /* CheckUserNameDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC767F852B1214C10088CF9B /* CheckUserNameDTO.swift */; }; FC767F932B1220CC0088CF9B /* NicknameDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC767F922B1220CC0088CF9B /* NicknameDTO.swift */; }; @@ -351,6 +352,7 @@ FC68E2A02B023326001AABFF /* EndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndPoint.swift; sourceTree = ""; }; FC68E2A22B0233BC001AABFF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; FC68E2A42B0233D3001AABFF /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; + FC70BB2A2B20718A00B37CBE /* VideoPickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPickerManager.swift; sourceTree = ""; }; FC767F832B1214A70088CF9B /* MockUserWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockUserWorker.swift; sourceTree = ""; }; FC767F852B1214C10088CF9B /* CheckUserNameDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckUserNameDTO.swift; sourceTree = ""; }; FC767F922B1220CC0088CF9B /* NicknameDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameDTO.swift; sourceTree = ""; }; @@ -672,6 +674,7 @@ FC0E801D2B1A0A4900EF56D6 /* UploadPost */ = { isa = PBXGroup; children = ( + FC70BB2A2B20718A00B37CBE /* VideoPickerManager.swift */, FC0E80222B1A0BBB00EF56D6 /* UploadPostViewController.swift */, FC0E80232B1A0BBB00EF56D6 /* UploadPostInteractor.swift */, FC0E801E2B1A0BBB00EF56D6 /* UploadPostPresenter.swift */, @@ -700,12 +703,12 @@ isa = PBXGroup; children = ( FC767FA62B1269980088CF9B /* Views */, + FC767FA92B126D080088CF9B /* LOAnnotation.swift */, FC2511A82B04DAD4004717BC /* MapViewController.swift */, FC2511AC2B04EACD004717BC /* MapInteractor.swift */, FC2511AE2B04EAD9004717BC /* MapPresenter.swift */, FC2511AA2B04EA6B004717BC /* MapConfigurator.swift */, FC2511B02B04EAEC004717BC /* MapModels.swift */, - FC767FA92B126D080088CF9B /* LOAnnotation.swift */, 836C33902B17629400ECAFB0 /* MapRouter.swift */, ); path = Map; @@ -1176,6 +1179,7 @@ 194551F42B037F2D00299768 /* LoginRouter.swift in Sources */, FC4975992B03439000D8627F /* UIFont+.swift in Sources */, FC5BE11E2B148D160036366D /* EditProfileRouter.swift in Sources */, + FC70BB2B2B20718A00B37CBE /* VideoPickerManager.swift in Sources */, FC42E4142B17AB69005D4956 /* VideoFileWorker.swift in Sources */, FC767F952B1222350088CF9B /* ProfileImageDTO.swift in Sources */, FC0E803F2B1B91C900EF56D6 /* EditTagInteractor.swift in Sources */, diff --git a/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift b/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift index 9d825a5..883ad16 100644 --- a/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift +++ b/iOS/Layover/Layover/Scenes/Home/HomeViewController.swift @@ -24,17 +24,9 @@ final class HomeViewController: BaseViewController { var router: (HomeRoutingLogic & HomeDataPassing)? var interactor: HomeBusinessLogic? - // MARK: - UI Components + private let videoPickerManager: VideoPickerManager = VideoPickerManager() - private lazy var phPickerViewController: PHPickerViewController = { - var configuration = PHPickerConfiguration() - configuration.preferredAssetRepresentationMode = .current - configuration.filter = .videos - configuration.selectionLimit = 1 - let phPickerViewController = PHPickerViewController(configuration: configuration) - phPickerViewController.delegate = self - return phPickerViewController - }() + // MARK: - UI Components private lazy var uploadButton: LOCircleButton = { let button = LOCircleButton(style: .add, diameter: 52) @@ -72,11 +64,13 @@ final class HomeViewController: BaseViewController { override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) setup() + setDelegation() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() + setDelegation() } // MARK: - Setup @@ -120,11 +114,15 @@ final class HomeViewController: BaseViewController { override func setUI() { super.setUI() carouselCollectionView.dataSource = carouselDatasource - carouselCollectionView.delegate = self } // MARK: - Methods + private func setDelegation() { + carouselCollectionView.delegate = self + videoPickerManager.videoPickerDelegate = self + } + private func createCarouselLayout(groupWidthDimension: CGFloat, groupHeightDimension: CGFloat, maximumZoomScale: CGFloat, @@ -184,7 +182,7 @@ final class HomeViewController: BaseViewController { // MARK: - Actions @objc private func uploadButtonDidTap() { - present(phPickerViewController, animated: true) + present(videoPickerManager.phPickerViewController, animated: true) } @objc private func tagButtonDidTap(_ sender: UIButton) { @@ -215,34 +213,16 @@ extension HomeViewController: HomeCarouselCollectionViewDelegate { } } -// MARK: - PHPickerViewControllerDelegate - -extension HomeViewController: PHPickerViewControllerDelegate { +// MARK: - VideoPickerDelegate - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - guard let result = results.first else { - self.phPickerViewController.dismiss(animated: true) - return - } - - _ = result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in - - if error != nil { - Task { - await MainActor.run { - Toast.shared.showToast(message: "지원하지 않는 동영상 형식입니다 T.T") - } - } - } +extension HomeViewController: VideoPickerDelegate { - if let url { - self.interactor?.selectVideo(with: HomeModels.SelectVideo.Request(videoURL: url)) - Task { - await MainActor.run { - self.router?.routeToEditVideo() - self.phPickerViewController.dismiss(animated: true) - } - } + func didFinishPickingVideo(_ url: URL) { + self.interactor?.selectVideo(with: HomeModels.SelectVideo.Request(videoURL: url)) + Task { + await MainActor.run { + self.router?.routeToEditVideo() + self.videoPickerManager.phPickerViewController.dismiss(animated: true) } } } diff --git a/iOS/Layover/Layover/Scenes/Map/MapConfigurator.swift b/iOS/Layover/Layover/Scenes/Map/MapConfigurator.swift index 2dc85c1..07c9318 100644 --- a/iOS/Layover/Layover/Scenes/Map/MapConfigurator.swift +++ b/iOS/Layover/Layover/Scenes/Map/MapConfigurator.swift @@ -20,9 +20,12 @@ final class MapConfigurator: Configurator { let interactor = MapInteractor() let presenter = MapPresenter() let router = MapRouter() + let videoFileWorker = VideoFileWorker() + router.viewController = viewController viewController.interactor = interactor interactor.presenter = presenter + interactor.videoFileWorker = videoFileWorker presenter.viewController = viewController viewController.router = router router.dataStore = interactor diff --git a/iOS/Layover/Layover/Scenes/Map/MapInteractor.swift b/iOS/Layover/Layover/Scenes/Map/MapInteractor.swift index 052ac3b..3e3684b 100644 --- a/iOS/Layover/Layover/Scenes/Map/MapInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Map/MapInteractor.swift @@ -14,12 +14,14 @@ protocol MapBusinessLogic { func fetchVideos() func moveToPlaybackScene(with: MapModels.MoveToPlaybackScene.Request) func playPosts(with: MapModels.PlayPosts.Request) + func selectVideo(with request: MapModels.SelectVideo.Request) } protocol MapDataStore { var postPlayStartIndex: Int? { get set } var posts: [Post]? { get set } var index: Int? { get set } + var selectedVideoURL: URL? { get set } } final class MapInteractor: NSObject, MapBusinessLogic, MapDataStore { @@ -27,20 +29,17 @@ final class MapInteractor: NSObject, MapBusinessLogic, MapDataStore { // MARK: - Properties typealias Models = MapModels + var presenter: MapPresentationLogic? + var videoFileWorker: VideoFileWorker? private let locationManager = CLLocationManager() - private var latitude: Double? - private var longitude: Double? - var index: Int? - - var posts: [Post]? - var postPlayStartIndex: Int? - - var presenter: MapPresentationLogic? + var posts: [Post]? + var index: Int? + var selectedVideoURL: URL? override init() { super.init() @@ -74,6 +73,10 @@ final class MapInteractor: NSObject, MapBusinessLogic, MapDataStore { presenter?.presentPlaybackScene() } + func selectVideo(with request: Models.SelectVideo.Request) { + selectedVideoURL = videoFileWorker?.copyToNewURL(at: request.videoURL) + } + private func checkCurrentLocationAuthorization(for status: CLAuthorizationStatus) { switch status { case .authorizedAlways, .authorizedWhenInUse: diff --git a/iOS/Layover/Layover/Scenes/Map/MapModels.swift b/iOS/Layover/Layover/Scenes/Map/MapModels.swift index 8de8dea..e13f637 100644 --- a/iOS/Layover/Layover/Scenes/Map/MapModels.swift +++ b/iOS/Layover/Layover/Scenes/Map/MapModels.swift @@ -61,4 +61,18 @@ enum MapModels { struct ViewModel { } } + + enum SelectVideo { + struct Request { + let videoURL: URL + } + + struct Response { + + } + + struct ViewModel { + + } + } } diff --git a/iOS/Layover/Layover/Scenes/Map/MapRouter.swift b/iOS/Layover/Layover/Scenes/Map/MapRouter.swift index a478afb..949d6c8 100644 --- a/iOS/Layover/Layover/Scenes/Map/MapRouter.swift +++ b/iOS/Layover/Layover/Scenes/Map/MapRouter.swift @@ -10,6 +10,7 @@ import Foundation protocol MapRoutingLogic { func routeToPlayback() + func routeToEditVideo() } protocol MapDataPassing { @@ -35,4 +36,17 @@ final class MapRouter: MapRoutingLogic, MapDataPassing { destination.posts = source.posts viewController?.navigationController?.pushViewController(playbackViewController, animated: true) } + + func routeToEditVideo() { + let nextViewController = EditVideoViewController() + guard let source = dataStore, + var destination = nextViewController.router?.dataStore, + let videoURL = source.selectedVideoURL + else { return } + + // Data Passing + destination.videoURL = videoURL + nextViewController.hidesBottomBarWhenPushed = true + viewController?.navigationController?.pushViewController(nextViewController, animated: true) + } } diff --git a/iOS/Layover/Layover/Scenes/Map/MapViewController.swift b/iOS/Layover/Layover/Scenes/Map/MapViewController.swift index 3cb0f6e..327651a 100644 --- a/iOS/Layover/Layover/Scenes/Map/MapViewController.swift +++ b/iOS/Layover/Layover/Scenes/Map/MapViewController.swift @@ -48,7 +48,11 @@ final class MapViewController: BaseViewController { return button }() - private let uploadButton: LOCircleButton = LOCircleButton(style: .add, diameter: 52) + private lazy var uploadButton: LOCircleButton = { + let button = LOCircleButton(style: .add, diameter: 52) + button.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) + return button + }() private lazy var carouselCollectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout()) @@ -72,6 +76,7 @@ final class MapViewController: BaseViewController { var interactor: MapBusinessLogic? var router: (MapRoutingLogic & MapDataPassing)? + private let videoPickerManager: VideoPickerManager = VideoPickerManager() private lazy var carouselCollectionViewHeight: NSLayoutConstraint = carouselCollectionView.heightAnchor.constraint(equalToConstant: 0) // MARK: - Life Cycle @@ -90,8 +95,8 @@ final class MapViewController: BaseViewController { super.viewDidLoad() interactor?.checkLocationAuthorizationStatus() setCollectionViewDataSource() + setDelegation() createMapAnnotation() - carouselCollectionView.delegate = self } // MARK: - UI + Layout @@ -131,6 +136,11 @@ final class MapViewController: BaseViewController { // MARK: - Methods + private func setDelegation() { + carouselCollectionView.delegate = self + videoPickerManager.videoPickerDelegate = self + } + private func createLayout() -> UICollectionViewLayout { let groupWidthDimension: CGFloat = 94/375 let minumumZoomScale: CGFloat = 73/94 @@ -185,8 +195,14 @@ final class MapViewController: BaseViewController { mapView.setUserTrackingMode(.follow, animated: true) } + @objc private func uploadButtonDidTap() { + present(videoPickerManager.phPickerViewController, animated: true) + } + } +// MARK: - MKMapViewDelegate + extension MapViewController: MKMapViewDelegate { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { @@ -211,6 +227,24 @@ extension MapViewController: MKMapViewDelegate { } +// MARK: - VideoPickerDelegate + +extension MapViewController: VideoPickerDelegate { + + func didFinishPickingVideo(_ url: URL) { + interactor?.selectVideo(with: Models.SelectVideo.Request(videoURL: url)) + Task { + await MainActor.run { + videoPickerManager.phPickerViewController.dismiss(animated: true) + router?.routeToEditVideo() + } + } + } + +} + +// MARK: - MapDisplayLogic + extension MapViewController: MapDisplayLogic { func displayFetchedVideos(viewModel: ViewModel) { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/VideoPickerManager.swift b/iOS/Layover/Layover/Scenes/UploadPost/VideoPickerManager.swift new file mode 100644 index 0000000..df8b136 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/UploadPost/VideoPickerManager.swift @@ -0,0 +1,60 @@ +// +// VideoPickerManager.swift +// Layover +// +// Created by kong on 2023/12/06. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import PhotosUI + +protocol VideoPickerDelegate: AnyObject { + func didFinishPickingVideo(_ url: URL) +} + +final class VideoPickerManager: NSObject, PHPickerViewControllerDelegate { + + // MARK: - Properties + + weak var videoPickerDelegate: VideoPickerDelegate? + + let phPickerViewController: PHPickerViewController = { + var configuration = PHPickerConfiguration() + configuration.preferredAssetRepresentationMode = .current + configuration.filter = .videos + configuration.selectionLimit = 1 + let phPickerViewController = PHPickerViewController(configuration: configuration) + return phPickerViewController + }() + + // MARK: - Object LifeCycle + + override init() { + super.init() + phPickerViewController.delegate = self + } + + // MARK: - Methods + + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + guard let result = results.first else { + self.phPickerViewController.dismiss(animated: true) + return + } + + _ = result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in + if error != nil { + Task { + await MainActor.run { + Toast.shared.showToast(message: "지원하지 않는 동영상 형식입니다 T.T") + } + } + } + + if let url { + self.videoPickerDelegate?.didFinishPickingVideo(url) + } + } + } + +}