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"