diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift index c869d1e5..100f3964 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift @@ -20,6 +20,7 @@ enum NaviType { case titleWithLeftButton // 뒤로가기 버튼 + 중앙 타이틀 case search // 검색창 case report // 신고 + case editModeForTitleWithLeftButton // 수정하기 페이지일 때 } final class CustomNavigationBar: UIView { @@ -49,7 +50,7 @@ final class CustomNavigationBar: UIView { self.vc = vc self.setUI(type) self.setLayout(type) - self.setAddTarget() + self.setAddTarget(type) self.setDelegate() } @@ -69,8 +70,16 @@ extension CustomNavigationBar { } } - private func setAddTarget() { - self.leftButton.addTarget(self, action: #selector(popToPreviousVC), for: .touchUpInside) + private func setAddTarget(_ type: NaviType) { + self.naviType = type + + switch type { + case .editModeForTitleWithLeftButton: + self.leftButton.addTarget(self, action: #selector(presentToQuitEditAlertVC), for: .touchUpInside) + default: + self.leftButton.addTarget(self, action: #selector(popToPreviousVC), for: .touchUpInside) + } + self.rightButton.addTarget(self, action: #selector(searchLocation), for: .touchUpInside) self.reportButton.addTarget(self, action: #selector(reportLocation), for: .touchUpInside) } @@ -87,25 +96,25 @@ extension CustomNavigationBar { } @discardableResult - func resetLeftButtonAction(_ closure: (() -> Void)? = nil) -> Self { + func resetLeftButtonAction(_ closure: (() -> Void)? = nil, _ type: NaviType) -> Self { self.leftButtonClosure = closure self.leftButton.removeTarget(self, action: nil, for: .touchUpInside) if closure != nil { self.leftButton.addTarget(self, action: #selector(leftButtonDidTap), for: .touchUpInside) } else { - self.setAddTarget() + self.setAddTarget(type) } return self } @discardableResult - func resetRightButtonAction(_ closure: (() -> Void)? = nil) -> Self { + func resetRightButtonAction(_ closure: (() -> Void)? = nil, _ type: NaviType) -> Self { self.rightButtonClosure = closure self.rightButton.removeTarget(self, action: nil, for: .touchUpInside) if closure != nil { self.rightButton.addTarget(self, action: #selector(rightButtonDidTap), for: .touchUpInside) } else { - self.setAddTarget() + self.setAddTarget(type) } return self } @@ -142,13 +151,13 @@ extension CustomNavigationBar { } @discardableResult - func resetReportButtonAction(_ closure: (() -> Void)? = nil) -> Self { + func resetReportButtonAction(_ closure: (() -> Void)? = nil, _ type: NaviType) -> Self { self.reportButtonClosure = closure self.reportButton.removeTarget(self, action: nil, for: .touchUpInside) if closure != nil { self.reportButton.addTarget(self, action: #selector(reportButtonDidTap), for: .touchUpInside) } else { - self.setAddTarget() + self.setAddTarget(type) } return self } @@ -172,6 +181,19 @@ extension CustomNavigationBar { } } + @objc private func presentToQuitEditAlertVC() { + guard let vc = vc else { return } + let quitEditAlertVC = RNAlertVC(description: "러닝 기록 수정을 종료할까요?\n종료 시 수정 내용이 반영되지 않아요.") + + quitEditAlertVC.rightButtonTapAction = { [weak self] in + quitEditAlertVC.dismiss(animated: false) + vc.navigationController?.popViewController(animated: true) + } + + quitEditAlertVC.modalPresentationStyle = .overFullScreen + self.vc?.present(quitEditAlertVC, animated: false, completion: nil) + } + @objc private func searchLocation() { guard let text = textField.text else { return } self.hideKeyboard() @@ -209,12 +231,7 @@ extension CustomNavigationBar { leftTitleLabel.textColor = .g1 leftTitleLabel.isHidden = false case .titleWithLeftButton: - centerTitleLabel.text = "" - centerTitleLabel.font = .h4 - centerTitleLabel.textColor = .g1 - centerTitleLabel.isHidden = false - leftButton.isHidden = false - leftButton.setImage(ImageLiterals.icArrowBack, for: .normal) + setTitleWithLeftButton() case .search: leftButton.setImage(ImageLiterals.icArrowBack, for: .normal) textField.attributedPlaceholder = NSAttributedString(string: "출발지 검색", attributes: [NSAttributedString.Key.foregroundColor: UIColor.g3, NSAttributedString.Key.font: UIFont.b1]) @@ -222,13 +239,23 @@ extension CustomNavigationBar { textField.textColor = .g1 textField.addLeftPadding(width: 2) rightButton.setImage(ImageLiterals.icSearch, for: .normal) - case .report: reportButton.setImage(ImageLiterals.icArrowBack, for: .normal) reportButton.isHidden = false + case .editModeForTitleWithLeftButton: + setTitleWithLeftButton() } } + private func setTitleWithLeftButton() { + centerTitleLabel.text = "" + centerTitleLabel.font = .h4 + centerTitleLabel.textColor = .g1 + centerTitleLabel.isHidden = false + leftButton.isHidden = false + leftButton.setImage(ImageLiterals.icArrowBack, for: .normal) + } + private func setLayout(_ type: NaviType) { switch type { case .title: @@ -239,6 +266,8 @@ extension CustomNavigationBar { setSearchLayout() case .report: setReportButtonLayout() + case .editModeForTitleWithLeftButton: + setTitleWithLeftButtonLayout() } } diff --git a/Runnect-iOS/Runnect-iOS/Network/Router/RecordRouter.swift b/Runnect-iOS/Runnect-iOS/Network/Router/RecordRouter.swift index ed8be11c..5c7f062b 100644 --- a/Runnect-iOS/Runnect-iOS/Network/Router/RecordRouter.swift +++ b/Runnect-iOS/Runnect-iOS/Network/Router/RecordRouter.swift @@ -13,6 +13,7 @@ enum RecordRouter { case recordRunning(param: RunningRecordRequestDto) case getActivityRecordInfo case deleteRecord(recordIdList: [Int]) + case updateRecordTitle(recordId: Int, recordTitle: String) } extension RecordRouter: TargetType { @@ -30,6 +31,8 @@ extension RecordRouter: TargetType { return "/record" case .getActivityRecordInfo: return "/record/user" + case .updateRecordTitle(recordId: let recordId, _): + return "/record/\(recordId)" } } @@ -41,6 +44,8 @@ extension RecordRouter: TargetType { return .get case .deleteRecord: return .put + case .updateRecordTitle: + return .patch } } @@ -56,12 +61,17 @@ extension RecordRouter: TargetType { return .requestPlain case .deleteRecord(let recordIdList): return .requestParameters(parameters: ["recordIdList": recordIdList], encoding: JSONEncoding.default) + case .updateRecordTitle(_, let recordTitle): + do { + return .requestParameters(parameters: ["title": recordTitle], encoding: JSONEncoding.default) + } catch { + fatalError("Encoding 실패")} } } var headers: [String: String]? { switch self { - case .recordRunning, .getActivityRecordInfo, .deleteRecord: + default: return Config.defaultHeader } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingHomeVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingHomeVC.swift index 856bf47c..6e421753 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingHomeVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingHomeVC.swift @@ -33,6 +33,10 @@ final class CourseDrawingHomeVC: UIViewController { self.setLayout() self.setAddTarget() } + + override func viewWillAppear(_ animated: Bool) { + self.hideTabBar(wantsToHide: false) + } } // MARK: - Methods diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift index 007d6d88..8668738d 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift @@ -18,11 +18,15 @@ final class ActivityRecordDetailVC: UIViewController { private let recordProvider = Providers.recordProvider private var recordId: Int? - + + private var isEditMode: Bool = false + + private let courseTitleMaxLength = 20 + // MARK: - UI Components private lazy var navibar = CustomNavigationBar(self, type: .titleWithLeftButton) - + private let moreButton = UIButton(type: .system).then { $0.setImage(ImageLiterals.icMore, for: .normal) $0.tintColor = .g1 @@ -36,11 +40,20 @@ final class ActivityRecordDetailVC: UIViewController { private let mapImageView = UIImageView() private let courseTitleLabel = UILabel().then { - $0.text = "제목" $0.textColor = .g1 $0.font = .h4 } + private let courseTitleTextField = UITextField().then { + $0.attributedPlaceholder = NSAttributedString( + string: "글 제목", + attributes: [.font: UIFont.h4, .foregroundColor: UIColor.g3] + ) + $0.font = .h4 + $0.textColor = .g1 + $0.addLeftPadding(width: 2) + } + private let recordDateInfoView = CourseDetailInfoView(title: "날짜", description: String()) private let recordDepartureInfoView = CourseDetailInfoView(title: "출발지", description: String()) @@ -88,6 +101,11 @@ final class ActivityRecordDetailVC: UIViewController { $0.distribution = .fill } + private lazy var finishEditButton = CustomButton(title: "완료").then { + $0.isHidden = true + $0.isEnabled = false + } + // MARK: - View Life Cycle override func viewDidLoad() { @@ -97,6 +115,8 @@ final class ActivityRecordDetailVC: UIViewController { setUI() setLayout() setAddTarget() + self.setKeyboardNotification() + self.setTapGesture() } } @@ -106,7 +126,9 @@ extension ActivityRecordDetailVC { @objc func moreButtonDidTap() { let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let editAction = UIAlertAction(title: "수정하기", style: .default, handler: {(_: UIAlertAction!) in - //self.navigationController?.pushViewController(courseEditVC, animated: false) + // 수정 모드일 때 + self.isEditMode = true + self.setEditMode() }) let deleteVC = RNAlertVC(description: "러닝 기록을 정말로 삭제하시겠어요?").setButtonTitle("취소", "삭제하기") deleteVC.modalPresentationStyle = .overFullScreen @@ -121,16 +143,54 @@ extension ActivityRecordDetailVC { [ editAction, deleteAction ].forEach { alertController.addAction($0) } present(alertController, animated: true, completion: nil) } + + @objc private func textFieldTextDidChange() { + guard let text = courseTitleTextField.text else { return } + + self.finishEditButton.isEnabled = !text.isEmpty + + if text == self.courseTitleLabel.text { + self.finishEditButton.isEnabled = false + } + + if text.count > courseTitleMaxLength { + let index = text.index(text.startIndex, offsetBy: courseTitleMaxLength) + let newString = text[text.startIndex.. UIStackView { @@ -196,12 +258,33 @@ extension ActivityRecordDetailVC { label.font = .b4 return label } + + // 키보드가 올라오면 scrollView 위치 조정 + private func setKeyboardNotification() { + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillShow), + name: UIResponder.keyboardWillShowNotification, + object: nil) + + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillHide), + name: UIResponder.keyboardWillHideNotification, + object: nil) + } + + // 화면 터치 시 키보드 내리기 + private func setTapGesture() { + let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing)) + tap.cancelsTouchesInView = false + view.addGestureRecognizer(tap) + } } // MARK: - Layout Helpers extension ActivityRecordDetailVC { - private func setUI() { view.backgroundColor = .w1 middleScorollView.backgroundColor = .w1 @@ -302,6 +385,76 @@ extension ActivityRecordDetailVC { make.bottom.equalToSuperview().inset(30) } } + + private func setEditMode() { + self.navibar.isHidden = true + + let editNavibar = CustomNavigationBar(self, type: .editModeForTitleWithLeftButton) + + view.addSubview(editNavibar) + + editNavibar.snp.makeConstraints { make in + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(48) + } + + middleScorollView.snp.makeConstraints { make in + make.top.equalTo(editNavibar.snp.bottom) + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.bottom.equalTo(view.safeAreaLayoutGuide) + } + + mapImageView.snp.remakeConstraints { make in + make.top.equalToSuperview() + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(middleScorollView.snp.width) + } + + self.courseTitleLabel.isHidden = true + + self.courseTitleTextField.text = self.courseTitleLabel.text + + middleScorollView.addSubview(courseTitleTextField) + + courseTitleTextField.snp.makeConstraints { make in + make.top.equalTo(mapImageView.snp.bottom).offset(27) + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(35) + } + + self.finishEditButton.isHidden = false + + firstHorizontalDivideLine.snp.remakeConstraints { make in + make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16) + make.height.equalTo(2) + make.top.equalTo(courseTitleTextField.snp.bottom).offset(5) + } + + recordInfoStackView.snp.makeConstraints { make in + make.top.equalTo(firstHorizontalDivideLine.snp.bottom).offset(20) + make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16) + } + + secondHorizontalDivideLine.snp.remakeConstraints { make in + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(7) + make.top.equalTo(recordInfoStackView.snp.bottom).offset(24) + } + + recordSubInfoStackView.snp.remakeConstraints { make in + make.top.equalTo(secondHorizontalDivideLine.snp.bottom).offset(22) + make.centerX.equalToSuperview() + } + + middleScorollView.addSubview(finishEditButton) + + finishEditButton.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(30) + make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16) + make.top.equalTo(recordSubInfoStackView.snp.bottom).offset(50) + make.height.equalTo(44) + } + } } // MARK: - Network @@ -309,7 +462,6 @@ extension ActivityRecordDetailVC { extension ActivityRecordDetailVC { private func deleteRecord() { guard let recordId = self.recordId else { return } - print(recordId) LoadingIndicator.showLoading() recordProvider.request(.deleteRecord(recordIdList: [recordId])) { [weak self] response in LoadingIndicator.hideLoading() @@ -321,7 +473,34 @@ extension ActivityRecordDetailVC { if 200..<300 ~= status { print("삭제 성공") self.navigationController?.popViewController(animated: false) - + let activityRecordInfoVC = ActivityRecordInfoVC() + activityRecordInfoVC.getActivityRecordInfo() + activityRecordInfoVC.reloadActivityRecordInfoVC() + } + if status >= 400 { + print("400 error") + self.showNetworkFailureToast() + } + case .failure(let error): + print(error.localizedDescription) + self.showNetworkFailureToast() + } + } + } + + private func editRecordTitle() { + guard let recordId = self.recordId else { return } + guard let editRecordTitle = self.courseTitleTextField.text else { return } + LoadingIndicator.showLoading() + recordProvider.request(.updateRecordTitle(recordId: recordId, recordTitle: editRecordTitle)) { [weak self] response in + LoadingIndicator.hideLoading() + guard let self = self else { return } + switch response { + case .success(let result): + print("result:", result) + let status = result.statusCode + if 200..<300 ~= status { + print("제목 수정 성공") } if status >= 400 { print("400 error") diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift index 52f18009..bd2e8288 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift @@ -24,11 +24,11 @@ final class ActivityRecordInfoVC: UIViewController, deleteRecordDelegate { private var activityRecordList = [ActivityRecord]() private var deleteRecordList = [Int]() - + weak var delegate: deleteRecordDelegate? - + private var isEditMode: Bool = false - + // MARK: - UI Components private lazy var navibar = CustomNavigationBar(self, type: .titleWithLeftButton).setTitle("러닝 기록") @@ -65,9 +65,9 @@ final class ActivityRecordInfoVC: UIViewController, deleteRecordDelegate { } // MARK: - View Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) setNavigationBar() setUI() setLayout() @@ -77,11 +77,6 @@ final class ActivityRecordInfoVC: UIViewController, deleteRecordDelegate { getActivityRecordInfo() self.hideTabBar(wantsToHide: true) } - -// override func viewWillAppear(_ animated: Bool) { -// super.viewWillAppear(animated) -// self.reloadActivityRecordInfoVC() -// } } // MARK: - Methods @@ -110,7 +105,6 @@ extension ActivityRecordInfoVC { } func reloadActivityRecordInfoVC() { - self.activityRecordTableView.reloadData() self.editButton.setTitle("편집", for: .normal) self.deleteRecordButton.isHidden = true self.totalNumOfRecordlabel.text = "총 기록 \(self.activityRecordList.count)개" @@ -168,12 +162,12 @@ extension ActivityRecordInfoVC { } @objc func deleteRecordButtonDidTap() { - let deleteVC = RNAlertVC(description: "러닝 기록을 정말로 삭제하시겠어요?").setButtonTitle("취소", "삭제하기") - deleteVC.modalPresentationStyle = .overFullScreen - deleteVC.deleteRecordDelegate = self - self.present(deleteVC, animated: false, completion: nil) - deleteVC.rightButtonTapAction = { [weak self] in - deleteVC.dismiss(animated: false) + let deleteAlertVC = RNAlertVC(description: "러닝 기록을 정말로 삭제하시겠어요?").setButtonTitle("취소", "삭제하기") + deleteAlertVC.modalPresentationStyle = .overFullScreen + deleteAlertVC.deleteRecordDelegate = self + self.present(deleteAlertVC, animated: false, completion: nil) + deleteAlertVC.rightButtonTapAction = { [weak self] in + deleteAlertVC.dismiss(animated: false) } } } @@ -249,17 +243,16 @@ extension ActivityRecordInfoVC: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard tableView.cellForRow(at: indexPath) is ActivityRecordInfoTVC else { return } guard let selectedRecords = tableView.indexPathsForSelectedRows else { return } - let activityRecordDetailVC = ActivityRecordDetailVC() if isEditMode { self.deleteRecordButton.isEnabled = true let countSelectedRows = selectedRecords.count self.deleteRecordButton.setTitle(title: "삭제하기(\(countSelectedRows))") } else { + let activityRecordDetailVC = ActivityRecordDetailVC() tableView.deselectRow(at: indexPath, animated: true) self.deleteRecordButton.setTitle(title: "삭제하기") activityRecordDetailVC.setData(model: activityRecordList[indexPath.row]) - activityRecordDetailVC.setRecordId(recordId: activityRecordList[indexPath.row].id) // 편집 모드가 아닐 때 상세 페이지로 이동 self.navigationController?.pushViewController(activityRecordDetailVC, animated: true) @@ -366,6 +359,7 @@ extension ActivityRecordInfoVC { let status = result.statusCode if 200..<300 ~= status { print("삭제 성공") + self.getActivityRecordInfo() self.reloadActivityRecordInfoVC() } if status >= 400 {