From 90e602d3e1cc2dec15032f9fb25196a63466d916 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 00:47:19 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[Feat]=20#33=20-=20RunningRecordVC=20?= =?UTF-8?q?=EA=B8=B0=EC=B4=88=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS.xcodeproj/project.pbxproj | 12 ++ .../Extension/UIKit+/UITextField+.swift | 9 + .../Global/Supports/SceneDelegate.swift | 2 +- .../UIComponents/CourseDetailInfoView.swift | 79 ++++++++ .../Global/UIComponents/StatsInfoView.swift | 84 ++++++++ .../CourseDrawing/VC/RunningRecordVC.swift | 182 ++++++++++++++++++ 6 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 Runnect-iOS/Runnect-iOS/Global/UIComponents/CourseDetailInfoView.swift create mode 100644 Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift create mode 100644 Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 2ee617f1..108a6696 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ CE146770296568DC00DCEA1B /* RunTrackingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE14676F296568DC00DCEA1B /* RunTrackingVC.swift */; }; CE14677829658C7200DCEA1B /* Stopwatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE14677729658C7200DCEA1B /* Stopwatch.swift */; }; CE14677A2965A80700DCEA1B /* CustomBottomSheetVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1467792965A80700DCEA1B /* CustomBottomSheetVC.swift */; }; + CE14677C2965C1B100DCEA1B /* RunningRecordVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE14677B2965C1B100DCEA1B /* RunningRecordVC.swift */; }; CE17F02D2961BBA100E1DED0 /* ColorLiterals.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE17F02C2961BBA100E1DED0 /* ColorLiterals.swift */; }; CE17F0332961BEF800E1DED0 /* Pretendard-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = CE17F02F2961BEF800E1DED0 /* Pretendard-Medium.otf */; }; CE17F0342961BEF800E1DED0 /* Pretendard-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = CE17F0302961BEF800E1DED0 /* Pretendard-Bold.otf */; }; @@ -77,6 +78,8 @@ CE665610295D92C200C64E12 /* setTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE66560F295D92C200C64E12 /* setTextLineHeight.swift */; }; CE665612295D92E400C64E12 /* UserDefaultWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE665611295D92E400C64E12 /* UserDefaultWrapper.swift */; }; CE665615295D989A00C64E12 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = CE665614295D989A00C64E12 /* .swiftlint.yml */; }; + CE9291252965C9FB0010959C /* CourseDetailInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */; }; + CE9291272965D0ED0010959C /* StatsInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291262965D0ED0010959C /* StatsInfoView.swift */; }; CEB8416E2962C45300BF8080 /* LocationSearchResultTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB8416D2962C45300BF8080 /* LocationSearchResultTVC.swift */; }; CEB841702963360800BF8080 /* CountDownVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB8416F2963360800BF8080 /* CountDownVC.swift */; }; CEC2A6852961F92C00160BF7 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2A6842961F92C00160BF7 /* CustomButton.swift */; }; @@ -110,6 +113,7 @@ CE14676F296568DC00DCEA1B /* RunTrackingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunTrackingVC.swift; sourceTree = ""; }; CE14677729658C7200DCEA1B /* Stopwatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stopwatch.swift; sourceTree = ""; }; CE1467792965A80700DCEA1B /* CustomBottomSheetVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBottomSheetVC.swift; sourceTree = ""; }; + CE14677B2965C1B100DCEA1B /* RunningRecordVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunningRecordVC.swift; sourceTree = ""; }; CE17F02C2961BBA100E1DED0 /* ColorLiterals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorLiterals.swift; sourceTree = ""; }; CE17F02F2961BEF800E1DED0 /* Pretendard-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Medium.otf"; sourceTree = ""; }; CE17F0302961BEF800E1DED0 /* Pretendard-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Bold.otf"; sourceTree = ""; }; @@ -173,6 +177,8 @@ CE66560F295D92C200C64E12 /* setTextLineHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = setTextLineHeight.swift; sourceTree = ""; }; CE665611295D92E400C64E12 /* UserDefaultWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultWrapper.swift; sourceTree = ""; }; CE665614295D989A00C64E12 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; + CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailInfoView.swift; sourceTree = ""; }; + CE9291262965D0ED0010959C /* StatsInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsInfoView.swift; sourceTree = ""; }; CEB8416D2962C45300BF8080 /* LocationSearchResultTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSearchResultTVC.swift; sourceTree = ""; }; CEB8416F2963360800BF8080 /* CountDownVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountDownVC.swift; sourceTree = ""; }; CEC2A6842961F92C00160BF7 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = ""; }; @@ -360,6 +366,7 @@ CEC2A6912962BE2900160BF7 /* DepartureSearchVC.swift */, CE29D581296402B500F47542 /* CourseDrawingVC.swift */, CEB8416F2963360800BF8080 /* CountDownVC.swift */, + CE14677B2965C1B100DCEA1B /* RunningRecordVC.swift */, ); path = VC; sourceTree = ""; @@ -633,6 +640,8 @@ CEC2A6842961F92C00160BF7 /* CustomButton.swift */, CE0D9FD229648DA300CEB5CD /* CustomAlertVC.swift */, CE1467792965A80700DCEA1B /* CustomBottomSheetVC.swift */, + CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */, + CE9291262965D0ED0010959C /* StatsInfoView.swift */, ); path = UIComponents; sourceTree = ""; @@ -871,6 +880,7 @@ CE665604295D91B100C64E12 /* makeAlert.swift in Sources */, A3BC2F2F2962C40A00198261 /* UploadedCourseInfoVC.swift in Sources */, CE6655EA295D88B200C64E12 /* UITabBar+.swift in Sources */, + CE9291272965D0ED0010959C /* StatsInfoView.swift in Sources */, CEC2A68729629B9B00160BF7 /* SignInVC.swift in Sources */, CE665602295D918000C64E12 /* JsonCoder.swift in Sources */, CE4545CD295D7AF4003201E1 /* TaBarController.swift in Sources */, @@ -896,6 +906,7 @@ CE17F0382961BF8B00E1DED0 /* FontLiterals.swift in Sources */, CE6655E8295D889600C64E12 /* UISwitch+.swift in Sources */, CE5875A029601500005D967E /* Toast.swift in Sources */, + CE14677C2965C1B100DCEA1B /* RunningRecordVC.swift in Sources */, CE6655F6295D90B600C64E12 /* addToolBar.swift in Sources */, CEC2A68A2962ADCD00160BF7 /* RNMapView.swift in Sources */, CE6655F0295D891B00C64E12 /* UITextView+.swift in Sources */, @@ -941,6 +952,7 @@ CEB8416E2962C45300BF8080 /* LocationSearchResultTVC.swift in Sources */, CE6655CA295D84DD00C64E12 /* UserDefaultKeyList.swift in Sources */, CE6655F2295D894D00C64E12 /* UIView+.swift in Sources */, + CE9291252965C9FB0010959C /* CourseDetailInfoView.swift in Sources */, CE665600295D915D00C64E12 /* getClassName.swift in Sources */, A3BC2F2D2962C3F200198261 /* ActivityRecordInfoVC.swift in Sources */, CE6655FC295D90F500C64E12 /* calculatePastTime.swift in Sources */, diff --git a/Runnect-iOS/Runnect-iOS/Global/Extension/UIKit+/UITextField+.swift b/Runnect-iOS/Runnect-iOS/Global/Extension/UIKit+/UITextField+.swift index 0eb4d443..e6209888 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Extension/UIKit+/UITextField+.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Extension/UIKit+/UITextField+.swift @@ -34,4 +34,13 @@ extension UITextField { attributedStr.addAttribute(NSAttributedString.Key.kern, value: spacing, range: NSMakeRange(0, attributedStr.length)) self.attributedText = attributedStr } + + /// 하단에 Border 생성 + func addBottomBorder(height: CGFloat) { + let bottomLine = CALayer() + bottomLine.frame = CGRect(x: 0, y: self.frame.size.height - height, width: self.frame.size.width, height: height) + bottomLine.backgroundColor = UIColor.g5.cgColor + borderStyle = .none + layer.addSublayer(bottomLine) + } } diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift index b1a5c3a4..495cd4dc 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift @@ -16,7 +16,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) - window.rootViewController = TabBarController() + window.rootViewController = RunningRecordVC() self.window = window window.makeKeyAndVisible() } diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CourseDetailInfoView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CourseDetailInfoView.swift new file mode 100644 index 00000000..0390cc0b --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CourseDetailInfoView.swift @@ -0,0 +1,79 @@ +// +// CourseDetailInfoView.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/04. +// + +import UIKit + +final class CourseDetailInfoView: UIView { + + // MARK: - UI Components + + private let leftImageView = UIImageView().then { + $0.image = ImageLiterals.icStar + } + + private let titleLabel = UILabel().then { + $0.font = .b5 + $0.textColor = .g1 + } + + private let descriptionLabel = UILabel().then { + $0.font = .b6 + $0.textColor = .g1 + } + + // MARK: - initialization + + init(title: String, description: String) { + super.init(frame: .zero) + self.setUI(title: title, description: description) + self.setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension CourseDetailInfoView { + @discardableResult + func setDescriptionText(description: String) -> Self { + self.descriptionLabel.text = description + return self + } +} + +// MARK: - UI & Layout + +extension CourseDetailInfoView { + private func setUI(title: String, description: String) { + self.backgroundColor = .w1 + self.titleLabel.text = title + self.descriptionLabel.text = description + } + + private func setLayout() { + self.addSubviews(leftImageView, titleLabel, descriptionLabel) + + leftImageView.snp.makeConstraints { make in + make.top.leading.bottom.equalToSuperview() + make.width.equalTo(leftImageView.snp.height) + } + + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalTo(leftImageView.snp.trailing).offset(9) + } + + descriptionLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalTo(titleLabel.snp.leading).offset(57) + make.trailing.greaterThanOrEqualToSuperview() + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift new file mode 100644 index 00000000..00ec2840 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift @@ -0,0 +1,84 @@ +// +// StatsInfoView.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/05. +// + +import UIKit + +final class StatsInfoView: UIView { + + // MARK: - UI Components + + private let titleLabel = UILabel().then { + $0.font = .b4 + $0.textColor = .g2 + } + + private let statsLabel = UILabel().then { + $0.font = .h3 + $0.textColor = .g1 + } + + private lazy var statsContainerStackView = UIStackView( + arrangedSubviews: [titleLabel, statsLabel] + ).then { + $0.axis = .vertical + $0.alignment = .center + $0.spacing = 9 + } + + // MARK: - initialization + + init(title: String, stats: String) { + super.init(frame: .zero) + self.setUI(title: title, stats: stats) + self.setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension StatsInfoView { + /// statsLabel의 text를 attributedString으로 변경 (기본 값은 Km) + @discardableResult + public func setAttributedStats(stats: String, unit: String = " Km") -> Self { + let attributedString = NSMutableAttributedString( + string: stats, + attributes: [.font: UIFont.h3, .foregroundColor: UIColor.g1] + ) + + attributedString.append( + NSAttributedString( + string: unit, + attributes: [.font: UIFont.b4, .foregroundColor: UIColor.g2] + ) + ) + + self.statsLabel.attributedText = attributedString + return self + } +} + +// MARK: - UI & Layout + +extension StatsInfoView { + private func setUI(title: String, stats: String) { + self.backgroundColor = .w1 + self.titleLabel.text = title + self.statsLabel.text = stats + } + + private func setLayout() { + self.addSubviews(statsContainerStackView) + + statsContainerStackView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift new file mode 100644 index 00000000..c504399a --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift @@ -0,0 +1,182 @@ +// +// RunningRecordVC.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/04. +// + +import UIKit + +final class RunningRecordVC: UIViewController { + + // MARK: - UI Components + + private lazy var naviBar = CustomNavigationBar(self, type: .titleWithLeftButton) + .setTitle("러닝 기록") + + private let scrollView = UIScrollView() + + private let contentView = UIView() + + private let courseImageView = UIImageView().then { + $0.backgroundColor = .gray + $0.contentMode = .scaleAspectFill + } + + 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 dateInfoView = CourseDetailInfoView(title: "날짜", description: "출발 날짜") + + private let departureInfoView = CourseDetailInfoView(title: "출발지", description: "출발지 주소") + + private let dividerView = UIView().then { + $0.backgroundColor = .g5 + } + + private let distanceStatsView = StatsInfoView(title: "거리", stats: "0.0 Km").setAttributedStats(stats: "0.0") + private let totalTimeStatsView = StatsInfoView(title: "이동 시간", stats: "00:00:00") + private let averagePaceStatsView = StatsInfoView(title: "평균 페이스", stats: "0'00'") + private let verticalDividerView = UIView().then { + $0.backgroundColor = .g2 + } + private let verticalDividerView2 = UIView().then { + $0.backgroundColor = .g2 + } + + private lazy var statsContainerStackView = UIStackView( + arrangedSubviews: [distanceStatsView, + verticalDividerView, + totalTimeStatsView, + verticalDividerView2, + averagePaceStatsView] + ).then { + $0.spacing = 25 + } + + private let saveButton = CustomButton(title: "저장하기") + + // MARK: - View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.setUI() + self.setLayout() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(true) + self.setTextFieldBottomBorder() + } +} + +// MARK: - Methods + +extension RunningRecordVC { + +} + +// MARK: - UI & Layout + +extension RunningRecordVC { + private func setUI() { + view.backgroundColor = .w1 + } + + private func setLayout() { + view.addSubviews(naviBar, scrollView, saveButton) + scrollView.addSubviews(contentView) + + setContentViewLayout() + + naviBar.snp.makeConstraints { make in + make.leading.top.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(48) + } + + scrollView.snp.makeConstraints { make in + make.top.equalTo(naviBar.snp.bottom) + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.bottom.equalTo(view.safeAreaLayoutGuide).inset(91) + } + + contentView.snp.makeConstraints { make in + make.edges.equalTo(scrollView.contentLayoutGuide) + make.width.equalTo(scrollView.snp.width) + make.height.greaterThanOrEqualTo(scrollView) + } + + saveButton.snp.makeConstraints { make in + make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16) + make.bottom.equalToSuperview().inset(34) + make.height.equalTo(44) + } + } + + private func setContentViewLayout() { + contentView.addSubviews( + courseImageView, + courseTitleTextField, + dateInfoView, + departureInfoView, + dividerView, + statsContainerStackView + ) + + courseImageView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() + make.height.equalTo(courseImageView.snp.width) + } + + courseTitleTextField.snp.makeConstraints { make in + make.top.equalTo(courseImageView.snp.bottom).offset(27) + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(35) + } + + dateInfoView.snp.makeConstraints { make in + make.top.equalTo(courseTitleTextField.snp.bottom).offset(22) + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(16) + } + + departureInfoView.snp.makeConstraints { make in + make.top.equalTo(dateInfoView.snp.bottom).offset(6) + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(16) + } + + dividerView.snp.makeConstraints { make in + make.top.equalTo(departureInfoView.snp.bottom).offset(34) + make.leading.trailing.equalToSuperview() + make.height.equalTo(7) + } + + verticalDividerView.snp.makeConstraints { make in + make.height.equalTo(44) + make.width.equalTo(0.5) + } + + verticalDividerView2.snp.makeConstraints { make in + make.height.equalTo(44) + make.width.equalTo(0.5) + } + + statsContainerStackView.snp.makeConstraints { make in + make.top.equalTo(dividerView.snp.bottom).offset(25) + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(25) + } + } + + private func setTextFieldBottomBorder() { + courseTitleTextField.addBottomBorder(height: 2) + } +} From 3dad36f9c6bdead42088997f05c6430c6446985b Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 01:18:44 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[Feat]=20#30=20-=20=EB=9F=AC=EB=8B=9D=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/UIComponents/StatsInfoView.swift | 8 +- .../CourseDrawing/VC/RunningRecordVC.swift | 75 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift index 00ec2840..69295e3a 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/StatsInfoView.swift @@ -47,7 +47,7 @@ final class StatsInfoView: UIView { extension StatsInfoView { /// statsLabel의 text를 attributedString으로 변경 (기본 값은 Km) @discardableResult - public func setAttributedStats(stats: String, unit: String = " Km") -> Self { + func setAttributedStats(stats: String, unit: String = " Km") -> Self { let attributedString = NSMutableAttributedString( string: stats, attributes: [.font: UIFont.h3, .foregroundColor: UIColor.g1] @@ -63,6 +63,12 @@ extension StatsInfoView { self.statsLabel.attributedText = attributedString return self } + + @discardableResult + func setStats(stats: String) -> Self { + self.statsLabel.text = stats + return self + } } // MARK: - UI & Layout diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift index c504399a..f74b358f 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift @@ -9,6 +9,10 @@ import UIKit final class RunningRecordVC: UIViewController { + // MARK: - Properties + + private let courseTitleMaxLength = 20 + // MARK: - UI Components private lazy var naviBar = CustomNavigationBar(self, type: .titleWithLeftButton) @@ -62,6 +66,7 @@ final class RunningRecordVC: UIViewController { } private let saveButton = CustomButton(title: "저장하기") + .setEnabled(false) // MARK: - View Life Cycle @@ -69,6 +74,9 @@ final class RunningRecordVC: UIViewController { super.viewDidLoad() self.setUI() self.setLayout() + self.setAddTarget() + self.setKeyboardNotification() + self.setTapGesture() } override func viewDidAppear(_ animated: Bool) { @@ -80,7 +88,74 @@ final class RunningRecordVC: UIViewController { // MARK: - Methods extension RunningRecordVC { + private func setAddTarget() { + self.courseTitleTextField.addTarget(self, action: #selector(textFieldTextDidChange), for: .editingChanged) + } + + // 키보드가 올라오면 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) + } + + func setData(distance: String, totalTime: String, averagePace: String) { + self.distanceStatsView.setStats(stats: distance) + self.totalTimeStatsView.setStats(stats: totalTime) + self.averagePaceStatsView.setStats(stats: averagePace) + } +} + +// MARK: - @objc Function + +extension RunningRecordVC { + @objc private func textFieldTextDidChange() { + guard let text = courseTitleTextField.text else { return } + + saveButton.isEnabled = !text.isEmpty + + if text.count > courseTitleMaxLength { + let index = text.index(text.startIndex, offsetBy: courseTitleMaxLength) + let newString = text[text.startIndex.. Date: Thu, 5 Jan 2023 01:35:10 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[Feat]=20#33=20-=20RNTimeFormatter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS.xcodeproj/project.pbxproj | 4 ++++ .../Global/Supports/SceneDelegate.swift | 2 +- .../Global/Utils/RNTimeFormatter.swift | 22 +++++++++++++++++++ .../RunTracking/VC/RunTrackingVC.swift | 16 +++++--------- 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 108a6696..f16f42d7 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ CE665615295D989A00C64E12 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = CE665614295D989A00C64E12 /* .swiftlint.yml */; }; CE9291252965C9FB0010959C /* CourseDetailInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */; }; CE9291272965D0ED0010959C /* StatsInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291262965D0ED0010959C /* StatsInfoView.swift */; }; + CE9291292965E01D0010959C /* RNTimeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9291282965E01D0010959C /* RNTimeFormatter.swift */; }; CEB8416E2962C45300BF8080 /* LocationSearchResultTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB8416D2962C45300BF8080 /* LocationSearchResultTVC.swift */; }; CEB841702963360800BF8080 /* CountDownVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB8416F2963360800BF8080 /* CountDownVC.swift */; }; CEC2A6852961F92C00160BF7 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2A6842961F92C00160BF7 /* CustomButton.swift */; }; @@ -179,6 +180,7 @@ CE665614295D989A00C64E12 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; CE9291242965C9FB0010959C /* CourseDetailInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailInfoView.swift; sourceTree = ""; }; CE9291262965D0ED0010959C /* StatsInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsInfoView.swift; sourceTree = ""; }; + CE9291282965E01D0010959C /* RNTimeFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNTimeFormatter.swift; sourceTree = ""; }; CEB8416D2962C45300BF8080 /* LocationSearchResultTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSearchResultTVC.swift; sourceTree = ""; }; CEB8416F2963360800BF8080 /* CountDownVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountDownVC.swift; sourceTree = ""; }; CEC2A6842961F92C00160BF7 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = ""; }; @@ -588,6 +590,7 @@ CEC2A68F2962B06C00160BF7 /* convertLocationObject.swift */, CE29D583296416D800F47542 /* caculateStatusBarHeight.swift */, CE14677729658C7200DCEA1B /* Stopwatch.swift */, + CE9291282965E01D0010959C /* RNTimeFormatter.swift */, ); path = Utils; sourceTree = ""; @@ -895,6 +898,7 @@ CE665606295D91C500C64E12 /* makeVibrate.swift in Sources */, CE66560A295D924A00C64E12 /* Result+.swift in Sources */, CE66560E295D92A500C64E12 /* setStatusBarBackgroundColor.swift in Sources */, + CE9291292965E01D0010959C /* RNTimeFormatter.swift in Sources */, CE6655D7295D86F900C64E12 /* String+.swift in Sources */, CE58759E29601476005D967E /* LoadingIndicator.swift in Sources */, CE5875A2296015A2005D967E /* NetworkLoggerPlugin.swift in Sources */, diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift index 495cd4dc..b1a5c3a4 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift @@ -16,7 +16,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) - window.rootViewController = RunningRecordVC() + window.rootViewController = TabBarController() self.window = window window.makeKeyAndVisible() } diff --git a/Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift b/Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift new file mode 100644 index 00000000..46aa7212 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift @@ -0,0 +1,22 @@ +// +// RNTimeFormatter.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/05. +// + +import Foundation + +class RNTimeFormatter { + static func secondsToHHMMSS(seconds: Int) -> String { + let interval = seconds + + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.hour, .minute, .second] + formatter.unitsStyle = .positional + formatter.zeroFormattingBehavior = .pad + + let formattedString = formatter.string(from: TimeInterval(interval))! + return formattedString + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index 3a1c7c2b..8524558c 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -17,6 +17,7 @@ final class RunTrackingVC: UIViewController { private let stopwatch = Stopwatch() private var cancelBag = CancelBag() var totalTime: Int = 0 + var distance: String = "0.0" // MARK: - UI Components @@ -80,7 +81,7 @@ final class RunTrackingVC: UIViewController { } private let timeStatsLabel = UILabel().then { - $0.text = "00:00" + $0.text = "00:00:00" $0.font = .h1 $0.textColor = .g1 } @@ -131,6 +132,7 @@ extension RunTrackingVC { func makePath(locations: [NMGLatLng], distance: String) { self.mapView.makeMarkersWithStartMarker(at: locations) self.totalDistanceLabel.attributedText = makeAttributedLabelForDistance(distance: distance) + self.distance = distance } private func setAddTarget() { @@ -165,17 +167,9 @@ extension RunTrackingVC { } private func setTimeLabel(with totalSeconds: Int) { - var minutes: String = "\(totalSeconds / 60)" - if minutes.count == 1 { - minutes = "0\(minutes)" - } - - var seconds: String = "\(totalSeconds % 60)" - if seconds.count == 1 { - seconds = "0\(seconds)" - } + let formattedString = RNTimeFormatter.secondsToHHMMSS(seconds: totalSeconds) - timeStatsLabel.text = "\(minutes):\(seconds)" + timeStatsLabel.text = formattedString } } From 2eb91eb602be813315e5052582b03d5e33d273ee Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 01:56:28 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[Feat]=20#33=20-=20RunningRecordVC=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/Utils/RNTimeFormatter.swift | 11 +++++++++++ .../CourseDrawing/VC/RunningRecordVC.swift | 4 ++-- .../RunTracking/VC/RunTrackingVC.swift | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift b/Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift index 46aa7212..30eb9e92 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Utils/RNTimeFormatter.swift @@ -19,4 +19,15 @@ class RNTimeFormatter { let formattedString = formatter.string(from: TimeInterval(interval))! return formattedString } + + static func getCurrentTimeToString(date: Date) -> String { + let formatter = DateFormatter() + + formatter.locale = Locale(identifier: "ko_kr") + formatter.timeZone = TimeZone(abbreviation: "KST") + + formatter.dateFormat = "yyyy.MM.dd HH:mm" + + return formatter.string(from: date) + } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift index f74b358f..07dd7340 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift @@ -37,7 +37,7 @@ final class RunningRecordVC: UIViewController { $0.addLeftPadding(width: 2) } - private let dateInfoView = CourseDetailInfoView(title: "날짜", description: "출발 날짜") + private let dateInfoView = CourseDetailInfoView(title: "날짜", description: RNTimeFormatter.getCurrentTimeToString(date: Date())) private let departureInfoView = CourseDetailInfoView(title: "출발지", description: "출발지 주소") @@ -115,7 +115,7 @@ extension RunningRecordVC { } func setData(distance: String, totalTime: String, averagePace: String) { - self.distanceStatsView.setStats(stats: distance) + self.distanceStatsView.setAttributedStats(stats: distance) self.totalTimeStatsView.setStats(stats: totalTime) self.averagePaceStatsView.setStats(stats: averagePace) } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index 8524558c..b00895d0 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -171,6 +171,18 @@ extension RunTrackingVC { timeStatsLabel.text = formattedString } + + private func pushToRunningRecordVC() { + guard let distance = Float(self.distance) else { return } + let averagePaceSeconds = Int(Float(self.totalTime) / distance) + let formatedAveragePace = "\(averagePaceSeconds / 60)'\(averagePaceSeconds % 60)''" + + let runningRecordVC = RunningRecordVC() + runningRecordVC.setData(distance: self.distance, + totalTime: RNTimeFormatter.secondsToHHMMSS(seconds: self.totalTime), + averagePace: formatedAveragePace) + self.navigationController?.pushViewController(runningRecordVC, animated: true) + } } // MARK: - @objc Function @@ -184,6 +196,13 @@ extension RunTrackingVC { stopwatch.isRunning.toggle() let bottomSheetVC = CustomBottomSheetVC() bottomSheetVC.modalPresentationStyle = .overFullScreen + + bottomSheetVC.completeButtonTapped.sink { [weak self] _ in + guard let self = self else { return } + self.pushToRunningRecordVC() + bottomSheetVC.dismiss(animated: true) + }.store(in: cancelBag) + self.present(bottomSheetVC, animated: true) } } From ef4b25b6b2f017b19e28407c1340586692fd4b1c Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 02:05:57 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[Chore]=20#33=20-=20stopwatch=20=EC=9E=91?= =?UTF-8?q?=EB=8F=99=20=EC=8B=9C=EC=A0=90=EC=9D=84=20viewWillAppear?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/RunTracking/VC/RunTrackingVC.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index b00895d0..f2bec968 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -122,6 +122,10 @@ final class RunTrackingVC: UIViewController { self.setUI() self.setLayout() self.setAddTarget() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) self.bindStopwatch() } } From fe2571db1a87a6756644d09940f6d43c808556dd Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 03:26:04 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[Feat]=20#33=20-=20=EC=BD=94=EC=8A=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CourseDrawing/VC/RunningRecordVC.swift | 3 ++- .../Presentation/RunTracking/VC/RunTrackingVC.swift | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift index 07dd7340..8357ebe3 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift @@ -114,10 +114,11 @@ extension RunningRecordVC { view.addGestureRecognizer(tap) } - func setData(distance: String, totalTime: String, averagePace: String) { + func setData(distance: String, totalTime: String, averagePace: String, pathImage: UIImage?) { self.distanceStatsView.setAttributedStats(stats: distance) self.totalTimeStatsView.setStats(stats: totalTime) self.averagePaceStatsView.setStats(stats: averagePace) + self.courseImageView.image = pathImage } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index f2bec968..e5dab9ca 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -18,6 +18,7 @@ final class RunTrackingVC: UIViewController { private var cancelBag = CancelBag() var totalTime: Int = 0 var distance: String = "0.0" + var pathImage: UIImage? // MARK: - UI Components @@ -122,6 +123,7 @@ final class RunTrackingVC: UIViewController { self.setUI() self.setLayout() self.setAddTarget() + self.bindMapView() } override func viewWillAppear(_ animated: Bool) { @@ -137,6 +139,7 @@ extension RunTrackingVC { self.mapView.makeMarkersWithStartMarker(at: locations) self.totalDistanceLabel.attributedText = makeAttributedLabelForDistance(distance: distance) self.distance = distance + self.mapView.getPathImage() } private func setAddTarget() { @@ -170,6 +173,14 @@ extension RunTrackingVC { }.store(in: cancelBag) } + private func bindMapView() { + mapView.pathImage.sink { [weak self] image in + guard let self = self else { return } + self.pathImage = image + print(image) + }.store(in: cancelBag) + } + private func setTimeLabel(with totalSeconds: Int) { let formattedString = RNTimeFormatter.secondsToHHMMSS(seconds: totalSeconds) @@ -184,7 +195,7 @@ extension RunTrackingVC { let runningRecordVC = RunningRecordVC() runningRecordVC.setData(distance: self.distance, totalTime: RNTimeFormatter.secondsToHHMMSS(seconds: self.totalTime), - averagePace: formatedAveragePace) + averagePace: formatedAveragePace, pathImage: pathImage) self.navigationController?.pushViewController(runningRecordVC, animated: true) } } From c1703bc63d4e6583499731684b834e47641da325 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 03:59:56 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[Feat]=20#33=20-=20=EC=A7=80=EB=8F=84=20?= =?UTF-8?q?=EC=BA=A1=EC=B3=90=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIComponents/MapView/RNMapView.swift | 43 ++++++++++++------- .../CourseDrawing/VC/RunningRecordVC.swift | 2 +- .../RunTracking/VC/RunTrackingVC.swift | 7 +-- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift index a571f326..76a819d2 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift @@ -37,6 +37,10 @@ final class RNMapView: UIView { private var bottomPadding: CGFloat = 0 private let locationOverlayIcon = NMFOverlayImage(image: ImageLiterals.icLocationOverlay) + private lazy var dummyMap = RNMapView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)).then { + $0.isUserInteractionEnabled = false + } + // MARK: - UI Components let map = NMFNaverMapView() @@ -137,15 +141,32 @@ extension RNMapView { /// NMGLatLng 어레이를 받아서 첫 위치를 startMarker로 설정하고 나머지를 일반 마커로 생성 @discardableResult - func makeMarkersWithStartMarker(at locations: [NMGLatLng]) -> Self { + func makeMarkersWithStartMarker(at locations: [NMGLatLng], willCapture: Bool) -> Self { if locations.count < 2 { return self } - makeStartMarker(at: locations[0], withCameraMove: true) + makeStartMarker(at: locations[0], withCameraMove: willCapture) locations[1...].forEach { location in makeMarker(at: location) } + + if willCapture { + makeDummyMarkerAndCameraMove(at: locations) + } + return self } + /// 캡처를 위한 좌표 설정 및 카메라 이동 + func makeDummyMarkerAndCameraMove(at locations: [NMGLatLng]) { + addSubview(dummyMap) + sendSubviewToBack(dummyMap) + dummyMap.makeMarkersWithStartMarker(at: locations, willCapture: false) + let bounds = makeMBR(at: locations) + let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: 0) + cameraUpdate.animation = .none + dummyMap.map.mapView.moveCamera(cameraUpdate) + dummyMap.map.mapView.zoomLevel -= 1 + } + /// 사용자 위치로 카메라 이동 @discardableResult func moveToUserLocation() -> Self { @@ -218,25 +239,15 @@ extension RNMapView { /// 경로 뷰를 UIImage로 변환하여 pathImage에 send func getPathImage() { - let bounds = makeMBR() - let dummyMap = RNMapView(frame: CGRect(x: 50, y: 50, width: 300, height: 250)) - .makeMarkersWithStartMarker(at: self.markersLatLngs) - addSubview(dummyMap) - sendSubviewToBack(dummyMap) - let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: 150) - cameraUpdate.animation = .none - dummyMap.map.mapView.moveCamera(cameraUpdate) - - DispatchQueue.main.asyncAfter(deadline: .now()+1) { - self.pathImage.send(UIImage(view: dummyMap.map.mapView)) - } + bringSubviewToFront(dummyMap) + self.pathImage.send(UIImage(view: dummyMap.map.mapView)) } /// 바운더리(MBR) 생성 - func makeMBR() -> NMGLatLngBounds { + func makeMBR(at locations: [NMGLatLng]) -> NMGLatLngBounds { var latitudes = [Double]() var longitudes = [Double]() - self.markersLatLngs.forEach { latLng in + locations.forEach { latLng in latitudes.append(latLng.lat) longitudes.append(latLng.lng) } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift index 8357ebe3..b524114d 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/RunningRecordVC.swift @@ -24,7 +24,7 @@ final class RunningRecordVC: UIViewController { private let courseImageView = UIImageView().then { $0.backgroundColor = .gray - $0.contentMode = .scaleAspectFill + $0.contentMode = .scaleToFill } private let courseTitleTextField = UITextField().then { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index e5dab9ca..188af5d3 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -136,10 +136,9 @@ final class RunTrackingVC: UIViewController { extension RunTrackingVC { func makePath(locations: [NMGLatLng], distance: String) { - self.mapView.makeMarkersWithStartMarker(at: locations) + self.mapView.makeMarkersWithStartMarker(at: locations, willCapture: true) self.totalDistanceLabel.attributedText = makeAttributedLabelForDistance(distance: distance) self.distance = distance - self.mapView.getPathImage() } private func setAddTarget() { @@ -177,7 +176,7 @@ extension RunTrackingVC { mapView.pathImage.sink { [weak self] image in guard let self = self else { return } self.pathImage = image - print(image) + LoadingIndicator.hideLoading() }.store(in: cancelBag) } @@ -208,6 +207,8 @@ extension RunTrackingVC { } @objc private func runningCompleteButtonDidTap() { + LoadingIndicator.showLoading() + self.mapView.getPathImage() stopwatch.isRunning.toggle() let bottomSheetVC = CustomBottomSheetVC() bottomSheetVC.modalPresentationStyle = .overFullScreen From a90859102b0c237e646e194b23ba25469f9d129d Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 04:10:37 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[Feat]=20#33=20-=20=EB=8D=94=EB=AF=B8=20?= =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EC=A4=8C=20=EB=A0=88=EB=B2=A8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/UIComponents/MapView/RNMapView.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift index 76a819d2..f52ebafa 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift @@ -161,10 +161,15 @@ extension RNMapView { sendSubviewToBack(dummyMap) dummyMap.makeMarkersWithStartMarker(at: locations, willCapture: false) let bounds = makeMBR(at: locations) - let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: 0) + let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: 100) cameraUpdate.animation = .none - dummyMap.map.mapView.moveCamera(cameraUpdate) - dummyMap.map.mapView.zoomLevel -= 1 + dummyMap.map.mapView.moveCamera(cameraUpdate) { isCancelled in + if isCancelled { + print("카메라 이동 취소") + } else { + self.dummyMap.map.mapView.zoomLevel -= 1 + } + } } /// 사용자 위치로 카메라 이동 @@ -239,7 +244,6 @@ extension RNMapView { /// 경로 뷰를 UIImage로 변환하여 pathImage에 send func getPathImage() { - bringSubviewToFront(dummyMap) self.pathImage.send(UIImage(view: dummyMap.map.mapView)) } From b208cfc09c48d9adf30d6afc8789f2c34777dcd5 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 15:00:44 +0900 Subject: [PATCH 09/12] =?UTF-8?q?[Feat]=20#33=20-=20=EC=A7=80=EB=8F=84=20?= =?UTF-8?q?=EC=BA=A1=EC=B3=90=20=EC=8B=9C=EC=A0=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/UIComponents/MapView/RNMapView.swift | 14 +++++++++++--- .../RunTracking/VC/RunTrackingVC.swift | 5 ++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift index f52ebafa..c6ffa974 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift @@ -19,7 +19,7 @@ final class RNMapView: UIView { @Published var pathDistance: Double = 0 @Published var markerCount = 0 - let pathImage = PassthroughSubject() + let pathImage = PassthroughSubject() var cancelBag = Set() let locationManager = CLLocationManager() @@ -168,6 +168,9 @@ extension RNMapView { print("카메라 이동 취소") } else { self.dummyMap.map.mapView.zoomLevel -= 1 + DispatchQueue.main.asyncAfter(deadline: .now()+1) { + self.makePathImage() + } } } } @@ -242,11 +245,16 @@ extension RNMapView { return pathDistance } - /// 경로 뷰를 UIImage로 변환하여 pathImage에 send - func getPathImage() { + /// 더미 뷰를 UIImage로 변환하여 pathImage에 send + func makePathImage() { self.pathImage.send(UIImage(view: dummyMap.map.mapView)) } + /// 현재 시점까지의 마커들을 캡쳐하여 pahImage에 send + func capturePathImage() { + makeDummyMarkerAndCameraMove(at: self.markersLatLngs) + } + /// 바운더리(MBR) 생성 func makeMBR(at locations: [NMGLatLng]) -> NMGLatLngBounds { var latitudes = [Double]() diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index 188af5d3..7d77ad41 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -176,7 +176,7 @@ extension RunTrackingVC { mapView.pathImage.sink { [weak self] image in guard let self = self else { return } self.pathImage = image - LoadingIndicator.hideLoading() + print("이미지 수신 완료") }.store(in: cancelBag) } @@ -187,6 +187,7 @@ extension RunTrackingVC { } private func pushToRunningRecordVC() { + guard let pathImage = pathImage else { return } guard let distance = Float(self.distance) else { return } let averagePaceSeconds = Int(Float(self.totalTime) / distance) let formatedAveragePace = "\(averagePaceSeconds / 60)'\(averagePaceSeconds % 60)''" @@ -207,8 +208,6 @@ extension RunTrackingVC { } @objc private func runningCompleteButtonDidTap() { - LoadingIndicator.showLoading() - self.mapView.getPathImage() stopwatch.isRunning.toggle() let bottomSheetVC = CustomBottomSheetVC() bottomSheetVC.modalPresentationStyle = .overFullScreen From 551d1af56c64e16b056f24983bf15bfa94a612a0 Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 15:18:26 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[Feat]=20#33=20-=20CourseDrawingVC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A7=80=EB=8F=84=20=EC=BA=A1=EC=B3=90=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIComponents/MapView/RNMapView.swift | 4 +- .../CourseDrawing/VC/CourseDrawingVC.swift | 49 ++++++++++++------- .../RunTracking/VC/RunTrackingVC.swift | 2 +- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift index c6ffa974..c08ccef5 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift @@ -168,8 +168,10 @@ extension RNMapView { print("카메라 이동 취소") } else { self.dummyMap.map.mapView.zoomLevel -= 1 - DispatchQueue.main.asyncAfter(deadline: .now()+1) { + LoadingIndicator.showLoading() + DispatchQueue.main.asyncAfter(deadline: .now()+3) { self.makePathImage() + LoadingIndicator.hideLoading() } } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift index d253778d..d5c9376a 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift @@ -11,6 +11,7 @@ import Combine final class CourseDrawingVC: UIViewController { // MARK: - Properties + var pathImage: UIImage? private var cancelBag = CancelBag() @@ -113,36 +114,30 @@ extension CourseDrawingVC { } private func bindMapView() { - mapView.$pathDistance.sink { distance in + mapView.$pathDistance.sink { [weak self] distance in + guard let self = self else { return } let kilometers = String(format: "%.1f", distance/1000) self.distanceLabel.text = kilometers }.store(in: cancelBag) mapView.$markerCount.sink { [weak self] count in - self?.completeButton.setEnabled(count >= 2) - self?.undoButton.isEnabled = (count >= 2) + guard let self = self else { return } + self.completeButton.setEnabled(count >= 2) + self.undoButton.isEnabled = (count >= 2) + }.store(in: cancelBag) + + mapView.pathImage.sink { [weak self] image in + guard let self = self else { return } + self.pathImage = image + self.presentAlertVC() }.store(in: cancelBag) } private func setNavigationGesture(_ enabled: Bool) { navigationController?.interactivePopGestureRecognizer?.isEnabled = enabled } -} - -// MARK: - @objc Function - -extension CourseDrawingVC { - @objc private func decideDepartureButtonDidTap() { - showHiddenViews(withDuration: 0.7) - - mapView.setDrawMode(to: true) - } - - @objc private func undoButtonDidTap() { - mapView.undo() - } - @objc private func completeButtonDidTap() { + private func presentAlertVC() { let alertVC = CustomAlertVC() alertVC.modalPresentationStyle = .overFullScreen @@ -166,6 +161,24 @@ extension CourseDrawingVC { } } +// MARK: - @objc Function + +extension CourseDrawingVC { + @objc private func decideDepartureButtonDidTap() { + showHiddenViews(withDuration: 0.7) + + mapView.setDrawMode(to: true) + } + + @objc private func undoButtonDidTap() { + mapView.undo() + } + + @objc private func completeButtonDidTap() { + mapView.capturePathImage() + } +} + // MARK: - UI & Layout extension CourseDrawingVC { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index 7d77ad41..8d818510 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -176,7 +176,7 @@ extension RunTrackingVC { mapView.pathImage.sink { [weak self] image in guard let self = self else { return } self.pathImage = image - print("이미지 수신 완료") + print("이미지 수신 완료2") }.store(in: cancelBag) } From d0a3a6601149e1333128e3ce0cb278345645e69a Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 15:32:38 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[Chore]=20#33=20-=20RunTrackingVC?= =?UTF-8?q?=EB=A1=9C=20=EC=BA=A1=EC=B3=90=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/UIComponents/MapView/RNMapView.swift | 10 +++------- .../Presentation/CourseDrawing/VC/CountDownVC.swift | 8 ++++++++ .../CourseDrawing/VC/CourseDrawingVC.swift | 5 +++-- .../Presentation/RunTracking/VC/RunTrackingVC.swift | 11 +---------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift index c08ccef5..2110f21a 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift @@ -141,17 +141,13 @@ extension RNMapView { /// NMGLatLng 어레이를 받아서 첫 위치를 startMarker로 설정하고 나머지를 일반 마커로 생성 @discardableResult - func makeMarkersWithStartMarker(at locations: [NMGLatLng], willCapture: Bool) -> Self { + func makeMarkersWithStartMarker(at locations: [NMGLatLng], moveCameraToStartMarker: Bool) -> Self { if locations.count < 2 { return self } - makeStartMarker(at: locations[0], withCameraMove: willCapture) + makeStartMarker(at: locations[0], withCameraMove: moveCameraToStartMarker) locations[1...].forEach { location in makeMarker(at: location) } - if willCapture { - makeDummyMarkerAndCameraMove(at: locations) - } - return self } @@ -159,7 +155,7 @@ extension RNMapView { func makeDummyMarkerAndCameraMove(at locations: [NMGLatLng]) { addSubview(dummyMap) sendSubviewToBack(dummyMap) - dummyMap.makeMarkersWithStartMarker(at: locations, willCapture: false) + dummyMap.makeMarkersWithStartMarker(at: locations, moveCameraToStartMarker: false) let bounds = makeMBR(at: locations) let cameraUpdate = NMFCameraUpdate(fit: bounds, padding: 100) cameraUpdate.animation = .none diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CountDownVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CountDownVC.swift index 37c00fbe..cf4515aa 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CountDownVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CountDownVC.swift @@ -15,6 +15,7 @@ final class CountDownVC: UIViewController { private var count = 3 var locations = [NMGLatLng]() var distance: String? + var pathImage: UIImage? // MARK: - UI Components @@ -60,6 +61,7 @@ extension CountDownVC { } else { let runTrackingVC = RunTrackingVC() runTrackingVC.makePath(locations: self.locations, distance: self.distance ?? "0:0") + runTrackingVC.pathImage = self.pathImage self.navigationController?.pushViewController(runTrackingVC, animated: true) // CountDownVC를 navigationController 스택에서 제거하여 pop 하였을 때 더 이전 뷰로 넘어가지도록 함 @@ -69,6 +71,12 @@ extension CountDownVC { } }) } + + func setData(locations: [NMGLatLng], distance: String?, pathImage: UIImage?) { + self.locations = locations + self.distance = distance + self.pathImage = pathImage + } } // MARK: - UI & Layout diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift index d5c9376a..6c1d1ee6 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift @@ -151,8 +151,9 @@ extension CourseDrawingVC { alertVC.rightButtonTapped.sink { [weak self] _ in guard let self = self else { return } let countDownVC = CountDownVC() - countDownVC.locations = self.mapView.getMarkersLatLng() - countDownVC.distance = self.distanceLabel.text + countDownVC.setData(locations: self.mapView.getMarkersLatLng(), + distance: self.distanceLabel.text, + pathImage: self.pathImage) self.navigationController?.pushViewController(countDownVC, animated: true) alertVC.dismiss(animated: true) }.store(in: cancelBag) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift index 8d818510..2b30ea27 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/RunTracking/VC/RunTrackingVC.swift @@ -123,7 +123,6 @@ final class RunTrackingVC: UIViewController { self.setUI() self.setLayout() self.setAddTarget() - self.bindMapView() } override func viewWillAppear(_ animated: Bool) { @@ -136,7 +135,7 @@ final class RunTrackingVC: UIViewController { extension RunTrackingVC { func makePath(locations: [NMGLatLng], distance: String) { - self.mapView.makeMarkersWithStartMarker(at: locations, willCapture: true) + self.mapView.makeMarkersWithStartMarker(at: locations, moveCameraToStartMarker: true) self.totalDistanceLabel.attributedText = makeAttributedLabelForDistance(distance: distance) self.distance = distance } @@ -172,14 +171,6 @@ extension RunTrackingVC { }.store(in: cancelBag) } - private func bindMapView() { - mapView.pathImage.sink { [weak self] image in - guard let self = self else { return } - self.pathImage = image - print("이미지 수신 완료2") - }.store(in: cancelBag) - } - private func setTimeLabel(with totalSeconds: Int) { let formattedString = RNTimeFormatter.secondsToHHMMSS(seconds: totalSeconds) From f717b6fefa3d759ffeeb3c983e24be22df6524ec Mon Sep 17 00:00:00 2001 From: Sejin Lee Date: Thu, 5 Jan 2023 15:59:43 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[Fix]=20#33=20-=20=EC=9E=AC=EC=BA=A1?= =?UTF-8?q?=EC=B3=90=ED=95=A0=20=EB=95=8C=20=EA=B8=B0=EC=A1=B4=EC=9D=98=20?= =?UTF-8?q?=EB=A7=88=EC=BB=A4=EA=B0=80=20=EC=A7=80=EC=9B=8C=EC=A7=80?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=95=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Global/UIComponents/MapView/RNMapView.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift index 2110f21a..55b44a5c 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/MapView/RNMapView.swift @@ -142,6 +142,7 @@ extension RNMapView { /// NMGLatLng 어레이를 받아서 첫 위치를 startMarker로 설정하고 나머지를 일반 마커로 생성 @discardableResult func makeMarkersWithStartMarker(at locations: [NMGLatLng], moveCameraToStartMarker: Bool) -> Self { + removeMarkers() if locations.count < 2 { return self } makeStartMarker(at: locations[0], withCameraMove: moveCameraToStartMarker) locations[1...].forEach { location in @@ -163,9 +164,9 @@ extension RNMapView { if isCancelled { print("카메라 이동 취소") } else { - self.dummyMap.map.mapView.zoomLevel -= 1 LoadingIndicator.showLoading() - DispatchQueue.main.asyncAfter(deadline: .now()+3) { + DispatchQueue.main.asyncAfter(deadline: .now()+2) { + self.dummyMap.map.mapView.zoomLevel -= 1 self.makePathImage() LoadingIndicator.hideLoading() } @@ -274,6 +275,13 @@ extension RNMapView { lastMarker.mapView = nil } + /// 출발지 마커를 제외한 모든 마커 제거 + func removeMarkers() { + while self.markers.count != 0 { + undo() + } + } + // 두 지점 사이의 거리(m) 추가 private func addDistance(with newLocation: NMGLatLng) { let lastCLLoc = markersLatLngs.last?.toCLLocation()