diff --git a/iOS/Features/Home/Sources/NavigateMap/Model/NavigateMapModel.swift b/iOS/Features/Home/Sources/NavigateMap/Model/NavigateMapModel.swift
new file mode 100644
index 0000000..f9f2105
--- /dev/null
+++ b/iOS/Features/Home/Sources/NavigateMap/Model/NavigateMapModel.swift
@@ -0,0 +1,48 @@
+//
+// NavigateMapModel.swift
+// Home
+//
+// Created by 윤동주 on 11/26/23.
+//
+
+import Foundation
+
+struct Song {
+ var id: UUID
+ var title: String
+ var atrwork: String
+}
+
+struct JourneyMetadata {
+ var date: Date
+}
+
+struct Coordinate {
+ var latitude: Double
+ var longitude: Double
+}
+
+struct Spot {
+ var id: UUID
+ var coordinate: Coordinate
+ var photo: String?
+ var w3w: String
+}
+
+public struct Journey {
+ var id: UUID
+ var title: String
+ var metadata: JourneyMetadata
+ var spots: [Spot]
+ var coordinates: [Coordinate]
+ var song: Song
+ var lineColor: String
+
+}
+
+public struct User {
+ var email: String
+ var journeys: [Journey]
+ public var isRecording: Bool
+ var coordinate: Coordinate
+}
diff --git a/iOS/Features/Home/Sources/NavigateMap/NavigateMap.swift b/iOS/Features/Home/Sources/NavigateMap/NavigateMap.swift
deleted file mode 100644
index 57f01ed..0000000
--- a/iOS/Features/Home/Sources/NavigateMap/NavigateMap.swift
+++ /dev/null
@@ -1,8 +0,0 @@
-//
-// NavigateMap.swift
-// Home
-//
-// Created by 이창준 on 2023.11.29.
-//
-
-import Foundation
diff --git a/iOS/Features/Home/Sources/NavigateMap/View/CustomMarkerAnnotation.swift b/iOS/Features/Home/Sources/NavigateMap/View/CustomMarkerAnnotation.swift
new file mode 100644
index 0000000..0b38aab
--- /dev/null
+++ b/iOS/Features/Home/Sources/NavigateMap/View/CustomMarkerAnnotation.swift
@@ -0,0 +1,33 @@
+//
+// CustomMarkerAnnotation.swift
+// Home
+//
+// Created by 윤동주 on 11/23/23.
+//
+
+import Foundation
+import CoreLocation
+import MapKit
+
+class CustomMarkerAnnotation: NSObject, MKAnnotation {
+
+ // MARK: - Properties
+
+ var id: UUID
+ var coordinate: CLLocationCoordinate2D
+ var timestamp: String
+ var photo: Data
+
+ // MARK: - Initializer
+
+ init(id: UUID,
+ coordinate: CLLocationCoordinate2D,
+ timestamp: String,
+ photo: Data) {
+ self.id = id
+ self.coordinate = coordinate
+ self.timestamp = timestamp
+ self.photo = photo
+ }
+
+}
diff --git a/iOS/Features/Home/Sources/NavigateMap/View/MapView.swift b/iOS/Features/Home/Sources/NavigateMap/View/MapView.swift
new file mode 100644
index 0000000..1c3e6ef
--- /dev/null
+++ b/iOS/Features/Home/Sources/NavigateMap/View/MapView.swift
@@ -0,0 +1,43 @@
+//
+// MapView.swift
+// Home
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import Foundation
+import UIKit
+import MapKit
+
+class MapView: UIView {
+
+ // MARK: - Properties
+
+ let centerbutton = UIButton()
+ let map = MKMapView()
+
+ // MARK: - Initializer
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ self.addSubview(map)
+ self.addSubview(centerbutton)
+ configureLayout()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError()
+ }
+
+ // MARK: - UI Configuration
+
+ func configureLayout() {
+
+ centerbutton.setTitle("시작하기", for: .normal)
+ centerbutton.backgroundColor = .darkGray
+ centerbutton.setTitleColor(.yellow, for: .normal)
+ centerbutton.layer.cornerRadius = 12
+ }
+
+}
diff --git a/iOS/Features/Home/Sources/NavigateMap/View/NavigateMapButtonView.swift b/iOS/Features/Home/Sources/NavigateMap/View/NavigateMapButtonView.swift
new file mode 100644
index 0000000..b3fb140
--- /dev/null
+++ b/iOS/Features/Home/Sources/NavigateMap/View/NavigateMapButtonView.swift
@@ -0,0 +1,152 @@
+//
+// NavigateMapButtonView.swift
+// Home
+//
+// Created by 윤동주 on 11/22/23.
+//
+
+import UIKit
+
+enum ButtonImage: String {
+ case setting = "gearshape.fill",
+ map = "map.fill",
+ location = "mappin"
+}
+
+/// HomeMap 내의 버튼들을 감싸는 View
+final class NavigateMapButtonView: UIView {
+
+ // MARK: - Properties
+
+ private var buttonStackView: ButtonStackView = {
+ let view = ButtonStackView()
+ return view
+ }()
+
+ // Button별 기능 주입
+ var settingButtonAction: (() -> Void)? {
+ didSet {
+ buttonStackView.settingButtonAction = settingButtonAction
+ }
+ }
+
+ var mapButtonAction: (() -> Void)? {
+ didSet {
+ buttonStackView.mapButtonAction = mapButtonAction
+ }
+ }
+
+ var locationButtonAction: (() -> Void)? {
+ didSet {
+ buttonStackView.locationButtonAction = locationButtonAction
+ }
+ }
+
+ // MARK: - Life Cycle
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ configureStyle()
+ configureLayout()
+ }
+
+ required init(coder: NSCoder) {
+ fatalError("MusicSpot은 code-based로만 작업 중입니다.")
+ }
+
+ // MARK: - Functions
+
+ private func configureStyle() {
+ backgroundColor = .lightGray
+ layer.cornerRadius = 8
+ }
+
+ private func configureLayout() {
+ addSubview(buttonStackView)
+ buttonStackView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ buttonStackView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
+ buttonStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
+ buttonStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
+ buttonStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16)
+ ])
+ }
+
+}
+
+/// HomeMap 내 3개 버튼 StackView
+class ButtonStackView: UIStackView {
+
+ // MARK: - Properties
+
+ var settingButtonAction: (() -> Void)?
+ var mapButtonAction: (() -> Void)?
+ var locationButtonAction: (() -> Void)?
+
+ // MARK: - Life Cycle
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ configureLayout()
+ }
+
+ required init(coder: NSCoder) {
+ fatalError("MusicSpot은 code-based로만 작업 중입니다.")
+ }
+
+ // MARK: - Functions
+
+ private func configureLayout() {
+ axis = .vertical
+ spacing = 24
+ alignment = .fill
+ distribution = .fillEqually
+ translatesAutoresizingMaskIntoConstraints = false
+
+ let settingButton = self.createButton(image: ButtonImage.setting)
+ settingButton.addTarget(self, action: #selector(settingButtondidTap), for: .touchUpInside)
+ let mapButton = self.createButton(image: ButtonImage.map)
+ mapButton.addTarget(self, action: #selector(mapButtondidTap), for: .touchUpInside)
+ let locationButton = self.createButton(image: ButtonImage.location)
+ locationButton.addTarget(self, action: #selector(locationButtondidTap), for: .touchUpInside)
+
+ addArrangedSubview(settingButton)
+ addArrangedSubview(mapButton)
+ addArrangedSubview(locationButton)
+ }
+
+ private func createButton(image: ButtonImage) -> UIButton {
+ let button = UIButton()
+ button.translatesAutoresizingMaskIntoConstraints = false
+
+ if #available(iOS 13.0, *) {
+ let symbolImage = UIImage(systemName: image.rawValue)
+ button.setImage(symbolImage, for: .normal)
+ } else {
+ // Fallback on earlier versions
+ }
+ button.imageView?.tintColor = .black
+
+ button.layer.borderColor = UIColor.black.cgColor
+ button.widthAnchor.constraint(equalToConstant: 24).isActive = true
+ button.heightAnchor.constraint(equalToConstant: 24).isActive = true
+ button.translatesAutoresizingMaskIntoConstraints = false
+
+ return button
+ }
+
+ // MARK: - Object Functions
+
+ @objc private func settingButtondidTap() {
+ settingButtonAction?()
+ }
+
+ @objc private func mapButtondidTap() {
+ mapButtonAction?()
+ }
+
+ @objc private func locationButtondidTap() {
+ locationButtonAction?()
+ }
+
+}
diff --git a/iOS/Features/Home/Sources/NavigateMap/View/NavigateMapViewController.swift b/iOS/Features/Home/Sources/NavigateMap/View/NavigateMapViewController.swift
new file mode 100644
index 0000000..f077d4b
--- /dev/null
+++ b/iOS/Features/Home/Sources/NavigateMap/View/NavigateMapViewController.swift
@@ -0,0 +1,255 @@
+//
+// NavigateMapViewController.swift
+// Home
+//
+// Created by 윤동주 on 11/21/23.
+//
+
+import CoreLocation
+import UIKit
+import MapKit
+
+public final class NavigateMapViewController: UIViewController {
+
+ // MARK: - Properties
+
+ // 임시 위치 정보
+ let tempCoordinate = CLLocationCoordinate2D(latitude: 37.495120492289026, longitude: 126.9553042366186)
+
+ /// 전체 Map에 대한 View
+ let mapView = MapView()
+
+ /// HomeMap 내 우상단 3버튼 View
+ var buttonStackView: NavigateMapButtonView = {
+ let stackView = NavigateMapButtonView()
+
+ return stackView
+ }()
+
+ /// 시작하기 Button
+ var startButton: UIButton = {
+ let button = UIButton()
+
+ button.backgroundColor = .gray
+ button.setTitle("시작하기", for: .normal)
+ button.layer.cornerRadius = 25
+ return button
+ }()
+
+ @objc func findMyLocation() {
+
+ guard let currentLocation = locationManager.location else {
+ locationManager.requestWhenInUseAuthorization()
+ return
+ }
+
+ mapView.map.showsUserLocation = true
+
+ mapView.map.setUserTrackingMode(.follow, animated: true)
+
+ }
+
+ var timer: Timer?
+ var previousCoordinate: CLLocationCoordinate2D?
+ private var polyline: MKPolyline?
+
+ let locationManager = CLLocationManager()
+
+ // MARK: - Life Cycle
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view = mapView
+
+ startButton.addTarget(self, action: #selector(findMyLocation), for: .touchUpInside)
+
+ locationManager.requestWhenInUseAuthorization()
+ mapView.map.setRegion(MKCoordinateRegion(center: tempCoordinate,
+ span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.11)),
+ animated: true)
+
+ mapView.map.delegate = self
+
+ locationManager.delegate = self
+
+ configureLayout()
+ configureStyle()
+ }
+
+ // MARK: - Functions
+
+ private func configureStyle() {}
+
+ private func configureLayout() {
+
+ view.addSubview(buttonStackView)
+ view.addSubview(startButton)
+
+ mapView.map.translatesAutoresizingMaskIntoConstraints = false
+ buttonStackView.translatesAutoresizingMaskIntoConstraints = false
+ startButton.translatesAutoresizingMaskIntoConstraints = false
+
+ let safeArea = view.safeAreaLayoutGuide
+
+ NSLayoutConstraint.activate([
+ mapView.map.topAnchor.constraint(equalTo: safeArea.topAnchor),
+ mapView.map.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
+ mapView.map.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
+ mapView.map.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor),
+
+ buttonStackView.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 50),
+ buttonStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
+
+ startButton.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -27),
+ startButton.heightAnchor.constraint(equalToConstant: 60),
+ startButton.widthAnchor.constraint(equalToConstant: 186),
+ startButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+ ])
+ }
+}
+
+// MARK: - CLLocationManager
+
+extension NavigateMapViewController: CLLocationManagerDelegate {
+
+ /// 위치 정보 권한 변경에 따른 동작
+ func checkCurrentLocationAuthorization(authorizationStatus: CLAuthorizationStatus) {
+ switch authorizationStatus {
+ case .notDetermined:
+ locationManager.requestWhenInUseAuthorization()
+ locationManager.startUpdatingLocation()
+ case .restricted:
+ print("restricted")
+ case .denied:
+ print("denided")
+ case .authorizedAlways:
+ print("always")
+ case .authorizedWhenInUse:
+ print("wheninuse")
+ locationManager.startUpdatingLocation()
+ @unknown default:
+ print("unknown")
+ }
+ if #available(iOS 14.0, *) {
+ let accuracyState = locationManager.accuracyAuthorization
+ switch accuracyState {
+ case .fullAccuracy:
+ print("full")
+ case .reducedAccuracy:
+ print("reduced")
+ @unknown default:
+ print("Unknown")
+ }
+ }
+ }
+
+ func checkUserLocationServicesAuthorization() {
+ let authorizationStatus: CLAuthorizationStatus
+ if #available(iOS 14, *) {
+ authorizationStatus = locationManager.authorizationStatus
+ } else {
+ authorizationStatus = CLLocationManager.authorizationStatus()
+ }
+
+ if CLLocationManager.locationServicesEnabled() {
+ checkCurrentLocationAuthorization(authorizationStatus: authorizationStatus)
+ }
+ }
+
+// /// 현재 보고있는 화면을 내 현위치로 맞춤
+// private func centerMapOnLocation(_ location: CLLocation) {
+// let coordinateRegion = MKCoordinateRegion(center: location.coordinate,
+// latitudinalMeters: 500,
+// longitudinalMeters: 500)
+// mapView.map.setRegion(coordinateRegion, animated: true)
+// }
+
+ /// 이전 좌표와 현 좌표를 기준으로 polyline을 추가
+ @objc private func updatePolyline() {
+ guard let newCoordinate = locationManager.location?.coordinate else { return }
+ print(newCoordinate)
+ // Draw polyline
+ self.addPolylineToMap(from: previousCoordinate, to: newCoordinate)
+
+ // Update previous coordinate
+ previousCoordinate = newCoordinate
+ }
+
+ private func addPolylineToMap(from previousCoordinate: CLLocationCoordinate2D?,
+ to newCoordinate: CLLocationCoordinate2D) {
+ guard let previousCoordinate = previousCoordinate else { return }
+
+ var points: [CLLocationCoordinate2D]
+
+ if let existingPolyline = polyline {
+ points = [existingPolyline.coordinate] + [newCoordinate]
+ mapView.map.removeOverlay(existingPolyline)
+ } else {
+ points = [previousCoordinate, newCoordinate]
+ }
+
+ polyline = MKPolyline(coordinates: &points, count: points.count)
+ mapView.map.addOverlay(polyline!) // Add the updated polyline to the map
+ }
+
+ public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
+ print(#function)
+ checkUserLocationServicesAuthorization()
+ }
+
+ public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
+ print(#function)
+ checkUserLocationServicesAuthorization()
+ }
+
+ /// 위치 가져오기 실패
+ public func locationManager(_ manager: CLLocationManager,
+ didFailWithError error: Error) {
+ // 위치 가져오기 실패 에러 Logger 설정
+ }
+}
+
+// MARK: - MKMapView
+
+extension NavigateMapViewController: MKMapViewDelegate {
+
+ /// 현재까지의 polyline들을 지도 위에 그림
+ public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
+ guard let polyLine = overlay as? MKPolyline else {
+ return MKOverlayRenderer()
+ }
+
+ let renderer = MKPolylineRenderer(polyline: polyLine)
+ renderer.strokeColor = .orange
+ renderer.lineWidth = 5.0
+
+ return renderer
+ }
+
+ /// 재사용 할 수 있는 Annotation 만들어두기
+ public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
+ guard !annotation.isKind(of: MKUserLocation.self) else {
+ return nil
+ }
+
+ var annotationView = self.mapView.map.dequeueReusableAnnotationView(withIdentifier: "Custom")
+
+ if annotationView == nil {
+ annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "Custom")
+ annotationView?.canShowCallout = true
+
+ let miniButton = UIButton(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
+ miniButton.setImage(UIImage(systemName: "person"), for: .normal)
+ miniButton.tintColor = .blue
+ annotationView?.rightCalloutAccessoryView = miniButton
+
+ } else {
+ annotationView?.annotation = annotation
+ }
+
+ annotationView?.image = UIImage(named: "Circle")
+
+ return annotationView
+ }
+}
diff --git a/iOS/Features/Home/Sources/NavigateMap/ViewModel/NavigateMapViewModel.swift b/iOS/Features/Home/Sources/NavigateMap/ViewModel/NavigateMapViewModel.swift
new file mode 100644
index 0000000..8e718bc
--- /dev/null
+++ b/iOS/Features/Home/Sources/NavigateMap/ViewModel/NavigateMapViewModel.swift
@@ -0,0 +1,35 @@
+//
+// NavigateMapViewModel.swift
+// Home
+//
+// Created by 윤동주 on 11/26/23.
+//
+
+import Foundation
+
+class NavigateMapViewModel {
+
+ // MARK: - Properties
+
+ var journeys: [Journey]
+ var user: User
+ var currentJourney: Journey?
+
+ // MARK: - Initializer
+
+ init(journeys: [Journey], user: User, currentJourney: Journey? = nil) {
+ self.journeys = journeys
+ self.user = user
+ self.currentJourney = currentJourney
+ }
+
+ var userState: Bool {
+ self.user.isRecording
+ }
+
+ // MARK: - Functions
+
+ func toggleIsRecording() {
+ user.isRecording.toggle()
+ }
+}
diff --git a/iOS/Features/HomeMapScene/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/iOS/Features/HomeMapScene/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/iOS/Features/HomeMapScene/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/iOS/Features/HomeMapScene/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/Features/HomeMapScene/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/iOS/Features/HomeMapScene/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/iOS/Features/HomeMapScene/Package.swift b/iOS/Features/HomeMapScene/Package.swift
new file mode 100644
index 0000000..02b04c5
--- /dev/null
+++ b/iOS/Features/HomeMapScene/Package.swift
@@ -0,0 +1,30 @@
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+extension String {
+ static let package = "HomeMapScene"
+
+// var testTarget: String {
+// return self + "Tests"
+// }
+}
+let package = Package(
+ name: .package,
+ platforms: [
+ .iOS(.v15)
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, making them visible to other packages.
+ .library(
+ name: "HomeMapScene",
+ targets: ["HomeMapScene"])
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package, defining a module or a test suite.
+ // Targets can depend on other targets in this package and products from dependencies.
+ .target(
+ name: "HomeMapScene")
+ ]
+)
diff --git a/iOS/Features/HomeMapScene/Sources/HomeMapScene/Model/HomeMapModel.swift b/iOS/Features/HomeMapScene/Sources/HomeMapScene/Model/HomeMapModel.swift
new file mode 100644
index 0000000..89a34ea
--- /dev/null
+++ b/iOS/Features/HomeMapScene/Sources/HomeMapScene/Model/HomeMapModel.swift
@@ -0,0 +1,48 @@
+//
+// HomeMapModel.swift
+//
+//
+// Created by 윤동주 on 11/26/23.
+//
+
+import Foundation
+
+struct Song {
+ var id: UUID
+ var title: String
+ var atrwork: String
+}
+
+struct JourneyMetadata {
+ var date: Date
+}
+
+struct Coordinate {
+ var latitude: Double
+ var longitude: Double
+}
+
+struct Spot {
+ var id: UUID
+ var coordinate: Coordinate
+ var photo: String?
+ var w3w: String
+}
+
+public struct Journey {
+ var id: UUID
+ var title: String
+ var metadata: JourneyMetadata
+ var spots: [Spot]
+ var coordinates: [Coordinate]
+ var song: Song
+ var lineColor: String
+
+}
+
+public struct User {
+ var email: String
+ var journeys: [Journey]
+ public var isRecording: Bool
+ var coordinate: Coordinate
+}
diff --git a/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/CustomMarkerAnnotation.swift b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/CustomMarkerAnnotation.swift
new file mode 100644
index 0000000..467d543
--- /dev/null
+++ b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/CustomMarkerAnnotation.swift
@@ -0,0 +1,32 @@
+//
+// CustomMarkerAnnotation.swift
+//
+//
+// Created by 윤동주 on 11/23/23.
+//
+
+import Foundation
+import CoreLocation
+import MapKit
+
+class CustomMarkerAnnotation: NSObject, MKAnnotation {
+
+ // MARK: - Properties
+
+ var id: UUID
+ var coordinate: CLLocationCoordinate2D
+ var timestamp: String
+ var photo: Data
+
+ // MARK: - Life Cycle
+
+ init(id: UUID,
+ coordinate: CLLocationCoordinate2D,
+ timestamp: String,
+ photo: Data) {
+ self.id = id
+ self.coordinate = coordinate
+ self.timestamp = timestamp
+ self.photo = photo
+ }
+}
diff --git a/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/HomeMapButtonView.swift b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/HomeMapButtonView.swift
new file mode 100644
index 0000000..cb5880b
--- /dev/null
+++ b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/HomeMapButtonView.swift
@@ -0,0 +1,150 @@
+//
+// HomeMapButtonView.swift
+//
+//
+// Created by 윤동주 on 11/22/23.
+//
+
+import UIKit
+
+enum ButtonImage: String {
+ case setting = "gearshape.fill",
+ map = "map.fill",
+ location = "mappin"
+}
+
+/// HomeMap 내의 버튼들을 감싸는 View
+final class HomeMapButtonView: UIView {
+
+ // MARK: - Properties
+
+ private var buttonStackView: ButtonStackView = {
+ let view = ButtonStackView()
+ return view
+ }()
+
+ // Button별 기능 주입
+ var settingButtonAction: (() -> Void)? {
+ didSet {
+ buttonStackView.settingButtonAction = settingButtonAction
+ }
+ }
+
+ var mapButtonAction: (() -> Void)? {
+ didSet {
+ buttonStackView.mapButtonAction = mapButtonAction
+ }
+ }
+
+ var locationButtonAction: (() -> Void)? {
+ didSet {
+ buttonStackView.locationButtonAction = locationButtonAction
+ }
+ }
+
+ // MARK: - Life Cycle
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ configureStyle()
+ configureLayout()
+ }
+
+ required init(coder: NSCoder) {
+ fatalError("MusicSpot은 code-based로만 작업 중입니다.")
+ }
+
+ // MARK: - Functions
+
+ private func configureStyle() {
+ backgroundColor = .lightGray
+ layer.cornerRadius = 8
+ }
+
+ private func configureLayout() {
+ addSubview(buttonStackView)
+ buttonStackView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ buttonStackView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
+ buttonStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
+ buttonStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
+ buttonStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16)
+ ])
+ }
+}
+
+/// HomeMap 내 3개 버튼 StackView
+class ButtonStackView: UIStackView {
+
+ // MARK: - Properties
+
+ var settingButtonAction: (() -> Void)?
+ var mapButtonAction: (() -> Void)?
+ var locationButtonAction: (() -> Void)?
+
+ // MARK: - Life Cycle
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ configureLayout()
+ }
+
+ required init(coder: NSCoder) {
+ fatalError("MusicSpot은 code-based로만 작업 중입니다.")
+ }
+
+ // MARK: - Functions
+
+ private func configureLayout() {
+ axis = .vertical
+ spacing = 24
+ alignment = .fill
+ distribution = .fillEqually
+ translatesAutoresizingMaskIntoConstraints = false
+
+ let settingButton = createButton(image: ButtonImage.setting)
+ settingButton.addTarget(self, action: #selector(settingButtondidTap), for: .touchUpInside)
+ let mapButton = createButton(image: ButtonImage.map)
+ mapButton.addTarget(self, action: #selector(mapButtondidTap), for: .touchUpInside)
+ let locationButton = createButton(image: ButtonImage.location)
+ locationButton.addTarget(self, action: #selector(locationButtondidTap), for: .touchUpInside)
+
+ addArrangedSubview(settingButton)
+ addArrangedSubview(mapButton)
+ addArrangedSubview(locationButton)
+ }
+
+ private func createButton(image: ButtonImage) -> UIButton {
+ let button = UIButton()
+ button.translatesAutoresizingMaskIntoConstraints = false
+
+ if #available(iOS 13.0, *) {
+ let symbolImage = UIImage(systemName: image.rawValue)
+ button.setImage(symbolImage, for: .normal)
+ } else {
+ // Fallback on earlier versions
+ }
+ button.imageView?.tintColor = .black
+
+ button.layer.borderColor = UIColor.black.cgColor
+ button.widthAnchor.constraint(equalToConstant: 24).isActive = true
+ button.heightAnchor.constraint(equalToConstant: 24).isActive = true
+ button.translatesAutoresizingMaskIntoConstraints = false
+
+ return button
+ }
+
+ // MARK: - Object Functions
+
+ @objc private func settingButtondidTap() {
+ settingButtonAction?()
+ }
+
+ @objc private func mapButtondidTap() {
+ mapButtonAction?()
+ }
+
+ @objc private func locationButtondidTap() {
+ locationButtonAction?()
+ }
+}
diff --git a/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/HomeMapViewController.swift b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/HomeMapViewController.swift
new file mode 100644
index 0000000..a2d5088
--- /dev/null
+++ b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/HomeMapViewController.swift
@@ -0,0 +1,255 @@
+//
+// HomeMapViewController.swift
+//
+//
+// Created by 윤동주 on 11/21/23.
+//
+
+import UIKit
+import MapKit
+import CoreLocation
+
+public final class HomeMapViewController: UIViewController {
+
+ // MARK: - Properties
+
+ // 임시 위치 정보
+ let tempCoordinate = CLLocationCoordinate2D(latitude: 37.495120492289026, longitude: 126.9553042366186)
+
+ /// 전체 Map에 대한 View
+ let mapView = MapView()
+
+ /// HomeMap 내 우상단 3버튼 View
+ var buttonStackView: HomeMapButtonView = {
+ let stackView = HomeMapButtonView()
+
+ return stackView
+ }()
+
+ /// 시작하기 Button
+ var startButton: UIButton = {
+ let button = UIButton()
+
+ button.backgroundColor = .gray
+ button.setTitle("시작하기", for: .normal)
+ button.layer.cornerRadius = 25
+ return button
+ }()
+
+ @objc func findMyLocation() {
+
+ guard let currentLocation = locationManager.location else {
+ locationManager.requestWhenInUseAuthorization()
+ return
+ }
+
+ mapView.map.showsUserLocation = true
+
+ mapView.map.setUserTrackingMode(.follow, animated: true)
+
+ }
+
+ var timer: Timer?
+ var previousCoordinate: CLLocationCoordinate2D?
+ private var polyline: MKPolyline?
+
+ let locationManager = CLLocationManager()
+
+ // MARK: - Life Cycle
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view = mapView
+
+ startButton.addTarget(self, action: #selector(findMyLocation), for: .touchUpInside)
+
+ locationManager.requestWhenInUseAuthorization()
+ mapView.map.setRegion(MKCoordinateRegion(center: tempCoordinate,
+ span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.11)),
+ animated: true)
+
+ mapView.map.delegate = self
+
+ locationManager.delegate = self
+
+ configureLayout()
+ configureStyle()
+ }
+
+ // MARK: - Functions
+
+ private func configureStyle() {}
+
+ private func configureLayout() {
+
+ view.addSubview(buttonStackView)
+ view.addSubview(startButton)
+
+ mapView.map.translatesAutoresizingMaskIntoConstraints = false
+ buttonStackView.translatesAutoresizingMaskIntoConstraints = false
+ startButton.translatesAutoresizingMaskIntoConstraints = false
+
+ let safeArea = view.safeAreaLayoutGuide
+
+ NSLayoutConstraint.activate([
+ mapView.map.topAnchor.constraint(equalTo: safeArea.topAnchor),
+ mapView.map.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
+ mapView.map.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
+ mapView.map.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor),
+
+ buttonStackView.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 50),
+ buttonStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
+
+ startButton.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -27),
+ startButton.heightAnchor.constraint(equalToConstant: 60),
+ startButton.widthAnchor.constraint(equalToConstant: 186),
+ startButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+ ])
+ }
+}
+
+// MARK: - CLLocationManager
+
+extension HomeMapViewController: CLLocationManagerDelegate {
+
+ /// 위치 정보 권한 변경에 따른 동작
+ func checkCurrentLocationAuthorization(authorizationStatus: CLAuthorizationStatus) {
+ switch authorizationStatus {
+ case .notDetermined:
+ locationManager.requestWhenInUseAuthorization()
+ locationManager.startUpdatingLocation()
+ case .restricted:
+ print("restricted")
+ case .denied:
+ print("denided")
+ case .authorizedAlways:
+ print("always")
+ case .authorizedWhenInUse:
+ print("wheninuse")
+ locationManager.startUpdatingLocation()
+ @unknown default:
+ print("unknown")
+ }
+ if #available(iOS 14.0, *) {
+ let accuracyState = locationManager.accuracyAuthorization
+ switch accuracyState {
+ case .fullAccuracy:
+ print("full")
+ case .reducedAccuracy:
+ print("reduced")
+ @unknown default:
+ print("Unknown")
+ }
+ }
+ }
+
+ func checkUserLocationServicesAuthorization() {
+ let authorizationStatus: CLAuthorizationStatus
+ if #available(iOS 14, *) {
+ authorizationStatus = locationManager.authorizationStatus
+ } else {
+ authorizationStatus = CLLocationManager.authorizationStatus()
+ }
+
+ if CLLocationManager.locationServicesEnabled() {
+ checkCurrentLocationAuthorization(authorizationStatus: authorizationStatus)
+ }
+ }
+
+// /// 현재 보고있는 화면을 내 현위치로 맞춤
+// private func centerMapOnLocation(_ location: CLLocation) {
+// let coordinateRegion = MKCoordinateRegion(center: location.coordinate,
+// latitudinalMeters: 500,
+// longitudinalMeters: 500)
+// mapView.map.setRegion(coordinateRegion, animated: true)
+// }
+
+ /// 이전 좌표와 현 좌표를 기준으로 polyline을 추가
+ @objc private func updatePolyline() {
+ guard let newCoordinate = locationManager.location?.coordinate else { return }
+ print(newCoordinate)
+ // Draw polyline
+ addPolylineToMap(from: previousCoordinate, to: newCoordinate)
+
+ // Update previous coordinate
+ previousCoordinate = newCoordinate
+ }
+//
+// private func addPolylineToMap(from previousCoordinate: CLLocationCoordinate2D?,
+// to newCoordinate: CLLocationCoordinate2D) {
+// guard let previousCoordinate = previousCoordinate else { return }
+//
+// var points: [CLLocationCoordinate2D]
+//
+// if let existingPolyline = polyline {
+// points = [existingPolyline.coordinate] + [newCoordinate]
+// mapView.map.removeOverlay(existingPolyline)
+// } else {
+// points = [previousCoordinate, newCoordinate]
+// }
+//
+// polyline = MKPolyline(coordinates: &points, count: points.count)
+// mapView.map.addOverlay(polyline!) // Add the updated polyline to the map
+// }
+
+ public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
+ print(#function)
+ checkUserLocationServicesAuthorization()
+ }
+
+ public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
+ print(#function)
+ checkUserLocationServicesAuthorization()
+ }
+
+ /// 위치 가져오기 실패
+ public func locationManager(_ manager: CLLocationManager,
+ didFailWithError error: Error) {
+ // 위치 가져오기 실패 에러 Logger 설정
+ }
+}
+
+// MARK: - MKMapView
+
+extension HomeMapViewController: MKMapViewDelegate {
+
+ /// 현재까지의 polyline들을 지도 위에 그림
+ public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
+ guard let polyLine = overlay as? MKPolyline else {
+ return MKOverlayRenderer()
+ }
+
+ let renderer = MKPolylineRenderer(polyline: polyLine)
+ renderer.strokeColor = .orange
+ renderer.lineWidth = 5.0
+
+ return renderer
+ }
+
+ /// 재사용 할 수 있는 Annotation 만들어두기
+ public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
+ guard !annotation.isKind(of: MKUserLocation.self) else {
+ return nil
+ }
+
+ var annotationView = self.mapView.map.dequeueReusableAnnotationView(withIdentifier: "Custom")
+
+ if annotationView == nil {
+ annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "Custom")
+ annotationView?.canShowCallout = true
+
+ let miniButton = UIButton(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
+ miniButton.setImage(UIImage(systemName: "person"), for: .normal)
+ miniButton.tintColor = .blue
+ annotationView?.rightCalloutAccessoryView = miniButton
+
+ } else {
+ annotationView?.annotation = annotation
+ }
+
+ annotationView?.image = UIImage(named: "Circle")
+
+ return annotationView
+ }
+}
diff --git a/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/MapView.swift b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/MapView.swift
new file mode 100644
index 0000000..bdb3974
--- /dev/null
+++ b/iOS/Features/HomeMapScene/Sources/HomeMapScene/View/MapView.swift
@@ -0,0 +1,36 @@
+//
+// MapView.swift
+//
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import Foundation
+import UIKit
+import MapKit
+
+class MapView: UIView {
+
+ let centerbutton = UIButton()
+ let map = MKMapView()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ self.addSubview(map)
+ self.addSubview(centerbutton)
+ configureLayout()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError()
+ }
+
+ func configureLayout() {
+
+ centerbutton.setTitle("시작하기", for: .normal)
+ centerbutton.backgroundColor = .darkGray
+ centerbutton.setTitleColor(.yellow, for: .normal)
+ centerbutton.layer.cornerRadius = 12
+ }
+}
diff --git a/iOS/Features/HomeMapScene/Sources/HomeMapScene/ViewModel/HomeMapViewModel.swift b/iOS/Features/HomeMapScene/Sources/HomeMapScene/ViewModel/HomeMapViewModel.swift
new file mode 100644
index 0000000..39c5686
--- /dev/null
+++ b/iOS/Features/HomeMapScene/Sources/HomeMapScene/ViewModel/HomeMapViewModel.swift
@@ -0,0 +1,35 @@
+//
+// HomeMapViewModel.swift
+//
+//
+// Created by 윤동주 on 11/26/23.
+//
+
+import Foundation
+
+class HomeMapViewModel {
+
+ // MARK: - Properties
+
+ var journeys: [Journey]
+ var user: User
+ var currentJourney: Journey?
+
+ // MARK: - Initializer
+
+ init(journeys: [Journey], user: User, currentJourney: Journey? = nil) {
+ self.journeys = journeys
+ self.user = user
+ self.currentJourney = currentJourney
+ }
+
+ var userState: Bool {
+ self.user.isRecording
+ }
+
+ // MARK: - Functions
+
+ func toggleIsRecording() {
+ user.isRecording.toggle()
+ }
+}
diff --git a/iOS/Features/RewindJourney/Sources/RewindJourneyView/MSMusicView.swift b/iOS/Features/RewindJourney/Sources/RewindJourneyView/MSMusicView.swift
new file mode 100644
index 0000000..d202e20
--- /dev/null
+++ b/iOS/Features/RewindJourney/Sources/RewindJourneyView/MSMusicView.swift
@@ -0,0 +1,183 @@
+//
+// MSMusicView.swift
+// RewindJourney
+//
+// Created by 전민건 on 11/22/23.
+//
+
+import UIKit
+
+import MSDesignSystem
+import MSLogger
+import MSUIKit
+
+final class MSMusicView: UIProgressView {
+
+ // MARK: - Constants
+
+ private enum Metric {
+
+ static let verticalInset: CGFloat = 8.0
+ static let horizonalInset: CGFloat = 12.0
+ static let cornerRadius: CGFloat = 8.0
+
+ // albumart view
+ enum AlbumArtView {
+ static let height: CGFloat = 52.0
+ static let width: CGFloat = 52.0
+ }
+ // title view
+ enum TitleView {
+ static let height: CGFloat = 4.0
+ static let inset: CGFloat = 4.0
+ static let titleHight: CGFloat = 24.0
+ static let subTitleHight: CGFloat = 20.0
+ }
+
+ // playtime view
+ enum PlayTimeView {
+ static let width: CGFloat = 67.0
+ static let verticalInset: CGFloat = 22.0
+ static let horizonalInset: CGFloat = 4.0
+ static let conponentsHeight: CGFloat = 24.0
+ }
+
+ }
+
+ private enum Default {
+
+ // titleView
+ enum TitleView {
+ static let title: String = "Attention"
+ static let subTitle: String = "NewJeans"
+ static let defaultIndex: Int = 0
+ }
+
+ // stackView
+ enum PlayTime {
+ static let time: String = "00 : 00"
+ }
+
+ }
+
+ // MARK: - UI Components
+
+ private let albumArtView = UIImageView()
+ public var albumArtImage: UIImage? {
+ didSet {
+ self.albumArtView.image = albumArtImage
+ }
+ }
+ private let titleStackView = UIStackView()
+ private let titleLabel = UILabel()
+ private let subTitleLabel = UILabel()
+ private let playTimeStackView = UIStackView()
+ private let playTimeLabel = UILabel()
+ private let playTimeIconView = UIImageView(image: .msIcon(.voice))
+
+ // MARK: - UI Configuration
+
+ public func configure() {
+ self.configureLayout()
+ self.configureStyle()
+ }
+
+ // MARK: - UI Configuration: layout
+
+ private func configureLayout() {
+ self.configureAlbumArtViewLayout()
+ self.configurePlayTimeViewLayout()
+ self.configureTitleViewLayout()
+ }
+
+ private func configureAlbumArtViewLayout() {
+ self.addSubview(self.albumArtView)
+ self.albumArtView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ self.albumArtView.topAnchor.constraint(equalTo: self.topAnchor, constant: Metric.verticalInset),
+ self.albumArtView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -Metric.verticalInset),
+ self.albumArtView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Metric.horizonalInset),
+ self.albumArtView.widthAnchor.constraint(equalToConstant: Metric.AlbumArtView.width)
+ ])
+ }
+
+ private func configureTitleViewLayout() {
+ self.addSubview(self.titleStackView)
+ self.titleStackView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ self.titleStackView.topAnchor.constraint(equalTo: self.topAnchor,
+ constant: Metric.verticalInset),
+ self.titleStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor,
+ constant: -Metric.verticalInset),
+ self.titleStackView.leadingAnchor.constraint(equalTo: self.albumArtView.trailingAnchor,
+ constant: Metric.horizonalInset),
+ self.titleStackView.trailingAnchor.constraint(equalTo: self.playTimeStackView.leadingAnchor,
+ constant: -Metric.horizonalInset)
+ ])
+
+ self.titleStackView.addArrangedSubview(self.titleLabel)
+ self.titleStackView.addArrangedSubview(self.subTitleLabel)
+
+ self.titleStackView.axis = .vertical
+ self.titleStackView.spacing = Metric.TitleView.inset
+ }
+
+ private func configurePlayTimeViewLayout() {
+ self.addSubview(self.playTimeStackView)
+ self.playTimeStackView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ self.playTimeStackView.topAnchor.constraint(equalTo: self.topAnchor,
+ constant: Metric.PlayTimeView.verticalInset),
+ self.playTimeStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor,
+ constant: -Metric.PlayTimeView.verticalInset),
+ self.playTimeStackView.widthAnchor.constraint(equalToConstant: Metric.PlayTimeView.width),
+ self.playTimeStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor,
+ constant: -Metric.horizonalInset)
+ ])
+
+ self.playTimeStackView.addArrangedSubview(self.playTimeIconView)
+ self.playTimeStackView.addArrangedSubview(self.playTimeLabel)
+
+ self.playTimeStackView.axis = .horizontal
+ self.playTimeStackView.spacing = Metric.PlayTimeView.horizonalInset
+ self.titleStackView.distribution = .fill
+ }
+
+ // MARK: - UI Configuration: style
+
+ private func configureStyle() {
+ self.clipsToBounds = true
+ self.layer.cornerRadius = Metric.cornerRadius
+ self.trackTintColor = .msColor(.secondaryBackground)
+ self.progressTintColor = .msColor(.musicSpot)
+ self.progress = 0.4
+
+ self.configureTitleViewStyle()
+ self.configurePlayTimeViewStyle()
+ }
+
+ private func configureTitleViewStyle() {
+ self.titleLabel.text = Default.TitleView.title
+ self.titleLabel.font = .msFont(.buttonTitle)
+ self.subTitleLabel.text = Default.TitleView.subTitle
+ self.subTitleLabel.font = .msFont(.paragraph)
+ }
+
+ private func configurePlayTimeViewStyle() {
+ self.playTimeLabel.text = Default.PlayTime.time
+ self.playTimeLabel.font = .msFont(.caption)
+ self.playTimeLabel.textColor = .msColor(.secondaryTypo)
+ self.playTimeIconView.tintColor = .msColor(.secondaryTypo)
+ }
+
+}
+
+// MARK: - Preview
+
+@available(iOS 17, *)
+#Preview {
+ MSFont.registerFonts()
+ let musicView = MSMusicView()
+ musicView.albumArtImage = UIImage(systemName: "pencil")
+ return musicView
+}
diff --git a/iOS/Features/RewindJourney/Sources/RewindJourneyView/MSProgressView.swift b/iOS/Features/RewindJourney/Sources/RewindJourneyView/MSProgressView.swift
new file mode 100644
index 0000000..a56216e
--- /dev/null
+++ b/iOS/Features/RewindJourney/Sources/RewindJourneyView/MSProgressView.swift
@@ -0,0 +1,49 @@
+//
+// MSProgressView.swift
+// RewindJourney
+//
+// Created by 전민건 on 11/22/23.
+//
+
+import UIKit
+
+public final class MSProgressView: UIProgressView {
+
+ // MARK: - Properties
+
+ internal var percentage: Float = 0.0 {
+ didSet {
+ syncProgress(percentage: percentage)
+ }
+ }
+ internal var isHighlighted: Bool = false {
+ didSet {
+ self.percentage = self.isHighlighted ? 1.0 : 0.0
+ }
+ }
+
+ // MARK: - Configure
+
+ private func configureColor() {
+ self.trackTintColor = .systemGray6
+ self.tintColor = .white
+ }
+
+ // MARK: - Initializer
+
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+ self.configureColor()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("MusicSpot은 code-based 로 개발되었습니다.")
+ }
+
+ // MARK: - Functions: change progress
+
+ private func syncProgress(percentage: Float) {
+ self.progress = percentage
+ }
+
+}
diff --git a/iOS/Features/RewindJourney/Sources/RewindJourneyView/RewindJourneyViewController.swift b/iOS/Features/RewindJourney/Sources/RewindJourneyView/RewindJourneyViewController.swift
new file mode 100644
index 0000000..ac653d0
--- /dev/null
+++ b/iOS/Features/RewindJourney/Sources/RewindJourneyView/RewindJourneyViewController.swift
@@ -0,0 +1,244 @@
+//
+// RewindJourneyViewController.swift
+// RewindJourney
+//
+// Created by 전민건 on 11/22/23.
+//
+
+import UIKit
+
+import MSDesignSystem
+
+public final class RewindJourneyViewController: UIViewController {
+
+ // MARK: - Constants
+
+ private enum Metric {
+
+ // progress bar
+ enum Progressbar {
+
+ static let height: CGFloat = 4.0
+ static let inset: CGFloat = 4.0
+ static let defaultIndex: Int = 0
+
+ }
+
+ // stackView
+ enum StackView {
+
+ static let inset: CGFloat = 12.0
+
+ }
+
+ // musicView
+ enum MusicView {
+
+ static let height: CGFloat = 69.0
+ static let inset: CGFloat = 12.0
+ static let bottomInset: CGFloat = 34.0
+
+ }
+
+ }
+
+ // MARK: - Properties
+
+ private let stackView = UIStackView()
+ private let presentImageView = UIImageView()
+ private let musicView = MSMusicView()
+ private var progressViews: [MSProgressView]?
+ private var preHighlightenProgressView: MSProgressView?
+ private let leftTouchView = UIButton()
+ private let rightTouchView = UIButton()
+
+ public var images: [UIImage]?
+ private var presentImageIndex: Int? {
+ didSet {
+ self.changeProgressViews()
+ }
+ }
+
+ // MARK: - UI Configuration
+
+ func configure() {
+ self.configureLayout()
+ self.configureStyle()
+ self.configureAction()
+
+ self.musicView.configure()
+ }
+
+ // MARK: - UI Configuration: Layout
+
+ private func configureLayout() {
+ self.configurePresentImageViewLayout()
+ self.configureStackViewLayout()
+ self.configureProgressbarsLayout()
+ self.configureMusicViewLayout()
+ self.configureTouchViewLayout()
+ }
+
+ private func configurePresentImageViewLayout() {
+ self.view.addSubview(self.presentImageView)
+
+ self.presentImageView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ self.presentImageView.topAnchor.constraint(equalTo: self.view.topAnchor),
+ self.presentImageView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
+ self.presentImageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
+ self.presentImageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
+ ])
+ }
+
+ private func configureStackViewLayout() {
+ self.view.addSubview(self.stackView)
+
+ self.stackView.axis = .horizontal
+ self.stackView.spacing = Metric.Progressbar.inset
+ self.stackView.distribution = .fillEqually
+
+ self.stackView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ self.stackView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
+ self.stackView.heightAnchor.constraint(equalToConstant: Metric.Progressbar.height),
+ self.stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor,
+ constant: Metric.StackView.inset),
+ self.stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor,
+ constant: -Metric.StackView.inset)])
+ }
+
+ private func configureProgressbarsLayout() {
+ guard let images else { return }
+ var views = [MSProgressView]()
+ images.forEach {_ in
+ let progressView = MSProgressView()
+ views.append(progressView)
+ stackView.addArrangedSubview(progressView)
+ }
+
+ self.progressViews = views
+ }
+
+ private func configureMusicViewLayout() {
+ self.view.addSubview(self.musicView)
+
+ self.musicView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ self.musicView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor,
+ constant: -Metric.MusicView.bottomInset),
+ self.musicView.heightAnchor.constraint(equalToConstant: Metric.MusicView.height),
+ self.musicView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor,
+ constant: Metric.MusicView.inset),
+ self.musicView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor,
+ constant: -Metric.MusicView.inset)
+ ])
+ }
+
+ private func configureTouchViewLayout() {
+ self.view.addSubview(self.leftTouchView)
+ self.view.addSubview(self.rightTouchView)
+
+ self.leftTouchView.translatesAutoresizingMaskIntoConstraints = false
+ self.rightTouchView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ self.leftTouchView.topAnchor.constraint(equalTo: self.view.topAnchor),
+ self.rightTouchView.topAnchor.constraint(equalTo: self.view.topAnchor),
+ self.leftTouchView.bottomAnchor.constraint(equalTo: self.musicView.topAnchor),
+ self.rightTouchView.bottomAnchor.constraint(equalTo: self.musicView.topAnchor),
+ self.leftTouchView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
+ self.rightTouchView.leadingAnchor.constraint(equalTo: self.view.centerXAnchor),
+ self.leftTouchView.trailingAnchor.constraint(equalTo: self.view.centerXAnchor),
+ self.rightTouchView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
+ ])
+ }
+
+ // MARK: - UI Configuration: Style
+
+ private func configureStyle() {
+ self.configurePresentImageViewStyle()
+ self.configureProgressbarsStyle()
+ }
+
+ private func configurePresentImageViewStyle() {
+ self.presentImageView.contentMode = .scaleAspectFit
+ }
+
+ private func configureProgressbarsStyle() {
+ self.presentImageIndex = Metric.Progressbar.defaultIndex
+ }
+
+ // MARK: - Configuration: Action
+
+ private func configureAction() {
+ self.configureLeftTouchViewAction()
+ self.configureRightTouchViewAction()
+ }
+
+ private func configureLeftTouchViewAction() {
+ self.leftTouchView.addTarget(self, action: #selector(leftTouchViewTapped), for: .touchUpInside)
+ }
+
+ private func configureRightTouchViewAction() {
+ self.rightTouchView.addTarget(self, action: #selector(rightTouchViewTapped), for: .touchUpInside)
+ }
+
+ private func changeProgressViews() {
+ guard let presentIndex = self.presentImageIndex,
+ let images else { return }
+
+ self.presentImageView.image = images[presentIndex]
+ self.preHighlightenProgressView = self.progressViews?[presentIndex]
+ self.preHighlightenProgressView?.isHighlighted = false
+
+ let minIndex = 0
+ let maxIndex = images.count - 1
+
+ for index in minIndex...maxIndex {
+ self.progressViews?[index].isHighlighted = index <= presentIndex ? true : false
+ }
+ }
+
+ // MARK: - Life Cycle
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+
+ self.configure()
+ }
+
+ // MARK: - Actions
+
+ @objc private func leftTouchViewTapped() {
+ guard let presentImageIndex else {
+ return
+ }
+ if presentImageIndex > 0 {
+ let index = presentImageIndex - 1
+ self.presentImageIndex = index
+ }
+ }
+
+ @objc private func rightTouchViewTapped() {
+ guard let images, let presentImageIndex else {
+ return
+ }
+ if presentImageIndex < images.count - 1 {
+ let index = presentImageIndex + 1
+ self.presentImageIndex = index
+ }
+ }
+
+}
+
+// MARK: - Preview
+
+@available(iOS 17, *)
+#Preview {
+ MSFont.registerFonts()
+ let viewController = RewindJourneyViewController()
+ viewController.images = [UIImage(systemName: "pencil")!,
+ UIImage(systemName: "pencil")!,
+ UIImage(systemName: "pencil")!]
+ return viewController
+}
diff --git a/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/iOS/MSCoreKit/Package.swift b/iOS/MSCoreKit/Package.swift
index 35d96cc..e044c96 100644
--- a/iOS/MSCoreKit/Package.swift
+++ b/iOS/MSCoreKit/Package.swift
@@ -34,6 +34,9 @@ let package = Package(
.library(name: .cache,
targets: [.cache])
],
+ dependencies: [
+ .package(path: "../MSFoundation")
+ ],
targets: [
// Codes
.target(name: .persistentStorage),
@@ -43,7 +46,10 @@ let package = Package(
.target(name: .persistentStorage),
.target(name: .networking)
]),
- .target(name: .cache),
+ .target(name: .cache,
+ dependencies: [
+ .product(name: "MSUserDefaults", package: "MSFoundation")
+ ]),
// Tests
.testTarget(name: .persistentStorage.testTarget,
diff --git a/iOS/MSCoreKit/Sources/MSCacheStorage/MSCacheableData.swift b/iOS/MSCoreKit/Sources/MSCacheStorage/MSCacheableData.swift
new file mode 100644
index 0000000..4bdf3c8
--- /dev/null
+++ b/iOS/MSCoreKit/Sources/MSCacheStorage/MSCacheableData.swift
@@ -0,0 +1,29 @@
+//
+// MSCacheableData.swift
+// MSCoreKit
+//
+// Created by 이창준 on 11/15/23.
+//
+
+import Foundation
+
+public final class MSCacheableData: Codable {
+
+ struct Metadata: Hashable, Codable {
+ let etag: String
+ let lastUsed: Date
+ }
+
+ // MARK: - Properties
+
+ let data: Data
+ let metadata: Metadata
+
+ // MARK: - Initializer
+
+ public init(data: Data, etag: String) {
+ self.data = data
+ self.metadata = Metadata(etag: etag, lastUsed: .now)
+ }
+
+}
diff --git a/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift b/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift
new file mode 100644
index 0000000..6ac2e17
--- /dev/null
+++ b/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift
@@ -0,0 +1,22 @@
+//
+// APIURL.swift
+// MSCoreKit
+//
+// Created by 전민건 on 11/16/23.
+//
+
+import Foundation
+
+enum APIbaseURL: String {
+
+ // base URL 추가
+ case none = ""
+
+}
+
+enum APIpathURL: String {
+
+ // path URL 추가
+ case none = ""
+
+}
diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift
new file mode 100644
index 0000000..787d47b
--- /dev/null
+++ b/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift
@@ -0,0 +1,42 @@
+//
+// MSRouter.swift
+// MSCoreKit
+//
+// Created by 전민건 on 11/16/23.
+//
+
+import Foundation
+
+public struct RouterType {
+
+ private var encodable: Encodable?
+
+ // 기능별 MSRouter
+ public var getJourney: MSRouter {
+ MSRouter(baseURL: .none, pathURL: .none, method: .get, body: HTTPBody(content: encodable))
+ }
+ public var getPerson: MSRouter {
+ MSRouter(baseURL: .none, pathURL: .none, method: .get, body: HTTPBody(content: encodable))
+ }
+
+}
+
+public struct MSRouter: Router {
+
+ let baseURL: APIbaseURL
+ let pathURL: APIpathURL
+ let method: HTTPMethod
+ var body: HTTPBody
+
+ func asURLRequest() -> URLRequest? {
+ guard let url = URL(string: baseURL.rawValue + pathURL.rawValue) else {
+ return nil
+ }
+ var request = URLRequest(url: url)
+ request.httpMethod = method.rawValue
+ request.httpBody = body.contentToData()
+
+ return request
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj
index 645380b..5fd5a35 100644
--- a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj
+++ b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj
@@ -7,6 +7,20 @@
objects = {
/* Begin PBXBuildFile section */
+ 08CBF8782B18468E007D3797 /* SaveJourneyCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8702B18468E007D3797 /* SaveJourneyCoordinator.swift */; };
+ 08CBF8792B18468E007D3797 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8712B18468E007D3797 /* AppCoordinator.swift */; };
+ 08CBF87A2B18468E007D3797 /* RewindCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8722B18468E007D3797 /* RewindCoordinator.swift */; };
+ 08CBF87B2B18468E007D3797 /* HomeMapCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8732B18468E007D3797 /* HomeMapCoordinator.swift */; };
+ 08CBF87C2B18468E007D3797 /* SettingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8742B18468E007D3797 /* SettingCoordinator.swift */; };
+ 08CBF87D2B18468E007D3797 /* SpotCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8752B18468E007D3797 /* SpotCoordinator.swift */; };
+ 08CBF87E2B18468E007D3797 /* SearchMusicCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8762B18468E007D3797 /* SearchMusicCoordinator.swift */; };
+ 08CBF87F2B18468E007D3797 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8772B18468E007D3797 /* Coordinator.swift */; };
+ 08CBF8872B1846A0007D3797 /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8812B1846A0007D3797 /* SettingViewController.swift */; };
+ 08CBF8882B1846A0007D3797 /* HomeMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8822B1846A0007D3797 /* HomeMapViewController.swift */; };
+ 08CBF8892B1846A0007D3797 /* SaveJourneyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8832B1846A0007D3797 /* SaveJourneyViewController.swift */; };
+ 08CBF88A2B1846A0007D3797 /* SpotViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8842B1846A0007D3797 /* SpotViewController.swift */; };
+ 08CBF88B2B1846A0007D3797 /* RewindViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8852B1846A0007D3797 /* RewindViewController.swift */; };
+ 08CBF88C2B1846A0007D3797 /* SearchMusicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8862B1846A0007D3797 /* SearchMusicViewController.swift */; };
DD5EA2552B16EC920080AEC1 /* JourneyList in Frameworks */ = {isa = PBXBuildFile; productRef = DD5EA2542B16EC920080AEC1 /* JourneyList */; };
DD5EA2572B16EC960080AEC1 /* NavigateMap in Frameworks */ = {isa = PBXBuildFile; productRef = DD5EA2562B16EC960080AEC1 /* NavigateMap */; };
DD5EA2592B16EC9B0080AEC1 /* RecordJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD5EA2582B16EC9B0080AEC1 /* RecordJourney */; };
@@ -23,6 +37,20 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 08CBF8702B18468E007D3797 /* SaveJourneyCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveJourneyCoordinator.swift; sourceTree = ""; };
+ 08CBF8712B18468E007D3797 /* AppCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; };
+ 08CBF8722B18468E007D3797 /* RewindCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RewindCoordinator.swift; sourceTree = ""; };
+ 08CBF8732B18468E007D3797 /* HomeMapCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeMapCoordinator.swift; sourceTree = ""; };
+ 08CBF8742B18468E007D3797 /* SettingCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingCoordinator.swift; sourceTree = ""; };
+ 08CBF8752B18468E007D3797 /* SpotCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpotCoordinator.swift; sourceTree = ""; };
+ 08CBF8762B18468E007D3797 /* SearchMusicCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchMusicCoordinator.swift; sourceTree = ""; };
+ 08CBF8772B18468E007D3797 /* Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; };
+ 08CBF8812B1846A0007D3797 /* SettingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; };
+ 08CBF8822B1846A0007D3797 /* HomeMapViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeMapViewController.swift; sourceTree = ""; };
+ 08CBF8832B1846A0007D3797 /* SaveJourneyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveJourneyViewController.swift; sourceTree = ""; };
+ 08CBF8842B1846A0007D3797 /* SpotViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpotViewController.swift; sourceTree = ""; };
+ 08CBF8852B1846A0007D3797 /* RewindViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RewindViewController.swift; sourceTree = ""; };
+ 08CBF8862B1846A0007D3797 /* SearchMusicViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchMusicViewController.swift; sourceTree = ""; };
DD73F8552B024C4900EE9BF2 /* MusicSpot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MusicSpot.app; sourceTree = BUILT_PRODUCTS_DIR; };
DD73F8582B024C4900EE9BF2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
DD73F85A2B024C4900EE9BF2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
@@ -50,6 +78,42 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 08CBF86F2B18468E007D3797 /* MSCoordinator */ = {
+ isa = PBXGroup;
+ children = (
+ DD2856C32B187BAB002C994D /* Protocol */,
+ 08CBF8712B18468E007D3797 /* AppCoordinator.swift */,
+ 08CBF8732B18468E007D3797 /* HomeMapCoordinator.swift */,
+ 08CBF8752B18468E007D3797 /* SpotCoordinator.swift */,
+ 08CBF8762B18468E007D3797 /* SearchMusicCoordinator.swift */,
+ 08CBF8702B18468E007D3797 /* SaveJourneyCoordinator.swift */,
+ 08CBF8742B18468E007D3797 /* SettingCoordinator.swift */,
+ 08CBF8722B18468E007D3797 /* RewindCoordinator.swift */,
+ );
+ path = MSCoordinator;
+ sourceTree = "";
+ };
+ 08CBF8802B1846A0007D3797 /* TempViewController */ = {
+ isa = PBXGroup;
+ children = (
+ 08CBF8812B1846A0007D3797 /* SettingViewController.swift */,
+ 08CBF8822B1846A0007D3797 /* HomeMapViewController.swift */,
+ 08CBF8832B1846A0007D3797 /* SaveJourneyViewController.swift */,
+ 08CBF8842B1846A0007D3797 /* SpotViewController.swift */,
+ 08CBF8852B1846A0007D3797 /* RewindViewController.swift */,
+ 08CBF8862B1846A0007D3797 /* SearchMusicViewController.swift */,
+ );
+ path = TempViewController;
+ sourceTree = "";
+ };
+ DD2856C32B187BAB002C994D /* Protocol */ = {
+ isa = PBXGroup;
+ children = (
+ 08CBF8772B18468E007D3797 /* Coordinator.swift */,
+ );
+ path = Protocol;
+ sourceTree = "";
+ };
DD5EA23F2B16EC690080AEC1 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -77,6 +141,8 @@
DD73F8572B024C4900EE9BF2 /* MusicSpot */ = {
isa = PBXGroup;
children = (
+ 08CBF8802B1846A0007D3797 /* TempViewController */,
+ 08CBF86F2B18468E007D3797 /* MSCoordinator */,
DD73F8582B024C4900EE9BF2 /* AppDelegate.swift */,
DD73F85A2B024C4900EE9BF2 /* SceneDelegate.swift */,
DD73F8612B024C4B00EE9BF2 /* Assets.xcassets */,
@@ -180,7 +246,21 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 08CBF88C2B1846A0007D3797 /* SearchMusicViewController.swift in Sources */,
+ 08CBF8882B1846A0007D3797 /* HomeMapViewController.swift in Sources */,
+ 08CBF8792B18468E007D3797 /* AppCoordinator.swift in Sources */,
+ 08CBF8872B1846A0007D3797 /* SettingViewController.swift in Sources */,
+ 08CBF8782B18468E007D3797 /* SaveJourneyCoordinator.swift in Sources */,
+ 08CBF87F2B18468E007D3797 /* Coordinator.swift in Sources */,
+ 08CBF88B2B1846A0007D3797 /* RewindViewController.swift in Sources */,
+ 08CBF8892B1846A0007D3797 /* SaveJourneyViewController.swift in Sources */,
+ 08CBF88A2B1846A0007D3797 /* SpotViewController.swift in Sources */,
+ 08CBF87D2B18468E007D3797 /* SpotCoordinator.swift in Sources */,
+ 08CBF87E2B18468E007D3797 /* SearchMusicCoordinator.swift in Sources */,
+ 08CBF87A2B18468E007D3797 /* RewindCoordinator.swift in Sources */,
+ 08CBF87C2B18468E007D3797 /* SettingCoordinator.swift in Sources */,
DD73F8592B024C4900EE9BF2 /* AppDelegate.swift in Sources */,
+ 08CBF87B2B18468E007D3797 /* HomeMapCoordinator.swift in Sources */,
DD73F85B2B024C4900EE9BF2 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -333,6 +413,9 @@
INFOPLIST_FILE = MusicSpot/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = MusicSpot;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.travel";
+ INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "사용자의 위치 정보에 대한 엑세스가 필요합니다.";
+ INFOPLIST_KEY_NSLocationUsageDescription = "사용자의 위치 정보를 통해 실시간 위치 표시 및 데이터를 제공해드립니다.";
+ INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "사용자의 위치를 필요 시 쓰겠습니다.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
@@ -366,6 +449,9 @@
INFOPLIST_FILE = MusicSpot/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = MusicSpot;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.travel";
+ INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "사용자의 위치 정보에 대한 엑세스가 필요합니다.";
+ INFOPLIST_KEY_NSLocationUsageDescription = "사용자의 위치 정보를 통해 실시간 위치 표시 및 데이터를 제공해드립니다.";
+ INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "사용자의 위치를 필요 시 쓰겠습니다.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
diff --git a/iOS/MusicSpot/MusicSpot.xcworkspace/contents.xcworkspacedata b/iOS/MusicSpot/MusicSpot.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..2cbce27
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/iOS/MusicSpot/MusicSpot.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/MusicSpot/MusicSpot.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/iOS/MusicSpot/MusicSpot/Info.plist b/iOS/MusicSpot/MusicSpot/Info.plist
index 0eb786d..558884a 100644
--- a/iOS/MusicSpot/MusicSpot/Info.plist
+++ b/iOS/MusicSpot/MusicSpot/Info.plist
@@ -2,6 +2,8 @@
+ NSLocationDefaultAccuracyReduced
+
UIApplicationSceneManifest
UIApplicationSupportsMultipleScenes
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/AppCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/AppCoordinator.swift
new file mode 100644
index 0000000..dbb394b
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/AppCoordinator.swift
@@ -0,0 +1,39 @@
+//
+// AppCoordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+protocol AppCoordinatorDelegate {
+
+ func popToHomeMap(from coordinator: Coordinator)
+ func popToSearchMusic(from coordinator: Coordinator)
+
+}
+
+final class AppCoordinator: Coordinator {
+
+ // MARK: - Properties
+
+ let navigationController: UINavigationController
+
+ var childCoordinators: [Coordinator] = []
+
+ // MARK: - Initializer
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ // MARK: - From: HomeMap
+
+ func start() {
+ let homeMapCoordinator = HomeMapCoordinator(navigationController: self.navigationController)
+ self.childCoordinators.append(homeMapCoordinator)
+ homeMapCoordinator.start()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/HomeMapCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/HomeMapCoordinator.swift
new file mode 100644
index 0000000..05920bd
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/HomeMapCoordinator.swift
@@ -0,0 +1,84 @@
+//
+// HomeMapCoordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+protocol HomeMapCoordinatorDelegate: AnyObject {
+
+ func popToHomeMap(from coordinator: Coordinator)
+
+}
+
+final class HomeMapCoordinator: Coordinator {
+
+ // MARK: - Properties
+
+ var navigationController: UINavigationController
+
+ var childCoordinators: [Coordinator] = []
+
+ var delegate: AppCoordinatorDelegate?
+
+ // MARK: - Initializer
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ // MARK: - Functions
+
+ func start() {
+ let homeMapViewController = HomeMapViewController()
+ homeMapViewController.delegate = self
+ self.navigationController.pushViewController(homeMapViewController, animated: true)
+ }
+
+}
+
+// MARK: - NavigateMapViewController
+
+extension HomeMapCoordinator: HomeMapViewControllerDelegate {
+
+ func navigateToSpot() {
+ let spotCoordinator = SpotCoordinator(navigationController: self.navigationController)
+ spotCoordinator.delegate = self
+ self.childCoordinators.append(spotCoordinator)
+ spotCoordinator.start()
+ }
+
+ func navigateToSearchMusic() {
+ let searchMusicCoordinator = SearchMusicCoordinator(navigationController: self.navigationController)
+ searchMusicCoordinator.delegate = self
+ self.childCoordinators.append(searchMusicCoordinator)
+ searchMusicCoordinator.start()
+ }
+
+ func navigateToRewind() {
+ let rewindCoordinator = RewindCoordinator(navigationController: self.navigationController)
+ rewindCoordinator.delegate = self
+ self.childCoordinators.append(rewindCoordinator)
+ rewindCoordinator.start()
+ }
+
+ func navigateToSetting() {
+ let settingCoordinator = SettingCoordinator(navigationController: self.navigationController)
+ settingCoordinator.delegate = self
+ self.childCoordinators.append(settingCoordinator)
+ settingCoordinator.start()
+ }
+
+}
+
+// MARK: - HomeMap Coordinator
+
+extension HomeMapCoordinator: HomeMapCoordinatorDelegate {
+
+ func popToHomeMap(from coordinator: Coordinator) {
+ self.childCoordinators.removeAll()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/Protocol/Coordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/Protocol/Coordinator.swift
new file mode 100644
index 0000000..940294a
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/Protocol/Coordinator.swift
@@ -0,0 +1,15 @@
+//
+// Coordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/29/23.
+//
+
+import UIKit
+
+protocol Coordinator: AnyObject {
+
+ var navigationController: UINavigationController { get }
+ var childCoordinators: [Coordinator] { get set }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/RewindCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/RewindCoordinator.swift
new file mode 100644
index 0000000..9d11731
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/RewindCoordinator.swift
@@ -0,0 +1,56 @@
+//
+// RewindCoordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+final class RewindCoordinator: Coordinator {
+
+ // MARK: - Properties
+
+ var navigationController: UINavigationController
+
+ var childCoordinators: [Coordinator] = []
+
+ var delegate: HomeMapCoordinatorDelegate?
+
+ // MARK: - Initializer
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ // MARK: - Functions
+
+ func start() {
+ let rewindViewController = RewindViewController()
+ rewindViewController.delegate = self
+ self.navigationController.pushViewController(rewindViewController, animated: true)
+ }
+
+}
+
+// MARK: - RewindViewController
+
+extension RewindCoordinator: RewindViewControllerDelegate {
+
+ func navigateToHomeMap() {
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+}
+
+// MARK: - HomeMap Coordinator
+
+extension RewindCoordinator: HomeMapCoordinatorDelegate {
+
+ func popToHomeMap(from coordinator: Coordinator) {
+ self.childCoordinators.removeAll()
+ self.navigationController.popViewController(animated: true)
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift
new file mode 100644
index 0000000..c447085
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift
@@ -0,0 +1,48 @@
+//
+// SearchMusicCoordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/29/23.
+//
+
+import UIKit
+
+final class SaveJourneyCoordinator: Coordinator {
+
+ // MARK: - Properties
+
+ var navigationController: UINavigationController
+
+ var childCoordinators: [Coordinator] = []
+
+ var delegate: SearchMusicCoordinatorDelegate?
+
+ // MARK: - Initializer
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ // MARK: - Functions
+
+ func start() {
+ let saveJourneyViewController = SaveJourneyViewController()
+ saveJourneyViewController.delegate = self
+ self.navigationController.pushViewController(saveJourneyViewController, animated: true)
+ }
+
+}
+
+// MARK: - SaveJourneyViewController
+
+extension SaveJourneyCoordinator: SaveJourneyViewControllerDelegate {
+
+ func navigateToHomeMap() {
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+ func navigateToSearchMusic() {
+ self.delegate?.popToSearchMusic(from: self)
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/SearchMusicCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/SearchMusicCoordinator.swift
new file mode 100644
index 0000000..bfa934f
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/SearchMusicCoordinator.swift
@@ -0,0 +1,74 @@
+//
+// SearchMusicCoordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/29/23.
+//
+
+import UIKit
+
+protocol SearchMusicCoordinatorDelegate {
+
+ func popToHomeMap(from coordinator: Coordinator)
+ func popToSearchMusic(from coordinator: Coordinator)
+
+}
+
+final class SearchMusicCoordinator: Coordinator {
+
+ // MARK: - Properties
+
+ var navigationController: UINavigationController
+
+ var childCoordinators: [Coordinator] = []
+
+ var delegate: HomeMapCoordinatorDelegate?
+
+ // MARK: - Initializer
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ // MARK: - Functions
+
+ func start() {
+ let searchMusicViewController = SearchMusicViewController()
+ searchMusicViewController.delegate = self
+ self.navigationController.pushViewController(searchMusicViewController, animated: true)
+ }
+
+}
+
+// MARK: - SearchMusicViewController
+
+extension SearchMusicCoordinator: SearchMusicViewControllerDelegate {
+
+ func navigateToHomeMap() {
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+ func navigateToSaveJourney() {
+ let saveJourneyCoordinator = SaveJourneyCoordinator(navigationController: self.navigationController)
+ saveJourneyCoordinator.delegate = self
+ self.childCoordinators.append(saveJourneyCoordinator)
+ saveJourneyCoordinator.start()
+ }
+
+}
+
+// MARK: - App Coordinator
+
+extension SearchMusicCoordinator: SearchMusicCoordinatorDelegate {
+
+ func popToHomeMap(from coordinator: Coordinator) {
+ self.childCoordinators.removeAll()
+ self.navigationController.popViewController(animated: true)
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+ func popToSearchMusic(from coordinator: Coordinator) {
+ self.childCoordinators.removeAll()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/SettingCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/SettingCoordinator.swift
new file mode 100644
index 0000000..f429580
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/SettingCoordinator.swift
@@ -0,0 +1,56 @@
+//
+// SettingCoordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+final class SettingCoordinator: Coordinator {
+
+ // MARK: - Properties
+
+ var navigationController: UINavigationController
+
+ var childCoordinators: [Coordinator] = []
+
+ var delegate: HomeMapCoordinatorDelegate?
+
+ // MARK: - Initializer
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ // MARK: - Functions
+
+ func start() {
+ let settingViewController = SettingViewController()
+ settingViewController.delegate = self
+ self.navigationController.pushViewController(settingViewController, animated: true)
+ }
+
+}
+
+// MARK: - SettingViewController
+
+extension SettingCoordinator: SettingViewControllerDelegate {
+
+ func navigateToHomeMap() {
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+}
+
+// MARK: - HomeMap Coordinator
+
+extension SettingCoordinator: HomeMapCoordinatorDelegate {
+
+ func popToHomeMap(from coordinator: Coordinator) {
+ self.childCoordinators.removeAll()
+ self.navigationController.popViewController(animated: true)
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/SpotCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/SpotCoordinator.swift
new file mode 100644
index 0000000..a1d81bf
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/SpotCoordinator.swift
@@ -0,0 +1,56 @@
+//
+// SpotCoordinator.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+final class SpotCoordinator: Coordinator {
+
+ // MARK: - Properties
+
+ var navigationController: UINavigationController
+
+ var childCoordinators: [Coordinator] = []
+
+ var delegate: HomeMapCoordinatorDelegate?
+
+ // MARK: - Initializer
+
+ init(navigationController: UINavigationController) {
+ self.navigationController = navigationController
+ }
+
+ // MARK: - Functions
+
+ func start() {
+ let spotViewController = SpotViewController()
+ spotViewController.delegate = self
+ self.navigationController.pushViewController(spotViewController, animated: true)
+ }
+
+}
+
+// MARK: - SpotViewController
+
+extension SpotCoordinator: SpotViewControllerDelegate {
+
+ func navigateToHomeMap() {
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+}
+
+// MARK: - HomeMap Coordinator
+
+extension SpotCoordinator: HomeMapCoordinatorDelegate {
+
+ func popToHomeMap(from coordinator: Coordinator) {
+ self.childCoordinators.removeAll()
+ self.navigationController.popViewController(animated: true)
+ self.delegate?.popToHomeMap(from: self)
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift
index f0ed3b8..918046c 100644
--- a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift
+++ b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift
@@ -6,6 +6,7 @@
//
import UIKit
+import NavigateMap
import MSDesignSystem
@@ -16,7 +17,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// MARK: - Functions
-
+
func scene(_ scene: UIScene,
willConnectTo _: UISceneSession,
options _: UIScene.ConnectionOptions) {
@@ -24,10 +25,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let window = UIWindow(windowScene: windowScene)
defer { self.window = window }
- MSFont.registerFonts()
+ let musicSpotNavigationController = UINavigationController()
+ let appCoordinator = AppCoordinator(navigationController: musicSpotNavigationController)
+ window.rootViewController = musicSpotNavigationController
+
+ appCoordinator.start()
- let testViewController = UIViewController()
- window.rootViewController = testViewController
window.makeKeyAndVisible()
}
diff --git a/iOS/MusicSpot/MusicSpot/TempViewController/HomeMapViewController.swift b/iOS/MusicSpot/MusicSpot/TempViewController/HomeMapViewController.swift
new file mode 100644
index 0000000..94052d9
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/TempViewController/HomeMapViewController.swift
@@ -0,0 +1,94 @@
+//
+// NavigateMapViewController.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+protocol HomeMapViewControllerDelegate: AnyObject {
+ func navigateToSpot()
+ func navigateToSearchMusic()
+ func navigateToRewind()
+ func navigateToSetting()
+}
+
+class HomeMapViewController: UIViewController {
+
+ // MARK: - Properties
+
+ var delegate: HomeMapViewControllerDelegate?
+
+ var titleLabel: UILabel = {
+ var label = UILabel()
+
+ label.text = "HomeMap"
+
+ return label
+ }()
+
+ var startButton: UIButton = {
+ let button = UIButton()
+
+ button.setTitle("시작하기", for: .normal)
+ button.layer.cornerRadius = 8
+ button.backgroundColor = .gray
+
+ return button
+ }()
+
+ // MARK: - Life Cycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.view.backgroundColor = .white
+
+ self.configureStyle()
+ self.configureLayout()
+ }
+
+ // MARK: - Functions
+
+ private func configureStyle() {
+ self.view.addSubview(titleLabel)
+ self.view.addSubview(startButton)
+
+ self.startButton.addTarget(self, action: #selector(navigateToSpot), for: .touchUpInside)
+ }
+
+ private func configureLayout() {
+
+ self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
+ self.startButton.translatesAutoresizingMaskIntoConstraints = false
+
+ let safeArea = view.safeAreaLayoutGuide
+
+ NSLayoutConstraint.activate([
+ self.startButton.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 40),
+ self.startButton.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 100),
+ self.startButton.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -100),
+ self.startButton.heightAnchor.constraint(equalToConstant: 50),
+
+ self.titleLabel.topAnchor.constraint(equalTo: startButton.bottomAnchor, constant: 40),
+ self.titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ self.titleLabel.heightAnchor.constraint(equalToConstant: 50)
+ ])
+ }
+
+ @objc
+ func navigateToSpot() {
+ self.delegate?.navigateToSpot()
+ }
+
+ @objc
+ func navigateToRewind() {
+ self.delegate?.navigateToRewind()
+ }
+
+ @objc
+ func navigateToSetting() {
+ self.delegate?.navigateToSetting()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/TempViewController/RewindViewController.swift b/iOS/MusicSpot/MusicSpot/TempViewController/RewindViewController.swift
new file mode 100644
index 0000000..5dfec63
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/TempViewController/RewindViewController.swift
@@ -0,0 +1,35 @@
+//
+// RewindViewController.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+protocol RewindViewControllerDelegate: AnyObject {
+ func navigateToHomeMap()
+}
+
+class RewindViewController: UIViewController {
+
+ var delegate: RewindViewControllerDelegate?
+
+ var label: UILabel = {
+ var label = UILabel()
+ label.text = "Rewind"
+ return label
+ }()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.view.backgroundColor = .red
+ self.view.addSubview(label)
+ }
+
+ @objc
+ func navigateToHomeMap() {
+ self.delegate?.navigateToHomeMap()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/TempViewController/SaveJourneyViewController.swift b/iOS/MusicSpot/MusicSpot/TempViewController/SaveJourneyViewController.swift
new file mode 100644
index 0000000..c3c693b
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/TempViewController/SaveJourneyViewController.swift
@@ -0,0 +1,47 @@
+//
+// RewindViewController.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/29/23.
+//
+
+import UIKit
+
+protocol SaveJourneyViewControllerDelegate: AnyObject {
+ func navigateToHomeMap()
+ func navigateToSearchMusic()
+}
+
+class SaveJourneyViewController: UIViewController {
+
+ // MARK: - Properties
+
+ var delegate: SaveJourneyViewControllerDelegate?
+
+ var label: UILabel = {
+ var label = UILabel()
+ label.text = "SaveJourney"
+ return label
+ }()
+
+ // MARK: - Life Cycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.view.backgroundColor = .red
+ self.view.addSubview(label)
+ }
+
+ // MARK: - Functions
+
+ @objc
+ func navigateToHomeMap() {
+ self.delegate?.navigateToHomeMap()
+ }
+
+ @objc
+ func navigateToSearchMusic() {
+ self.delegate?.navigateToSearchMusic()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/TempViewController/SearchMusicViewController.swift b/iOS/MusicSpot/MusicSpot/TempViewController/SearchMusicViewController.swift
new file mode 100644
index 0000000..c44e405
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/TempViewController/SearchMusicViewController.swift
@@ -0,0 +1,41 @@
+//
+// RewindViewController.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+protocol SearchMusicViewControllerDelegate: AnyObject {
+ func navigateToHomeMap()
+ func navigateToSaveJourney()
+}
+
+class SearchMusicViewController: UIViewController {
+
+ var delegate: SearchMusicViewControllerDelegate?
+
+ var label: UILabel = {
+ var label = UILabel()
+ label.text = "SearchMusic"
+ return label
+ }()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.view.backgroundColor = .red
+ self.view.addSubview(label)
+ }
+
+ @objc
+ func navigateToHomeMap() {
+ self.delegate?.navigateToHomeMap()
+ }
+
+ @objc
+ func navigateToSaveJourney() {
+ self.delegate?.navigateToSaveJourney()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/TempViewController/SettingViewController.swift b/iOS/MusicSpot/MusicSpot/TempViewController/SettingViewController.swift
new file mode 100644
index 0000000..bbd82c4
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/TempViewController/SettingViewController.swift
@@ -0,0 +1,35 @@
+//
+// SettingViewController.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+protocol SettingViewControllerDelegate: AnyObject {
+ func navigateToHomeMap()
+}
+
+class SettingViewController: UIViewController {
+
+ var delegate: SettingViewControllerDelegate?
+
+ var label: UILabel = {
+ var label = UILabel()
+ label.text = "Setting"
+ return label
+ }()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.view.backgroundColor = .red
+ self.view.addSubview(label)
+ }
+
+ @objc
+ func navigateToHomeMap() {
+ self.delegate?.navigateToHomeMap()
+ }
+
+}
diff --git a/iOS/MusicSpot/MusicSpot/TempViewController/SpotViewController.swift b/iOS/MusicSpot/MusicSpot/TempViewController/SpotViewController.swift
new file mode 100644
index 0000000..525c7f1
--- /dev/null
+++ b/iOS/MusicSpot/MusicSpot/TempViewController/SpotViewController.swift
@@ -0,0 +1,86 @@
+//
+// SpotViewController.swift
+// MusicSpot
+//
+// Created by 윤동주 on 11/28/23.
+//
+
+import UIKit
+
+protocol SpotViewControllerDelegate: AnyObject {
+ func navigateToHomeMap()
+}
+
+class SpotViewController: UIViewController {
+
+ // MARK: - Properties
+
+ var delegate: SpotViewControllerDelegate?
+
+ var number = 0
+
+ lazy var titleLabel: UILabel = {
+ var label = UILabel()
+ label.text = "\(number)"
+ return label
+ }()
+
+ var startButton: UIButton = {
+ let button = UIButton()
+
+ button.setTitle("시작하기", for: .normal)
+ button.layer.cornerRadius = 8
+ button.backgroundColor = .orange
+
+ return button
+ }()
+
+ // MARK: - Life Cycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.view.backgroundColor = .white
+
+ self.configureStyle()
+ self.configureLayout()
+ }
+
+ override func viewDidDisappear(_ animated: Bool) {
+ super.viewDidDisappear(animated)
+
+ self.delegate?.navigateToHomeMap()
+ }
+
+ // MARK: - Functions
+
+ private func configureStyle() {
+ self.view.addSubview(titleLabel)
+ self.view.addSubview(startButton)
+
+ self.startButton.addTarget(self, action: #selector(navigateToHomeMap), for: .touchUpInside)
+ }
+
+ private func configureLayout() {
+ self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
+ self.startButton.translatesAutoresizingMaskIntoConstraints = false
+
+ let safeArea = view.safeAreaLayoutGuide
+
+ NSLayoutConstraint.activate([
+ self.startButton.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 40),
+ self.startButton.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 100),
+ self.startButton.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -100),
+ self.startButton.heightAnchor.constraint(equalToConstant: 50),
+
+ self.titleLabel.topAnchor.constraint(equalTo: startButton.bottomAnchor, constant: 40),
+ self.titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ self.titleLabel.heightAnchor.constraint(equalToConstant: 50)
+ ])
+ }
+
+ @objc
+ func navigateToHomeMap() {
+ self.delegate?.navigateToHomeMap()
+ }
+
+}
diff --git a/iOS/commit b/iOS/commit
index e43386d..8eac4c3 100755
--- a/iOS/commit
+++ b/iOS/commit
@@ -1,9 +1,9 @@
#!/bin/sh
-
+
LINTPATH='.swiftlint.yml'
declare -a PATHS=("MSCoreKit" "MSFoundation" "MSUIKit" "MSData" "MusicSpot" "Features")
failures=""
-
+
for path in "${PATHS[@]}"; do
if [ -d "$path" ]; then
echo "👀 Running SwiftLint for $path"