Skip to content

Commit

Permalink
Merge pull request #356 from boostcampwm2023/iOS/feat#355
Browse files Browse the repository at this point in the history
feat: 위치 선택 기능 추가
  • Loading branch information
chopmozzi authored Jan 25, 2024
2 parents cf28193 + 4d10bde commit 4f65246
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 29 deletions.
4 changes: 4 additions & 0 deletions iOS/Layover/Layover.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
836C338B2B15D22C00ECAFB0 /* PlaybackConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 836C338A2B15D22C00ECAFB0 /* PlaybackConfigurator.swift */; };
836C33912B17629400ECAFB0 /* MapRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 836C33902B17629400ECAFB0 /* MapRouter.swift */; };
83957AA62B20F94C00B3BA8A /* MockPlaybackWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83957AA52B20F94C00B3BA8A /* MockPlaybackWorker.swift */; };
839F1DF82B62AEDA0071C622 /* LOTextLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839F1DF72B62AEDA0071C622 /* LOTextLabel.swift */; };
83C35E1B2B108C3500D8DD5C /* PlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C35E1A2B108C3500D8DD5C /* PlaybackView.swift */; };
83C35E1E2B10923C00D8DD5C /* PlaybackCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C35E1D2B10923C00D8DD5C /* PlaybackCell.swift */; };
FC0E80242B1A0BBB00EF56D6 /* UploadPostPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0E801E2B1A0BBB00EF56D6 /* UploadPostPresenter.swift */; };
Expand Down Expand Up @@ -426,6 +427,7 @@
836C33902B17629400ECAFB0 /* MapRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRouter.swift; sourceTree = "<group>"; };
836C33972B1843BE00ECAFB0 /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = "<group>"; };
83957AA52B20F94C00B3BA8A /* MockPlaybackWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPlaybackWorker.swift; sourceTree = "<group>"; };
839F1DF72B62AEDA0071C622 /* LOTextLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOTextLabel.swift; sourceTree = "<group>"; };
83C35E1A2B108C3500D8DD5C /* PlaybackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackView.swift; sourceTree = "<group>"; };
83C35E1D2B10923C00D8DD5C /* PlaybackCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackCell.swift; sourceTree = "<group>"; };
FC0E801E2B1A0BBB00EF56D6 /* UploadPostPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostPresenter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1205,6 +1207,7 @@
8321A2F62B1E14A1000A12AF /* LOPopUpView.swift */,
8321A2F82B1E15F3000A12AF /* LOReportStackView.swift */,
8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */,
839F1DF72B62AEDA0071C622 /* LOTextLabel.swift */,
);
path = DesignSystem;
sourceTree = "<group>";
Expand Down Expand Up @@ -1578,6 +1581,7 @@
198167A42B20583D0032F563 /* SettingInteractor.swift in Sources */,
FC4E0C1D2B28977000152596 /* CurrentLocationManager.swift in Sources */,
83C35E1B2B108C3500D8DD5C /* PlaybackView.swift in Sources */,
839F1DF82B62AEDA0071C622 /* LOTextLabel.swift in Sources */,
FC4E0C192B28955400152596 /* LocationFetcher.swift in Sources */,
835A61A02B068115002F22A5 /* PlaybackModels.swift in Sources */,
FC0E80292B1A0BBB00EF56D6 /* UploadPostInteractor.swift in Sources */,
Expand Down
39 changes: 39 additions & 0 deletions iOS/Layover/Layover/DesignSystem/LOTextLabel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// LOTextLabel.swift
// Layover
//
// Created by 황지웅 on 1/25/24.
// Copyright © 2024 CodeBomber. All rights reserved.
//

import UIKit

final class LOTextLabel: UILabel {

private var padding = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)

convenience init(padding: UIEdgeInsets) {
self.init()
self.padding = padding
setUI()
}

override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: padding))
}

override var intrinsicContentSize: CGSize {
var contentSize = super.intrinsicContentSize
contentSize.height += padding.top + padding.bottom
contentSize.width += padding.left + padding.right

return contentSize
}

private func setUI() {
layer.cornerRadius = 8
layer.borderWidth = 1
layer.borderColor = UIColor.grey500.cgColor
backgroundColor = UIColor.clear
}
}
77 changes: 67 additions & 10 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ protocol UploadPostBusinessLogic {
func fetchTags()
func editTags(with request: UploadPostModels.EditTags.Request)
func fetchThumbnailImage() async
func fetchCurrentAddress() async
func fetchCurrentAddress() async -> UploadPostModels.AddressInfo?
func canUploadPost(request: UploadPostModels.CanUploadPost.Request)
func uploadPost(request: UploadPostModels.UploadPost.Request)
func fetchVideoAddress() async -> UploadPostModels.AddressInfo?
func fetchAddresses() async
func showActionSheet()
func selectAddress(with request: UploadPostModels.SelectAddress.Request)
}

