-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] #546 - 오브의 추천소 채팅 및 추천 장소 업데이트 기능 구현 #568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
여러 장소를 입력 받아 해당 장소가 모두 보이도록 하는 기능
ORBRecommendationResponseDTO -> ORBRecommendationPlacesResponseDTO
그 외 변경점 - 추천소 메인 화면에서 로딩 화면 구현
| private let placeService = NetworkService.shared.placeService | ||
|
|
||
| var places: [PlaceModel] = [] | ||
| var places: [ORBRecommendationPlaceModel] = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개발하다가 든 생각인데, places가 사실상 model 임에도 view에서 소유하고 있는 것 같더라구요.
collection view의 dataSource에 자기 자신(collection view)을 할당하면서 model을 collection view가 갖고 있는 것 같은데,
이 dataSource의 역할을 뷰컨트롤러(ORBRecommendationMainViewController)가 맡아도 괜찮을 것 같다는 생각을 해봤습니다.
(어차피 뷰컨트롤러에서 marker들 정보를 갖고 있고, 이 marker들 정보에서 places 정보를 추출하여 dataSource로 사용할 수 있습니다!)
그렇게 view의 역할을 온전히 UI를 표시하는 것으로 명확히 하고,
데이터를 관리하는 역할은 뷰컨트롤러가 맡도록 하는 게 깔끔할 것 같아 보이는데,
다른 분들은 어떻게 생각하시는지 의견을 듣고 싶습니다!
@Johyerin @codeJiwon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
말씀하신 대로 places가 실제로는 모델 역할을 하고 있고,
이를 View에서 소유하면서 View가 UI 외의 역할까지 일부 책임지고 있는 구조가 다소 애매할 수 있을 것 같습니다.
뷰컨트롤러가 이미 markers를 가지고 있고, 그로부터 places를 유도할 수 있다면, View에서 굳이 places를 따로 갖고 있을 필요는 없어 보입니다.
저도 View는 순수하게 UI 렌더링만 담당하고, 데이터의 흐름이나 생명주기는 ViewController 쪽에서 관리하도록 구조를 명확히 하는 게 더 좋다고 생각합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 뷰컨트롤러에서 관리하는 것에 동의합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Johyerin @codeJiwon
의견 감사합니다 :)
다음 작업 브랜치에서 이 부분 반영하여 작업하겠습니다!
| let categoryImageUrl: URL | ||
| // 장소 목록 데이터를 불러올 때 페이징 시 필요한 값. | ||
| let distanceFromUser: Double? | ||
| let distanceFromUser: Double |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
탐험 지도, 장소 목록 뷰에서만 사용될 구체 타입이고, 이 때의 서버 응답값에서는 distanceFromUser 값을 항상 포함합니다.
따라서 non-Opitional로 설정하였습니다.
| /// | ||
| /// 개발용 `Target`(`ORB_Dev`)에서는 개발자 모드의 설정값 중 '탐험 시 위치 인증 무시' 값에 따라 분기처리됨. | ||
| func authenticateAdventurePlace(placeInfo: PlaceModel) { | ||
| func authenticateAdventurePlace<T: PlaceDescribable>(placeInfo: T) where T: Identifiable, T.ID == Int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금 보니 이 코드는 딱히 바꿀 이유가 없어 보였는데, 프로토콜로 장소 모델 타입을 추상화하면서 불필요하게 바꾼 것 같습니다.
(사실 PlaceDescribable 을 준수하면서 Identifiable하고 ID 타입이 Int 인 타입을 사용하기 위해 PlaceModel이라는 구체 타입을 정한 건데 말이죠..! 기껏 만들어 놓은 그 구체 타입인 PlaceModel을 쓰지 않고 장황하게 조건들을 늘어놓은 모양입니다!)

func authenticateAdventurePlace(placeInfo: PlaceModel) { ... }
로 돌려놓을까 하는데, 다른 팀원들의 의견을 받습니다!
@Johyerin @codeJiwon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동의합니다!
말씀하신 대로 지금 조건은 결국 PlaceModel만 대상으로 동작하는 상황인데, 그걸 다시 제네릭과 프로토콜로 감싼 건 오히려 복잡하게 만드는 것 같습니다. PlaceDescribable을 쓸 일이 생긴다면 그때 다시 추상화해도 좋을 것 같습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 동의합니다!
| protocol BasePlaceType { | ||
|
|
||
| /// 장소 이름 | ||
| var name: String { get } | ||
|
|
||
| /// 장소의 주소 | ||
| var address: String { get } | ||
|
|
||
| /// 장소의 카테고리. `ORBPlaceCategory` 타입 | ||
| var placeCategory: ORBPlaceCategory { get } | ||
|
|
||
| /// 장소의 위•경도 좌표 | ||
| var coordinate: CLLocationCoordinate2D { get } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BasePlaceType으로 추상화한 이유는 현재 코드에서 찾기는 힘듭니다.
다만, 코스퀘스트 장소들의 서버 응답값을 확인해보니(JSON 형식) 다른 장소 데이터를 반환하는 API들과는 다르게 굉장히 제한적인 속성들만을 보내주더라구요.
그래서 기존에 구현했던 PlaceModel 타입으로는 코스퀘스트 장소를 표현할 수 없기도 하고,
그렇다고 코스퀘스트 구현은 제 영역이 아니기 때문에 직접 코스퀘스트 장소 데이터 타입을 만들기에는 부적절해 보여서,
서버의 코스퀘스트 장소 데이터 응답값에 맞게 프로토콜로 추상화만 해 두었습니다!
필요할 경우, 채택해서 사용하시면 됩니다. (필수는 아님)
@codeJiwon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인했습니다! 참고하여 구현하도록 하겠습니다.
| } else if marker?.place is ORBRecommendationPlaceModel { | ||
| exploreButton.setTitle("지도 열기", for: .normal) | ||
| } else { | ||
| exploreButton.setTitle("", for: .normal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이론상 이 코드가 실행되서는 안 되긴 합니다.
현재 PlaceDescribable을 준수하는 구체 타입은 PlaceModel과 ORBRecommendationPlaceModel 밖에 없어서...
그래도 혹시 몰라 빈칸으로 남겨두었는데,
어떻게 처리하는 게 나을까요?
@Johyerin @codeJiwon
- 이대로 둔다. (빈칸 입력)
- "탐험하기" 나 "지도 열기" 중 하나로 입력한다.
- 위의 두 문구 말고 다른 문구를 넣는다.
- fatalError()를 호출하여 앱을 종료한다.(잘못된 접근이므로 발생하면 안 되는 상황임.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3번 방안으로 "알 수 없음"을 넣는 것이 좋을 것 같습니다.
실제로 해당 코드가 실행될 일은 없겠지만, 혹시라도 예외적인 상황이 발생했을 때 사용자가 빈 버튼을 보게 하는 것보다는, "알 수 없음" 버튼이 보이도록 하는 편이 더 나을 것 같습니다.
문제가 생겼다는 걸 시각적으로 드러낼 수 있을거라 생각합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@codeJiwon
의견 감사합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 버튼 자체 문구를 조작하기 보다는 팝업을 띄운다던지 해서 사용자가 오류 상황임을 인지하게 해주면 좋을 것 같긴한데 해당 구문이 실행되는 상황이 거의 없으니 4번의 방법으로 진행해도 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Johyerin
의견 감사합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Johyerin @codeJiwon
흠 의견이 갈리는군요..
그러면 혹시 두 의견 모두 반영할 수 있도록 "알 수 없음" 으로 표현하되,
대신 디버깅 중에라도 잘못됐음을 파악할 수 있도록, assertionFailure에 에러 메시지를 포함하여 구현하도록 하겠습니다!
("잘못된 장소 모델 타입이 사용되었습니다. PlaceModel 혹은 ORBRecommendationPlaceModel 타입만 사용 가능합니다." 정도의 메시지면 충분할 것 같습니다!)
| let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: padding) | ||
| cameraUpdate.reason = reason | ||
| cameraUpdate.animation = animationCurve | ||
| cameraUpdate.animationDuration = animationDuration | ||
| mapView.moveCamera(cameraUpdate) { isCancelled in | ||
| completion?(isCancelled) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 코드가 다른 함수에서도 반복돼서 별도의 함수로 뺄까도 생각을 해 봤습니다.
별도로 구현한 함수를 호출해 보았고, 함수 호출을 한 줄로 적으면 너무 길어져서 매개변수별로 줄바꿈하여(Ctrl + m) 호출해 봤는데,
그렇게 하면 줄 수에 차이가 거의 없더라구요..(오히려 새로 구현한 함수로 인해 줄 수가 늘어납니다.)
그래서 반복되는 코드이지만 별도의 함수로 구현하지 않고 지금처럼 구현하였습니다!
|
|
||
| /// 추천소 채팅 로그 뷰에서 장소 추천 채팅이 성공했을 때 이벤트를 방출하는 `PassthroughSubject` | ||
| // RxSwift에서 PublishRelay 역할 | ||
| let shouldUpdatePlaces = PassthroughSubject<[ORBRecommendationPlaceModel], Never>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(아주아주 x 100 ) 간단한 Combine을 적용해 보았습니다.
궁금한 부분은 문의 주세요! 저도 아직 잘 모르는 게 많아서 같이 알아나가면 좋을 것 같습니다. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존 방식처럼 RxSwift사용하지 않고 Combine으로 구현하신 이유도 궁금합니당! (단지 학습차원이라면 넘어가셔도 되어요 !!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
단순 학습 차원이 맞습니다!🙂 Combine에도 어느 정도 익숙해져야 할 것 같아서 간단하게나마 도입해봤습니다!
| private let locationManager = CLLocationManager() | ||
|
|
||
| /// 툴팁이 떠 있을 때 툴팁 아래의 배경을 터치할 수 있는지 여부. 기본값은 `true` | ||
| /// 툴팁이 떠 있을 때 지도 배경 터치를 막을지 여부. 기본값은 `true` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석이 반대로 설명되어있더라구요.
수정하였습니다.
|
|
||
| enum ORBRecommendationAPI: BaseTargetType { | ||
| /// 오브의 추천소 메인화면 상단 고정 문구 요청(`GET`) | ||
| case getFixedPhrase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 문구는 추천소 메인 화면 상단의 그라데이션 버튼에 표시될 문구입니다.
fixed phrase(고정 문구) 라고 이름을 지은 것은,
이 문구는 실제로 생성형 AI가 만든 것이 아니라, 미리 정해진 몇개의 문구 중 하나를 골라 랜덤으로 보여주는 방식이기 때문입니다.
어떤 단어로 표현할까 생각해 봤는데, 서버 API에서 fixedPhrase라고 해서, 해당 이름을 그대로 사용하였습니다.
| /// 채팅을 보낸 후 캐릭터가 답변할 내용을 비동기적으로 반환하는 함수. | ||
| /// - Parameter text: 사용자가 채팅을 보낸 문자열 | ||
| /// - Returns: 캐릭터가 답변할 문자열. | ||
| func getChatResponse(onSending text: String) async -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
원래 사실 이 기능을 서버에서 구현해야 하는 게 맞는데, 시간 관계상 임시로 클라에서 구현하기로 하였습니다.
장소 추천 로직 자체는 성공했지만 추천할 장소가 없을 때 그에 맞는 문구를 띄워 주어야 하는데, 기간 내에 서버에서 이를 수정하기 어려워서
클라에서 임시로 구현한 상태입니다.
따라서, 해당 기능은 추후 개발 방향에 따라 변경되거나, 삭제될 수 있습니다.
| private(set) var xButton = UIButton() | ||
| let xButton = UIButton() | ||
| private(set) var collectionView: UICollectionView! = nil | ||
| private(set) var exampleQuestionListView = ORBRecommendationChatExampleQuestionListView() | ||
| private(set) var chatInputView = ChatTextInputView() | ||
| let exampleQuestionListView = ORBRecommendationChatExampleQuestionListView() | ||
| let chatInputView = ChatTextInputView() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 곰곰히 생각해보니, 내부적으로도 새 값을 할당하지도 않는데 var 로 선언했더라구요.
외부에 읽기는 허용하고, 초깃값 할당 후 새 값을 할당하지 않는다면 굳이 var로 선언할 필요 없이 깔끔하게 let으로 선언하는 게 훨씬 자연스러울 것 같았습니다.
codeJiwon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구현하신 내용 잘 동작하는 것 확인했습니다!
| } else if marker?.place is ORBRecommendationPlaceModel { | ||
| exploreButton.setTitle("지도 열기", for: .normal) | ||
| } else { | ||
| exploreButton.setTitle("", for: .normal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3번 방안으로 "알 수 없음"을 넣는 것이 좋을 것 같습니다.
실제로 해당 코드가 실행될 일은 없겠지만, 혹시라도 예외적인 상황이 발생했을 때 사용자가 빈 버튼을 보게 하는 것보다는, "알 수 없음" 버튼이 보이도록 하는 편이 더 나을 것 같습니다.
문제가 생겼다는 걸 시각적으로 드러낼 수 있을거라 생각합니다.
| return try await withCheckedThrowingContinuation { continuation in | ||
| provider.request(api) { result in | ||
| switch result { | ||
| case .success(let resposne): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P4
response 오타 수정 부탁드립니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
꼼꼼한 확인 감사합니다!
수정하여 반영하겠습니다.
| /// 채팅을 보낸 후 캐릭터가 답변할 내용을 비동기적으로 반환하는 함수. | ||
| /// - Parameter text: 사용자가 채팅을 보낸 문자열 | ||
| /// - Returns: 캐릭터가 답변할 문자열. | ||
| func getChatResponse(onSending text: String) async -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P4
'서버에 채팅을 요청하고 응답값을 받아서 UI에 반영하는 함수'와 '채팅을 보낸 후 캐릭터가 답변할 내용을 비동기적으로 반환하는 함수'의 이름이 현재 동일하게 getChatResponse로 되어 있는데, 혹시 이렇게 네이밍하신 특별한 이유가 있을까요?
구분하기 위해 '서버에 채팅을 요청하고 응답값을 받아서 UI에 반영하는 함수'의 이름을 requestChatResponse로 바꾸는 게 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
말씀 주신 것처럼, 함수 이름이 같아서 충분히 헷갈릴 여지가 있어 보입니다!
두 번째 메서드(async)는 사실상 오브가 답변하게 될 '텍스트 자체'를 반환하는 것을 목적으로 만든 함수입니다.
그런데 getChatResponse 라는 이름 자체도 뜻이 애매하게 들릴 것 같고, 다른 함수와 이름도 겹치니 수정하는 게 좋을 것 같습니다.
첫 번째 함수의 이름을 바꾸기보다는, 두 번째 함수의 이름을 보다 명확히 하기 위해 getChatReplyText 로 바꿔 반영하도록 하겠습니다!
| } | ||
| } | ||
|
|
||
| func getRecommendedPlace() async throws -> [ORBRecommendationPlaceModel] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P5
에러를 throw하는 부분을 별도 함수로 분리하신 이유가 궁금합니다.
initialSettings() 내부에서 직접 호출하지 않고, 굳이 함수로 분리하신 특별한 이유가 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
원래 getRecommendedPlace() async 메서드를 다른 함수에서도 재사용할 목적으로 분리했던 것으로 기억하는데,
중간에 개발 방향을 바꾸면서 이 함수는 재사용되지 않게 된 것 같습니다!
말씀하신대로 이 메서드는 분리할 필요가 없어 보이네요.
꼼꼼한 확인 감사합니다 :)
해당 함수는 없애도 될 것 같으니, 삭제하여 반영하겠습니다!
|
|
||
| // 데이터 불러오기 실패 시 alert 팝업 띄우는 코드. | ||
| let alertController = ORBAlertController(message: message, type: .messageOnly) | ||
| let okAction = ORBAlertAction(title: "화인", style: .default) { _ in return } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P4
"확인" 오타 수정 부탁드립니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정하여 반영하겠습니다!
확인 감사합니다 :)
| let marker = PlaceMapMarker(place: $0) | ||
| (marker.width, marker.height) = (26, 32) | ||
| marker.mapView = rootView.orbMapView.mapView | ||
| marker.touchHandler = { [weak self] overlay in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P5
overlay를 사용하지 않으므로 _로 변경하는 게 더 깔끔할 것 같은데 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동의합니다! _ 로 변경하여 반영하겠습니다!
| private let placeService = NetworkService.shared.placeService | ||
|
|
||
| var places: [PlaceModel] = [] | ||
| var places: [ORBRecommendationPlaceModel] = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
말씀하신 대로 places가 실제로는 모델 역할을 하고 있고,
이를 View에서 소유하면서 View가 UI 외의 역할까지 일부 책임지고 있는 구조가 다소 애매할 수 있을 것 같습니다.
뷰컨트롤러가 이미 markers를 가지고 있고, 그로부터 places를 유도할 수 있다면, View에서 굳이 places를 따로 갖고 있을 필요는 없어 보입니다.
저도 View는 순수하게 UI 렌더링만 담당하고, 데이터의 흐름이나 생명주기는 ViewController 쪽에서 관리하도록 구조를 명확히 하는 게 더 좋다고 생각합니다!
| /// | ||
| /// 개발용 `Target`(`ORB_Dev`)에서는 개발자 모드의 설정값 중 '탐험 시 위치 인증 무시' 값에 따라 분기처리됨. | ||
| func authenticateAdventurePlace(placeInfo: PlaceModel) { | ||
| func authenticateAdventurePlace<T: PlaceDescribable>(placeInfo: T) where T: Identifiable, T.ID == Int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동의합니다!
말씀하신 대로 지금 조건은 결국 PlaceModel만 대상으로 동작하는 상황인데, 그걸 다시 제네릭과 프로토콜로 감싼 건 오히려 복잡하게 만드는 것 같습니다. PlaceDescribable을 쓸 일이 생긴다면 그때 다시 추상화해도 좋을 것 같습니다.
Johyerin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구현 내용 잘 확인했습니다! 고생 많으셨습니다~!!
| } else if marker?.place is ORBRecommendationPlaceModel { | ||
| exploreButton.setTitle("지도 열기", for: .normal) | ||
| } else { | ||
| exploreButton.setTitle("", for: .normal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 버튼 자체 문구를 조작하기 보다는 팝업을 띄운다던지 해서 사용자가 오류 상황임을 인지하게 해주면 좋을 것 같긴한데 해당 구문이 실행되는 상황이 거의 없으니 4번의 방법으로 진행해도 좋을 것 같아요!
|
|
||
| /// 추천소 채팅 로그 뷰에서 장소 추천 채팅이 성공했을 때 이벤트를 방출하는 `PassthroughSubject` | ||
| // RxSwift에서 PublishRelay 역할 | ||
| let shouldUpdatePlaces = PassthroughSubject<[ORBRecommendationPlaceModel], Never>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존 방식처럼 RxSwift사용하지 않고 Combine으로 구현하신 이유도 궁금합니당! (단지 학습차원이라면 넘어가셔도 되어요 !!)
| // 채팅 기능 관련 | ||
| private extension ORBRecommendationChatViewController { | ||
|
|
||
| func sendChat(text: String) { | ||
| // 채팅이 시작하면 X 버튼 비활성화 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 문제는 PR의 마지막 부분에도 언급드린것처럼
다음 작업 브랜치에서 반영할 예정입니다!
확인 감사합니다 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 적혀있었군요.!! 확인했습니닷~
| /// | ||
| /// 개발용 `Target`(`ORB_Dev`)에서는 개발자 모드의 설정값 중 '탐험 시 위치 인증 무시' 값에 따라 분기처리됨. | ||
| func authenticateAdventurePlace(placeInfo: PlaceModel) { | ||
| func authenticateAdventurePlace<T: PlaceDescribable>(placeInfo: T) where T: Identifiable, T.ID == Int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 동의합니다!

🌴 작업한 브랜치
✅ 작업한 내용
오브의 추천소 서버 API 관련 파일 및 기능을 추가하였습니다.
다른 네트워크 서비스와 거의 동일한 형태입니다.
다음 기능들을 가집니다.
장소 관련 데이터 모델들을 프로토콜로 추상화하였습니다.
오브의 추천소 기능을 구현하면서 '오브가 추천해준 장소' 를 나타낼 별도 데이터 모델을 정의해야 했습니다.
거의 비슷한 형식인데 조금씩 속성들이 달라서..
그리고 코스퀘스트 장소를 나타낼 데이터 모델의 속성도 기존 PlaceModel과는 크게 차이가 있는 것 같아서
여러 데이터 모델들(구조체)을 추상화하는 프로토콜을 만들었습니다.
장소 목록 셀이나 마커에서 장소 정보를 갖거나 이용하는 경우가 있는데요, 이때 탐험 시 사용되는 장소 모델과 추천소에서 사용되는 장소 모델 타입에 대해 일일이 분기처리할 경우 불필요한 코드가 너무 많아질 것 같아서, 장소 모델 자체를 추상화하였고,
구체 타입이 필요 시 타입캐스팅을 통해 분기처리할 수 있도록 하였습니다.
그리고 장소 목록 셀의 configure 메서드에서 다음과 같이 매개변수를 받을 수 있습니다.
ORBMapView에 새로운 함수를 추가하였습니다.오브의 추천소에서 추천 장소 목록을 받으면 지도에서는 모든 장소가 보이도록 해야 합니다.
이때 여러 장소들의 좌표를 매개변수로 받아서 해당 장소들이 모두 보이도록 지도의 카메라를 이동하는 함수를 새로 구현하였습니다.
홈 버튼이 있는 기기에서 오브의 추천소 UI를 개선하였습니다.
제목과 뒤로가기 버튼 사이의 간격을 좁게 설정하여, 스크롤 가능한 영역을 최대한 확보하도록 하였습니다.
❗️PR Point
질문을 받는 과정에 추천 장소를 새로고침하는 것도 포함되어있어서, 채팅이 끝난 후 추천소 메인화면으로 나갔을 때, 새로고침된 장소 목록을 바로 확인할 수 있도록 하기 위함입니다.
(채팅의 답변을 기다리는 중에 나가면 추천소 홈 화면에서 부자연스럽게 끊기며 새로고침되기도 하고, 사용자가 어떤 피드백을 받았는지 확인하지 못한다는 단점도 있음.)
다음 기능들은 이 PR이 머지되고 나면 다른 브랜치에서 추가할 예정입니다. 이 브랜치에서 같이 작업하면 변경사항이 지나치게 많아질 것 같아서, 머지하자마자 바로 작업에 들어간 후 새 PR로 올릴 예정입니다.
📸 스크린샷
Simulator.Screen.Recording.-.iPhone.16.-.2025-06-21.at.01.45.29.mp4