커뮤니티 지도 첨부
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import Foundation
import MessageKit
import CoreLocation
struct Location: LocationItem {
var location: CLLocation
var size: CGSize
init(location: CLLocation) {
self.location = location
self.size = CGSize(width: 240, height: 240) // 원하는 지도 크기
}
}
|
지도를 위한 모델링
Message.swift 변경사항 요약
✅ 핵심 요약
기존의 텍스트, 이미지 메시지 외에 위치(Location) 메시지를 전송할 수 있도록 구조를 확장하였다.
이에 따라 location
속성, MessageKind
처리, 이니셜라이저, Firestore 연동 및 직렬화 관련 코드가 추가되었다.
1. import CoreLocation 추가
- 위치 정보를 처리하기 위해 CoreLocation 프레임워크를 가져온다.
2. 프로퍼티 추가
1
| var location: CLLocation?
|
- 메시지가 위치 정보를 담을 수 있도록 프로퍼티를 추가하였다.
3. kind 계산 로직 확장
1
2
3
4
5
6
7
8
9
10
11
| var kind: MessageKind {
if let image = image {
let mediaItem = ImageMediaItem(image: image)
return .photo(mediaItem)
} else if let location = location {
let locationItem = Location(location: location)
return .location(locationItem)
} else {
return .text(content)
}
}
|
- 위치 메시지가 존재하는 경우
.location
형태로 반환되도록 처리.
4. 이니셜라이저 2개 추가 (User / CustomUser)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| init(user: User, location: CLLocation, displayName: String) {
sender = Sender(senderId: user.uid, displayName: displayName)
self.location = location
sentDate = Date()
content = ""
id = nil
}
init(customUser: CustomUser, location: CLLocation, displayName: String) {
sender = Sender(senderId: customUser.uid, displayName: displayName)
self.location = location
sentDate = Date()
content = ""
id = nil
}
|
- 위치 메시지를 만들 수 있도록 각각 사용자, 게스트용 초기화 메서드 추가.
5. Firestore Snapshot 초기화 로직 확장
1
2
3
4
5
| else if let latitude = data["latitude"] as? CLLocationDegrees, let longitude = data["longitude"] as? CLLocationDegrees {
location = CLLocation(latitude: latitude, longitude: longitude)
content = ""
downloadURL = nil
}
|
- Firestore에서 위도/경도를 읽어 메시지로 변환하는 로직 추가.
6. DatabaseRepresentation 확장
1
2
3
4
| else if let location = location {
representation["latitude"] = location.coordinate.latitude
representation["longitude"] = location.coordinate.longitude
}
|
- 메시지를 저장할 때 위치 정보가 있을 경우 위도/경도를 별도 필드로 저장함.
이번 수정은 메시지 모델을 확장하여 텍스트 / 이미지 / 위치 세 가지 형태를 모두 지원할 수 있도록 한 구조적 개선이다.
이로 인해 사용자 간 위치 공유 기능을 구현할 수 있는 기반이 마련되었다.
위치 공유 기능 추가 (ChatVC)
입력 버튼 대체 및 액션시트 추가
기존 카메라 버튼 대신 paperclip 아이콘으로 교체하고, 누르면 사진/지도 선택이 가능한 액션시트를 띄우도록 수정하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // Before
let cameraBarButtonItem: InputBarButtonItem = {
...
button.image = UIImage(systemName: "camera")
button.addTarget(self, action: #selector(didTapCameraButton), for: .touchUpInside)
}()
// After
let addBarButtonItem: InputBarButtonItem = {
let button = InputBarButtonItem(type: .system)
button.tintColor = ThemeColor.mainOrange
button.image = UIImage(systemName: "paperclip")
button.addTarget(self, action: #selector(presentInputActionSheet), for: .touchUpInside)
return button
}()
|
2. paperclip 버튼 클릭 시 액션시트 표시
사용자 상태에 따라 분기 처리하며, 유저일 경우 사진/지도 액션시트를, 게스트일 경우 경고 메시지를 보여준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| @objc private func presentInputActionSheet() {
if user != nil {
let actionSheet = UIAlertController(title: "유형을 선택해주세요", message: "아래에서 선택해주세요", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "사진", style: .default, handler: { [weak self] _ in
self?.didTapCameraButton()
}))
actionSheet.addAction(UIAlertAction(title: "지도", style: .default, handler: { [weak self] _ in
self?.presentLocationPicker()
}))
actionSheet.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil))
present(actionSheet, animated: true)
} else if customUser != nil {
showMessage(title: "로그인이 필요한 기능입니다.", message: "사용 할 수 없습니다.")
}
}
|
3. 지도 선택 화면 전환
지도 선택을 위해 MapViewController
를 모달로 띄운다. 선택된 위치는 델리게이트 메서드로 전달받는다.
1
2
3
4
5
6
7
| private func presentLocationPicker() {
let mapVC = MapViewController()
mapVC.delegate = self
mapVC.isLocationPicker = true
let navController = UINavigationController(rootViewController: mapVC)
present(navController, animated: true)
}
|
4. 위치 선택 후 메시지로 전송
선택된 좌표를 기반으로 Message
객체를 생성하여 Firestore에 저장한다. 이후 UI 업데이트는 리스너를 통해 처리된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| extension ChatVC: MapViewControllerDelegate {
func didSelectLocation(_ location: CLLocationCoordinate2D) {
guard let user = self.user else {
print("No valid user found")
return
}
let displayName = currentDisplayName
let locationMessage = Message(
user: user,
location: CLLocation(latitude: location.latitude, longitude: location.longitude),
displayName: displayName
)
chatFirestoreStream.save(locationMessage) { error in
if let error = error {
print(error)
return
}
// 메시지 추가는 Firestore 리스너가 처리
}
}
}
|
MapViewController 변경사항 요약
1. delegate 프로토콜 추가
1
2
3
| protocol MapViewControllerDelegate: AnyObject {
func didSelectLocation(_ location: CLLocationCoordinate2D)
}
|
설명: 위치 선택 결과를 전달할 수 있도록 외부에서 처리할 delegate 프로토콜을 정의함.
2. 프로퍼티 추가
1
2
3
| weak var delegate: MapViewControllerDelegate?
var selectedLocation: CLLocationCoordinate2D?
var isLocationPicker: Bool = false
|
설명: 지도 선택 기능에서 사용할 위치 저장 변수와 delegate 설정을 위한 프로퍼티가 추가됨. isLocationPicker
는 위치 선택 모드 여부를 나타냄.
3. 버튼 UI 추가 및 제약 조건 설정
1
2
3
| private let buttonStackView: UIStackView = ...
private let sendButton: UIButton = ...
private let cancelButton: UIButton = ...
|
설명: 선택된 위치를 전송하거나 취소할 수 있도록 Send
, Cancel
버튼을 UIStackView
로 구성함.
4. viewWillAppear에서 버튼 UI 활성화
1
2
3
4
5
6
| override func viewWillAppear(_ animated: Bool) {
...
if isLocationPicker {
setupButtons()
}
}
|
설명: 위치 선택 모드일 경우에만 버튼이 화면에 표시되도록 제어함.
5. UILongPressGestureRecognizer 등록 및 핸들러 구현
1
2
3
4
5
6
| let longPressGesture = UILongPressGestureRecognizer(...)
mapView.map.addGestureRecognizer(longPressGesture)
@objc func handleLongPress(gestureRecognizer: UILongPressGestureRecognizer) {
...
}
|
설명: 지도에서 길게 눌러 좌표를 선택할 수 있도록 제스처 인식기를 추가함. 선택된 좌표는 selectedLocation
에 저장되며 핀이 갱신됨.
6. 위치 선택 결과 전송 및 취소 처리
1
2
3
4
5
6
7
8
9
10
| @objc func sendButtonTapped() {
if let location = selectedLocation {
delegate?.didSelectLocation(location)
}
dismiss(animated: true, completion: nil)
}
@objc func cancelButtonTapped() {
dismiss(animated: true, completion: nil)
}
|
설명: 위치가 선택되었을 경우 delegate를 통해 외부에 전달하고, 현재 화면을 닫음. 취소 시에도 화면을 닫음.
커뮤니티 채널 입장 제한 추가
1. CoreLocation 도입 및 위치 기반 기능 추가
import CoreLocation
추가- 프로퍼티 추가:
1
2
3
| let locationManager: CLLocationManager = CLLocationManager()
var userLocation: CLLocation = CLLocation()
private var currentAddress = ""
|
- CLLocationManagerDelegate 프로토콜 채택 및 관련 메서드 구현
1
2
3
4
5
6
| extension ChannelVC: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
...
}
func getAddress(coordinate: CLLocation) { ... }
}
|
설명: 사용자의 현재 위치를 감지하고, 해당 위치의 행정구역(시/도)을 주소로 받아 currentAddress
에 저장함. 이후 채널 입장 제어에 활용됨.
2. 채널 입장 조건 강화 (지역 일치 여부 확인)
1
2
3
4
5
| if currentAddress == channel.name {
// 기존 로직
} else {
showMessage(title: "지역 오류", message: "현재 속한 지역의 대화방만 입장가능합니다.\n현재 유저님의 속한 지역은 \(currentAddress)입니다.")
}
|
설명: 채널 입장 조건으로 사용자 위치와 채널 이름(지역 이름)이 일치해야만 입장 가능하도록 제한함.
3. 위치 확인 트리거 추가
- viewDidLoad 및 viewWillAppear에 위치 요청 메서드 추가:
- checkUserLocation() 메서드 정의:
1
2
3
4
5
| private func checkUserLocation() {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
|
설명: 뷰 로딩 및 화면 재진입 시마다 사용자 위치를 확인하고 갱신하도록 설정함.
4. 툴바 버튼 제거 (일시적 또는 완전 삭제)
addToolBarItems()
호출 제거- 주석 처리된 상태로 남겨둠
설명: 채널 화면 하단 툴바(로그아웃/채널 생성 버튼)가 더 이상 호출되지 않음. UI 개선 또는 기능 분리 목적 추정.
5. ChatVC 진입 방식 변경 없음
- 유저 타입에 따라 ChatVC로 분기하는 기존 구조 유지
- 다만
지역 일치
여부 확인 이후에만 접근 가능하도록 위치
설명: 기존 분기 구조는 유지하면서 조건만 강화함.