protocol UploadPostDataStore {
Expand All @@ -45,6 +49,8 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
var videoURL: URL?
var isMuted: Bool?
var tags: [String]? = []
var videoAddress: Models.AddressInfo?
var currentAddress: Models.AddressInfo?

// MARK: - Object LifeCycle

Expand Down Expand Up @@ -80,23 +86,44 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
}
}

func fetchCurrentAddress() async {
guard let location = locationManager.getCurrentLocation() else { return }
func fetchCurrentAddress() async -> UploadPostModels.AddressInfo? {
guard let location = locationManager.getCurrentLocation() else { return nil }
let localeIdentifier = Locale.preferredLanguages.first != nil ? Locale.preferredLanguages[0] : Locale.current.identifier
let locale = Locale(identifier: localeIdentifier)
do {
let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last
let administrativeArea = address?.administrativeArea
let locality = address?.locality
let subLocality = address?.subLocality
let response = Models.FetchCurrentAddress.Response(administrativeArea: administrativeArea,
locality: locality,
subLocality: subLocality)
await MainActor.run {
presenter?.presentCurrentAddress(with: response)
}
return Models.AddressInfo(
administrativeArea: administrativeArea,
locality: locality,
subLocality: subLocality)
} catch {
os_log(.error, log: .data, "Failed to fetch Current Address with error: %@", error.localizedDescription)
return nil
}
}

func fetchVideoAddress() async -> UploadPostModels.AddressInfo? {
guard let videoURL,
let videoLocation = await worker?.loadVideoLocation(videoURL: videoURL),
let location = locationManager.getVideoLocation(latitude: videoLocation.latitude, longitude: videoLocation.longitude)
else { return nil }
let localeIdentifier = Locale.preferredLanguages.first != nil ? Locale.preferredLanguages[0] : Locale.current.identifier
let locale = Locale(identifier: localeIdentifier)
do {
let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last
let administrativeArea = address?.administrativeArea
let locality = address?.locality
let subLocality = address?.subLocality
return Models.AddressInfo(
administrativeArea: administrativeArea,
locality: locality,
subLocality: subLocality)
} catch {
os_log(.error, log: .data, "Failed to fetch Video Address with error: %@", error.localizedDescription)
return nil
}
}

Expand Down Expand Up @@ -127,6 +154,37 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
}
}

func fetchAddresses() async {
async let currentAddressInfo = fetchCurrentAddress()
async let videoAddressInfo = fetchVideoAddress()

videoAddress = await videoAddressInfo
currentAddress = await currentAddressInfo

let response: Models.FetchCurrentAddress.Response = Models.FetchCurrentAddress.Response(addressInfo: [ videoAddress, currentAddress].compactMap { $0 })
await MainActor.run {
presenter?.presentCurrentAddress(with: response)
}
}

func selectAddress(with request: UploadPostModels.SelectAddress.Request) {
var response: Models.FetchCurrentAddress.Response
switch request.addressType {
case .video:
guard let videoAddress else { return }
response = Models.FetchCurrentAddress.Response(addressInfo: [videoAddress])
case .current:
guard let currentAddress else { return }
response = Models.FetchCurrentAddress.Response(addressInfo: [currentAddress])
}
presenter?.presentCurrentAddress(with: response)
}

func showActionSheet() {
let response: Models.ShowActionSheet.Response = Models.ShowActionSheet.Response(videoAddress: videoAddress, currentAddress: currentAddress)
presenter?.presentShowActionSheet(with: response)
}

