diff --git a/Pinit/Pinit.xcodeproj/project.pbxproj b/Pinit/Pinit.xcodeproj/project.pbxproj index d1570f9..ed5c708 100644 --- a/Pinit/Pinit.xcodeproj/project.pbxproj +++ b/Pinit/Pinit.xcodeproj/project.pbxproj @@ -280,7 +280,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5664C7G97A; + DEVELOPMENT_TEAM = UUYBHY988H; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Pinit/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -308,7 +308,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5664C7G97A; + DEVELOPMENT_TEAM = UUYBHY988H; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Pinit/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/Pinit/Pinit/Service/UseCase.swift b/Pinit/Pinit/Service/UseCase.swift index 40b4974..48a1196 100644 --- a/Pinit/Pinit/Service/UseCase.swift +++ b/Pinit/Pinit/Service/UseCase.swift @@ -116,7 +116,9 @@ final class UseCaseImpl: UseCase { func fetchAllReviewsByPinID(pinID: UUID, completion: @escaping ([ReviewEntity]) -> Void) { dbRepository.fetchReviewsByPinId(pinID: pinID) { items in let reviewEntites = items.compactMap { $0.toReviewEntity() } - completion(reviewEntites) + DispatchQueue.main.async { + completion(reviewEntites) + } } } } diff --git a/Pinit/Pinit/Views/Home/HomeViewController.swift b/Pinit/Pinit/Views/Home/HomeViewController.swift index 71c64d1..91e17a9 100644 --- a/Pinit/Pinit/Views/Home/HomeViewController.swift +++ b/Pinit/Pinit/Views/Home/HomeViewController.swift @@ -56,12 +56,6 @@ class HomeViewController: UIViewController { setupLayout() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - print("HHH") - // loadAnnotations() - } - private func setupMapLocation() { locationmanager.delegate = self mapView.delegate = self @@ -80,19 +74,14 @@ class HomeViewController: UIViewController { mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomAnnotationView.identifier) // mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: "ClusterView") - loadAnnotations(PinEntity.sampleData) + loadAnnotations() } - private func loadAnnotations(_ samepleData: [PinEntity]? = nil) { - if let sampleData = samepleData { // 테스트용 - let annotations = PinEntity.sampleData.map{CustomAnnotation(pinData: $0)} - mapView.addAnnotations(annotations) - } - else { - usecase.fetchAllPins {[weak self] pins in - let annotations = pins.map { CustomAnnotation(pinData: $0) } - self?.mapView.addAnnotations(annotations) - } + private func loadAnnotations() { + usecase.fetchAllPins { pins in + let annotations = pins.map { CustomAnnotation(pinData: $0) } + self.mapView.addAnnotations(annotations) + self.mapView(self.mapView, regionDidChangeAnimated: true) } } @@ -136,6 +125,7 @@ class HomeViewController: UIViewController { $0.size.equalTo(circleButtonSize) } } + } // MARK: MapView Delegate @@ -154,6 +144,7 @@ extension HomeViewController: MKMapViewDelegate { return nil } + private func createCustomAnnotationView(for annotation: CustomAnnotation, in mapView: MKMapView) -> MKAnnotationView { let identifier = CustomAnnotationView.identifier var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? CustomAnnotationView @@ -186,16 +177,15 @@ extension HomeViewController: MKMapViewDelegate { func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { if let annotation = view.annotation as? CustomAnnotation { - print("Selected pin ID: \(annotation.pinData.pin_id)") - // 상세 화면 이동 + presentPinDetailViewController(selected: annotation.pinData) } } func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { let visibleAnnotations = mapView.annotations(in: mapView.visibleMapRect) let visibleMarkers = visibleAnnotations.compactMap { $0 as? CustomAnnotation } // BottomSheet의 CollectionView 업데이트 -// adapter?.data = visibleMarkers.map{ $0.pinData } -// bottomSheet.collectionView.reloadData() + adapter?.data = visibleMarkers.map{ $0.pinData } + bottomSheet.collectionView.reloadData() } } @@ -223,25 +213,50 @@ extension HomeViewController: CLLocationManagerDelegate { // MARK: PinCollectionViewAdapterDelegate extension HomeViewController: PinCollectionViewAdapterDelegate { func selectedItem(selected: PinEntity) { - print("Selected: \(selected)") - // 여기서 화면 이동 - let vc = PinDetailViewController(selected, isPin: true) - vc.sendToBack = {[weak self] entity in - guard let entity else { return } - let annotation = CustomAnnotation(pinData: entity) - self?.mapView.removeAnnotation(annotation) - } - present(vc, animated: true) + presentPinDetailViewController(selected: selected) } func deletedItem(deleted: PinEntity?) { - print("Deleted: \(deleted?.title ?? "empty")") - // 여기서 CoreData 업데이트 guard let deleted = deleted else { return } usecase.deletePin(pinID: deleted.pin_id) - let deletedAnnotation = CustomAnnotation(pinData: deleted) - mapView.removeAnnotation(deletedAnnotation) - // 삭제후 현재 위치의 어노테이션이 뭐가 있는지 다시 로드 + removePinEntity(pinEntity: deleted) + } +} + +// MARK: 화면이동 +extension HomeViewController { + private func presentAddPinViewController(lat: Double, lon: Double) { + let vc = PinEditViewController() + vc.modalPresentationStyle = .fullScreen + vc.pinmode = .create(latitude: lat, longtitude: lon) + vc.isAdded = { pin in + let newAnnotation = CustomAnnotation(pinData: pin) + self.usecase.addPin(pin: pin) + self.mapView.addAnnotation(newAnnotation) + self.mapView(self.mapView, regionDidChangeAnimated: true) + } + present(vc, animated: true) + } + private func presentPinDetailViewController(selected: PinEntity) { + let vc = PinDetailViewController(selected, isPin: true) + vc.deletePinNoti = removePinEntity + vc.updatePinNoti = { before, after in + self.removePinEntity(pinEntity: before) + let newAnnotation = CustomAnnotation(pinData: after) + self.mapView.addAnnotation(newAnnotation) + self.mapView(self.mapView, regionDidChangeAnimated: true) + } + present(vc, animated: true) + } + + private func removePinEntity(pinEntity: PinEntity) { // 클로저용.. + if let annotationToRemove = mapView.annotations.first( + where: { guard let custom = $0 as? CustomAnnotation else { return false } + return custom.pinData.pin_id == pinEntity.pin_id + }) + { + mapView.removeAnnotation(annotationToRemove) + } mapView(mapView, regionDidChangeAnimated: true) } } @@ -254,8 +269,7 @@ extension HomeViewController { showAlertAboutLocation() return } - - print("기록하기 화면으로 이동") + presentAddPinViewController(lat: location.latitude, lon: location.longitude) } @objc private func moveToUserLocation() { diff --git a/Pinit/Pinit/Views/PastPin/PastPinViewController.swift b/Pinit/Pinit/Views/PastPin/PastPinViewController.swift index bfc14fe..116c388 100644 --- a/Pinit/Pinit/Views/PastPin/PastPinViewController.swift +++ b/Pinit/Pinit/Views/PastPin/PastPinViewController.swift @@ -12,7 +12,7 @@ import SnapKit final class PastPinViewController: UIViewController { //MARK: - 모든 PinEntity를 가져옵니다. - let pinData = PinEntity.sampleData + var pinData = PinEntity.sampleData private let usecase: UseCase @@ -138,12 +138,20 @@ extension PastPinViewController : FSCalendarDelegate, FSCalendarDataSource, FSCa extension PastPinViewController : PinCollectionViewAdapterDelegate { func selectedItem(selected: PinEntity) { //화면 이동 - let vc = PinDetailViewController(selected) - self.present(vc, animated: true) + let vc = PinDetailViewController(selected, isPin: true) + vc.deletePinNoti = { pin in + // 여기서 삭제된 핀이 무엇인지 알려주니까 여기서 삭제된 핀 데이터 소스에서 제거하기 + } + vc.updatePinNoti = { before, after in + // 여기서 수정된 핀 비포, 애프터로 나오니까 데이터 소스에서 업데이트 하기 + } + present(vc, animated: true) } func deletedItem(deleted: PinEntity?) { //아이템 삭제 클릭시 - print("deletedItem") + guard let deleted = deleted else { return } + usecase.deletePin(pinID: deleted.pin_id) + self.pinData = pinData.filter{ $0.pin_id != deleted.pin_id } } } diff --git a/Pinit/Pinit/Views/PinDetail/PinDetailHeader.swift b/Pinit/Pinit/Views/PinDetail/PinDetailHeader.swift index da4fed3..fcd4910 100644 --- a/Pinit/Pinit/Views/PinDetail/PinDetailHeader.swift +++ b/Pinit/Pinit/Views/PinDetail/PinDetailHeader.swift @@ -83,9 +83,9 @@ class PinDetailHeader: UIView { return textView }() - private lazy var reviewSectionTitle: UILabel = { + public lazy var reviewSectionTitle: UILabel = { let label = UILabel() - label.text = entity.address == "" ? "방명록" : "리뷰" + label.text = "리뷰" label.font = DesignSystemFont.Pretendard_Bold20.value return label }() diff --git a/Pinit/Pinit/Views/PinDetail/PinDetailViewController.swift b/Pinit/Pinit/Views/PinDetail/PinDetailViewController.swift index f7a5bf6..7f1951c 100644 --- a/Pinit/Pinit/Views/PinDetail/PinDetailViewController.swift +++ b/Pinit/Pinit/Views/PinDetail/PinDetailViewController.swift @@ -18,7 +18,8 @@ final class PinDetailViewController: UIViewController { private var pinEntity: PinEntity private var isPin: Bool private var useCase = DIContainer.usecase - var sendToBack: ((PinEntity?) -> Void)! + var deletePinNoti: ((PinEntity) -> Void)? + var updatePinNoti: ((_ before: PinEntity, _ after: PinEntity) -> Void)? init(_ entity: PinEntity, isPin: Bool) { self.pinEntity = entity @@ -159,7 +160,12 @@ extension PinDetailViewController { } @objc func onCommitButtonTapped() { - var review: ReviewEntity = ReviewEntity(id: UUID(), pinID: pinEntity.pin_id, date: Date(), description: reviewPanelContainer.reviewText.text ?? "") + let review: ReviewEntity = ReviewEntity( + id: UUID(), + pinID: pinEntity.pin_id, + date: Date(), + description: reviewPanelContainer.reviewText.text ?? "" + ) reviewPanelContainer.reviewText.text = "" @@ -175,14 +181,21 @@ extension PinDetailViewController { let editAction = UIAlertAction(title: "수정", style: .default) { _ in print("수정") let vc = PinEditViewController() + vc.pinmode = .edit(PinEntity: self.pinEntity) + vc.isAdded = { pin in + self.useCase.updatePin(pin: pin) + self.updatePinNoti!(self.pinEntity, pin) + self.pinEntity = pin + // 디테일 화면 수정 + } vc.modalPresentationStyle = .fullScreen self.present(vc, animated: true, completion: nil) } - let deleteAction = UIAlertAction(title: "삭제", style: .destructive) {[weak self] _ in + let deleteAction = UIAlertAction(title: "삭제", style: .destructive) { _ in print("삭제") - self?.useCase.deletePin(pinID: (self?.pinEntity.pin_id)!) - self?.sendToBack(self?.pinEntity) - self?.dismiss(animated: true, completion: nil) + self.useCase.deletePin(pinID: (self.pinEntity.pin_id)) + self.deletePinNoti?(self.pinEntity) + self.dismiss(animated: true, completion: nil) } let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil) @@ -239,7 +252,10 @@ extension PinDetailViewController: UITableViewDataSource, UITableViewDelegate { // viewForHeaderInSection func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let header = PinDetailHeader(entity: pinEntity) - if !isPin { header.pinMenuButton.isHidden = true } + if !isPin { + header.pinMenuButton.isHidden = true + header.reviewSectionTitle.text = "방명록" + } header.pinMenuButton.addTarget(self, action: #selector(pinMenuButtonTapped), for: .touchUpInside) return header } diff --git a/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift b/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift index 0ea7c06..089b0e3 100644 --- a/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift +++ b/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift @@ -14,16 +14,30 @@ enum PinMode { case edit(PinEntity : PinEntity) } + + final class PinEditViewController: UIViewController, UITextViewDelegate { - + var pinEntity: PinEntity? var pinmode: PinMode? + var isAdded: ((PinEntity) -> Void)? // 핀추가가 됐을때 호출되는 클로저 (홈에서만 사용) - private var mapView: MKMapView = { - let view = MKMapView() - let center = CLLocationCoordinate2D(latitude: 37.506446, longitude: 126.885397) //중심좌표 - let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)) - view.setRegion(region, animated: true) - return view + public lazy var mapView: MKMapView = { + var map = MKMapView() + var lat = 37.506446 + var long = 126.885397 + let center = CLLocationCoordinate2D(latitude: lat, longitude: long) // San Francisco, CA + let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) + + map.setRegion(region, animated: true) + + let annotation = MKPointAnnotation() + annotation.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long) // San Francisco, CA + annotation.title = "test" + map.addAnnotation(annotation) + map.showsUserLocation = false + map.isUserInteractionEnabled = false + + return map }() private let saveButton : UIButton = { @@ -82,25 +96,20 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { } button.tintColor = UIColor(red: 96/255, green: 99/255, blue: 104/255, alpha: 1) button.imageView?.contentMode = .scaleAspectFit - button.addTarget(PinEditViewController.self, action: #selector(cameraButtonTapped), for: .touchUpInside) + return button }() - private let weatherButton : UIButton = { - let button = UIButton() - button.layer.cornerRadius = 10 - button.setTitle("맑음", for: .normal) - button.setTitleColor(UIColor.black, for: .normal) // 글자색을 검은색으로 변경 - return button + private let weatherImage : UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "01d") + return view }() - private let dateButton : UIButton = { - let button = UIButton() - button.backgroundColor = .clear - button.layer.cornerRadius = 10 - button.setTitle("3월 17일", for: .normal) - button.setTitleColor(UIColor.black, for: .normal) - return button + private let dateLabel : UILabel = { + let label = UILabel() + label.text = "테스트 날짜" + return label }() private let closeButton : UIButton = { @@ -113,19 +122,19 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { } button.tintColor = .black button.alpha = 0.7 // 투명도 50% 설정 - button.addTarget(PinEditViewController.self, action: #selector(dismissButtonTapped), for: .touchUpInside) return button }() - private let keyboardToolBar: UIToolbar = { + private lazy var keyboardToolBar: UIToolbar = { let toolbar = UIToolbar() let flexBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - let doneBarButton = UIBarButtonItem(title: "완료", style: .plain, target: PinEditViewController.self, action: #selector(doneBtnClicked)) + let doneBarButton = UIBarButtonItem(title: "완료", style: .plain, target: self, action: #selector(doneBtnClicked)) toolbar.items = [flexBarButton, doneBarButton] toolbar.sizeToFit() return toolbar }() + // MARK: - viewDidLoad override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white @@ -140,63 +149,95 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { switch pinmode { case let .create(latitude, longtitude): print("\(latitude), \(longtitude)") + + pinEntity = PinEntity(pin_id: UUID(), + title: "", + latitude: latitude, longitude: longtitude, + address: "", + date: Date(), + weather: "", + description: "", + mediaPath: nil) + case let .edit(PinEntity): print(PinEntity) - // titleTextField.text = PinEntity.description + self.pinEntity = PinEntity print("편집 모드입니다") + dateLabel.text = pinEntity?.date.koreanDateString() + titleTextField.text = pinEntity?.title + contentTextView.text = pinEntity?.description case .none: print("?") } } + // MARK: - SetUI private func SetUI() { titleTextField.inputAccessoryView = keyboardToolBar contentTextView.inputAccessoryView = keyboardToolBar contentTextView.delegate = self - self.view.addSubviews(mapView, saveButton, contentTextView, titleTextField, cameraButton, weatherButton, dateButton, closeButton) + closeButton.addTarget(PinEditViewController.self, action: #selector(dismissButtonTapped), for: .touchUpInside) + saveButton.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside) + cameraButton.addTarget(PinEditViewController.self, action: #selector(cameraButtonTapped), for: .touchUpInside) - saveButton.snp.makeConstraints{ - $0.leading.equalToSuperview().offset(130) - $0.top.equalToSuperview().offset(760) - $0.width.equalTo(150) - $0.height.equalTo(55) + self.view.addSubviews(mapView, saveButton, contentTextView, titleTextField, cameraButton, weatherImage, dateLabel, closeButton) + + mapView.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide.snp.top) + $0.width.equalToSuperview() + $0.height.equalToSuperview().dividedBy(4) } - contentTextView.snp.makeConstraints { - $0.leading.equalToSuperview().offset(20) - $0.top.equalTo(titleTextField.snp.bottom).offset(10) - $0.width.equalTo(360) - $0.height.equalTo(150) + + closeButton.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(10) + $0.trailing.equalToSuperview().offset(-10) + $0.width.height.equalTo(40) } - titleTextField.snp.makeConstraints{ - $0.leading.equalToSuperview().offset(20) - $0.top.equalToSuperview().offset(540) - $0.width.equalTo(360) - $0.height.equalTo(40) + + weatherImage.snp.makeConstraints{ + $0.top.equalTo(mapView.snp.bottom).offset(10) + $0.leading.equalTo(view.snp.centerX).offset(100) + $0.height.width.equalTo(30) + } + + dateLabel.snp.makeConstraints{ + $0.top.equalTo(mapView.snp.bottom).offset(10) + $0.leading.equalToSuperview().offset(10) + $0.width.equalTo(180) + $0.height.equalTo(30) } + + + cameraButton.snp.makeConstraints{ $0.centerX.equalToSuperview() - $0.centerY.equalToSuperview() + $0.top.equalTo(dateLabel.snp.bottom).offset(20) $0.width.equalTo(150) $0.height.equalTo(150) } - weatherButton.snp.makeConstraints{ - $0.leading.equalToSuperview().offset(210) - $0.top.equalToSuperview().offset(300) - $0.width.equalTo(180) - $0.height.equalTo(30) + + titleTextField.snp.makeConstraints{ + $0.top.equalTo(cameraButton.snp.bottom).offset(30) + $0.leading.equalToSuperview().offset(20) + $0.trailing.equalToSuperview().offset(-20) + $0.height.equalTo(40) } - dateButton.snp.makeConstraints{ - $0.leading.equalToSuperview().offset(10) - $0.top.equalToSuperview().offset(300) - $0.width.equalTo(180) - $0.height.equalTo(30) + + contentTextView.snp.makeConstraints { + $0.top.equalTo(titleTextField.snp.bottom).offset(10) + $0.leading.trailing.equalToSuperview().inset(20) + $0.bottom.equalTo(saveButton.snp.top).offset(-30) } - closeButton.snp.makeConstraints { - $0.top.equalToSuperview().offset(65) - $0.trailing.equalToSuperview().offset(-5) - $0.width.height.equalTo(40) + + saveButton.snp.makeConstraints{ + $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-20) + $0.centerX.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(60) + $0.height.equalTo(55) } + + } private func SetMap(){ @@ -204,6 +245,13 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { mapView = MKMapView(frame: CGRect(x: 0, y: 60, width: self.view.bounds.width, height: self.view.bounds.height / 4)) } + //MARK: 저장버튼 눌림 + @objc private func saveButtonTapped() { + guard let newPin = pinEntity else { return } + isAdded?(newPin) + dismiss(animated: true) + } + //MARK: 키보드 닫기 @objc func doneBtnClicked() { view.endEditing(true) @@ -280,7 +328,7 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { } } -//MARK: PinEditViewController 내에서 사진 선택 기능을 쉽게 사용 +//MARK: - PinEditViewController 내에서 사진 선택 기능을 쉽게 사용 extension PinEditViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let selectedImage = info[.editedImage] as? UIImage ?? info[.originalImage] as? UIImage { @@ -296,5 +344,7 @@ extension PinEditViewController: UIImagePickerControllerDelegate, UINavigationCo } #Preview{ + PinEditViewController() + }