private func exportVideoWithoutAudio(at url: URL) async {
let composition = AVMutableComposition()
let sourceAsset = AVURLAsset(url: url)
Expand Down Expand Up @@ -158,5 +216,4 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
os_log(.error, log: .data, "Failed to extract Video Without Audio with error: %@", error.localizedDescription)
}
}

}
47 changes: 43 additions & 4 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ enum UploadPostModels {
static let titleMaxLength: Int = 15
static let contentMaxLength: Int = 50

struct VideoAddress {
let latitude: Double
let longitude: Double
}

struct AddressInfo {
let administrativeArea: String?
let locality: String?
let subLocality: String?
}

enum AddressType {
case video
case current
}

enum CanUploadPost {
struct Request {
let title: String?
Expand Down Expand Up @@ -61,9 +77,7 @@ enum UploadPostModels {

}
struct Response {
let administrativeArea: String?
let locality: String?
let subLocality: String?
let addressInfo: [AddressInfo]
}
struct ViewModel {
let fullAddress: String
Expand All @@ -79,7 +93,32 @@ enum UploadPostModels {
struct Response {

}
struct VideModel {
struct ViewModel {

}
}

enum ShowActionSheet {
struct Request {

}
struct Response {
let videoAddress: AddressInfo?
let currentAddress: AddressInfo?
}
struct ViewModel {
let addressTypes: [AddressType]
}
}

enum SelectAddress {
struct Request {
let addressType: AddressType
}
struct Response {

}
struct ViewModel {

}
}
Expand Down
21 changes: 16 additions & 5 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protocol UploadPostPresentationLogic {
func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response)
func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response)
func presentUnsupportedFormatAlert()
func presentShowActionSheet(with response: UploadPostModels.ShowActionSheet.Response)
}

final class UploadPostPresenter: UploadPostPresentationLogic {
Expand All @@ -34,19 +35,18 @@ final class UploadPostPresenter: UploadPostPresentationLogic {

func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) {
let addresses: [String] = [
response.administrativeArea,
response.locality,
response.subLocality]
response.addressInfo.first?.administrativeArea,
response.addressInfo.first?.locality,
response.addressInfo.first?.subLocality]
.compactMap { $0 }

var fullAddress: [String] = []

for address in addresses {
if !fullAddress.contains(address) {
fullAddress.append(address)
}
}

let viewModel = Models.FetchCurrentAddress.ViewModel(fullAddress: fullAddress.joined(separator: " "))
viewController?.displayCurrentAddress(viewModel: viewModel)
}
Expand All @@ -59,4 +59,15 @@ final class UploadPostPresenter: UploadPostPresentationLogic {
func presentUnsupportedFormatAlert() {
viewController?.displayUnsupportedFormatAlert()
}

func presentShowActionSheet(with response: UploadPostModels.ShowActionSheet.Response) {
var addressTypes: [Models.AddressType] = []
if response.videoAddress != nil {
addressTypes.append(.video)
}
if response.currentAddress != nil {
addressTypes.append(.current)
}
viewController?.displayActionSheet(viewModel: Models.ShowActionSheet.ViewModel(addressTypes: addressTypes))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protocol UploadPostDisplayLogic: AnyObject {
func displayCurrentAddress(viewModel: UploadPostModels.FetchCurrentAddress.ViewModel)
func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel)
func displayUnsupportedFormatAlert()
func displayActionSheet(viewModel: UploadPostModels.ShowActionSheet.ViewModel)
}

final class UploadPostViewController: BaseViewController {
Expand Down Expand Up @@ -75,11 +76,12 @@ final class UploadPostViewController: BaseViewController {
return imageLabel
}()

private let currentAddressLabel: UILabel = {
let label = UILabel()
private let currentAddressLabel: LOTextLabel = {
let label = LOTextLabel(padding: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16))
label.font = .loFont(type: .body2)
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = true
label.isUserInteractionEnabled = true
return label
}()

Expand Down Expand Up @@ -142,6 +144,7 @@ final class UploadPostViewController: BaseViewController {
setConstraints()
setDelegation()
addTarget()
addLocationTarget()
fetchPostInfo()
}

Expand Down Expand Up @@ -176,7 +179,7 @@ final class UploadPostViewController: BaseViewController {

private func fetchPostInfo() {
Task {
await interactor?.fetchCurrentAddress()
await interactor?.fetchAddresses()
await interactor?.fetchThumbnailImage()
}
}
Expand Down Expand Up @@ -247,6 +250,11 @@ final class UploadPostViewController: BaseViewController {
scrollView.addGestureRecognizer(singleTapGestureRecognizer)
}

private func addLocationTarget() {
let singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(locationDidTap))
currentAddressLabel.addGestureRecognizer(singleTapGestureRecognizer)
}

@objc private func titleTextChanged() {
interactor?.canUploadPost(request: Models.CanUploadPost.Request(title: titleTextField.text))
}
Expand All @@ -270,6 +278,10 @@ final class UploadPostViewController: BaseViewController {
router?.routeToBack()
}

@objc private func locationDidTap() {
interactor?.showActionSheet()
}

}

extension UploadPostViewController: UITextFieldDelegate {
Expand Down Expand Up @@ -321,4 +333,21 @@ extension UploadPostViewController: UploadPostDisplayLogic {
Toast.shared.showToast(message: "지원하지 않는 파일 형식이에요 😢")
}

func displayActionSheet(viewModel: UploadPostModels.ShowActionSheet.ViewModel) {
let actionSheet = UIAlertController(title: "주소 선택", message: "원하는 위치의 주소를 선택하세요.", preferredStyle: .actionSheet)
for type in viewModel.addressTypes {
switch type {
case .video:
actionSheet.addAction(UIAlertAction(title: "영상 위치", style: .default, handler: { _ in
self.interactor?.selectAddress(with: Models.SelectAddress.Request(addressType: type))
}))
case .current:
actionSheet.addAction(UIAlertAction(title: "현재 위치", style: .default, handler: { _ in
self.interactor?.selectAddress(with: Models.SelectAddress.Request(addressType: type))
}))
}
}
actionSheet.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil))
present(actionSheet, animated: true, completion: nil)
}
}
Loading

0 comments on commit 4f65246

Please sign in to comment.