Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,5 @@ fastlane/test_output
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/
iOSInjectionProject/
RunLog/Secrets.xcconfig
6 changes: 6 additions & 0 deletions RunLog/RunLog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
16F688CA2D82974E00C2163F /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 16F688C92D82974E00C2163F /* Moya */; };
595FD80C2D90474C0073F561 /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 595FD80B2D90474C0073F561 /* Secrets.xcconfig */; };
595FD80D2D90474C0073F561 /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 595FD80B2D90474C0073F561 /* Secrets.xcconfig */; };
5EF1F5D62DA6266900C34BCB /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1F5D52DA6266900C34BCB /* Secrets.xcconfig */; };
5EF1F5D72DA6266900C34BCB /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 5EF1F5D52DA6266900C34BCB /* Secrets.xcconfig */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -31,6 +33,7 @@
16811EF62D8A9E520013461D /* RpTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RpTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
16F686B72D827D6C00C2163F /* RunLog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RunLog.app; sourceTree = BUILT_PRODUCTS_DIR; };
595FD80B2D90474C0073F561 /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = "<group>"; };
5EF1F5D52DA6266900C34BCB /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
Expand Down Expand Up @@ -90,6 +93,7 @@
16F686AE2D827D6C00C2163F = {
isa = PBXGroup;
children = (
5EF1F5D52DA6266900C34BCB /* Secrets.xcconfig */,
595FD80B2D90474C0073F561 /* Secrets.xcconfig */,
16F686B92D827D6C00C2163F /* RunLog */,
16811EF72D8A9E520013461D /* RpTest */,
Expand Down Expand Up @@ -212,6 +216,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5EF1F5D62DA6266900C34BCB /* Secrets.xcconfig in Resources */,
595FD80C2D90474C0073F561 /* Secrets.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -220,6 +225,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5EF1F5D72DA6266900C34BCB /* Secrets.xcconfig in Resources */,
595FD80D2D90474C0073F561 /* Secrets.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
2 changes: 1 addition & 1 deletion RunLog/RunLog/Resources/DesignSystem/FontSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ extension UIFont {
case thin = "Pretendard-Thin"
}

// 다이나믹 폰트 사이즈 설정
/// 다이나믹 폰트 사이즈 설정
private static func dynamicFont(
name: String,
baseSize: CGFloat,
Expand Down
17 changes: 14 additions & 3 deletions RunLog/RunLog/Resources/DesignSystem/LabelSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ final class RLLabel: UIView {
var icon = UIImageView()
var label = UILabel()

/// Label의 attributedText 설정
var attributedText: NSAttributedString? {
get {
return label.attributedText
}
set {
label.text = nil
label.text = nil // 기존의 text가 있으면 삭제 후 지정
label.attributedText = newValue
}
}

/// text와 icon의 색상 변경
override var tintColor: UIColor! {
get {
return label.tintColor
Expand All @@ -35,7 +37,14 @@ final class RLLabel: UIView {
}
}

/// 레이블 생성
/// 아이콘을 내포한 레이블을 생성합니다.
/// - Parameters:
/// - text: 레이블의 텍스트
/// - textColor: 텍스트 색상 (기본값: `.Gray000`)
/// - icon: 레이블에 들어갈 아이콘 이미지 (기본값: `nil`)
/// - align: 레이블 정렬 상태 (기본값: `.left`)
/// - font: 레이블의 폰트 (기본값: `.RLLabel2`)
/// - tintColor: 레이블의 틴트 색상 (기본값: `.Gray000`)
public init(
text: String = "Custom Label",
textColor: UIColor = .Gray000,
Expand All @@ -61,8 +70,10 @@ final class RLLabel: UIView {

// MARK: - Setup UI
private func setupUI() {
// UI 요소 추가
// subview
self.addSubviews(icon, label)

// autoLayout
if icon.image == nil {
label.snp.makeConstraints {
$0.top.bottom.leading.trailing.equalToSuperview()
Expand Down
1 change: 0 additions & 1 deletion RunLog/RunLog/Resources/DesignSystem/TextFieldSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ open class RLTextField: UITextField {

// MARK: - Init
public init(
// Q) 패딩 디폴트값에는 어떻게 다이나믹 패딩을 적용하나요!?
padding: UIEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16),
placeholder: String? = nil
) {
Expand Down
4 changes: 3 additions & 1 deletion RunLog/RunLog/Sources/Data/DistanceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import Foundation
import Combine
import CoreLocation


/// 사용자가 움직인 거리를 측정하는 매니저
final class DistanceManager {

// MARK: - Singleton
Expand Down Expand Up @@ -49,7 +51,7 @@ final class DistanceManager {
.store(in: &cancellables)
}

// MARK: - 이동 거리를 계산해서 output으로 send
/// 이전위치와 현재위치를 전달받아 움직인거리를 output으로 send
private func calculateDistance(previous: CLLocation, current: CLLocation) {
let distance = current.distance(from: previous)
if distance >= 1 {
Expand Down
2 changes: 0 additions & 2 deletions RunLog/RunLog/Sources/Data/DrawingManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ final class DrawingManager: NSObject, MKMapViewDelegate {
// MARK: - Output
enum Output {
case responsePolyline(MKPolyline)
case responseFullRoutePolyline(MKMapView)
}
let output = PassthroughSubject<Output, Never>()

Expand All @@ -50,7 +49,6 @@ final class DrawingManager: NSObject, MKMapViewDelegate {
mapView.delegate = self
let polyline = createFullRoutePolylines(route)
mapView.addOverlay(polyline)
self.output.send(.responseFullRoutePolyline(mapView))
mapView.delegate = nil
}
}
Expand Down
61 changes: 44 additions & 17 deletions RunLog/RunLog/Sources/Data/LocationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import MapKit
import CoreLocation
import Combine

/// 사용자의 위치 정보를 받아오는 매니저
final class LocationManager: NSObject, CLLocationManagerDelegate {

// MARK: - Singleton
Expand All @@ -19,6 +20,7 @@ final class LocationManager: NSObject, CLLocationManagerDelegate {
setupLocationManager()
bind()
}

deinit {
locationManager.stopUpdatingLocation()
}
Expand Down Expand Up @@ -53,49 +55,63 @@ final class LocationManager: NSObject, CLLocationManagerDelegate {
case .requestCurrentLocation:
guard let location = self.locationManager.location else { return }
self.output.send(.locationUpdate(location))

case .requestCityName(let location):
self.fetchCityName(location: location)
}
}
.store(in: &cancellables)
}
}

// MARK: - CLLocationManager 설정
extension LocationManager {

/// CLLocationManager 기본 설정
private func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
// Q) 지피티 피셜 걷기+달리기면 5m가 적당하다 - 실제로 3, 5로 해서 측정해보고 결정
locationManager.distanceFilter = 3
// 4미터 이동 시 사용자의 위치를 받아옴
locationManager.distanceFilter = 4
// 백그라운드 상태에서도 위치 업데이트
locationManager.allowsBackgroundLocationUpdates = true
// 사용자가 멈춰있으면 업데이트 일시정지
locationManager.pausesLocationUpdatesAutomatically = true
// 사용자의 위치 정보 권한 확인
getLocationUsagePermission()
}
}

// MARK: - 사용자 위치 데이터
extension LocationManager {

// MARK: - 사용자가 위치를 이동하면 output으로 send를 보냄
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
/// 사용자의 위치를 받아오는 Delegate 함수
func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
guard let location = locations.last else { return }

// GPS 신호가 불안정한 경우 필터링
if previousLocation != nil && (location.horizontalAccuracy < 0 || location.horizontalAccuracy > 10) {
if previousLocation != nil &&
(location.horizontalAccuracy < 0 ||
location.horizontalAccuracy > 10)
{
print("GPS 신호 불안정 - 위치 무시")
return
}
// 이전 위치와 비교하여 1m 이하 이동 시 무시
if let previous = previousLocation, location.distance(from: previous) < 1 {

// 이전 위치와 비교하여 1m 이하 이동 시 무시 - 위치가 튀는것을 방지
if let previous = previousLocation,
location.distance(from: previous) < 1
{
print("이동 거리 1m 이하 - 위치 업데이트 안함")
return
}

// 사용자의 위치를 output으로 send
self.output.send(.locationUpdate(location))
// 현재위치를 이전위치로 기억
previousLocation = location
}
}

// MARK: - 도시명 가져오기
extension LocationManager {
// MARK: - 도시명 가져오기

/// 사용자 위치의 도시명 받아오기
private func fetchCityName(location: CLLocation) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) {
Expand All @@ -112,30 +128,41 @@ extension LocationManager {

// MARK: - 위치 정보 권한 요청
extension LocationManager {

// MARK: - 권한 정보가 바뀌면 실행
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
func locationManager(
_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus
) {
getLocationUsagePermission()
}

// MARK: - 권한 정보에 따른 분기 처리
private func getLocationUsagePermission() {
let status = locationManager.authorizationStatus

switch status {
case .notDetermined: // 허용 안한 상태
locationManager.requestWhenInUseAuthorization()

case .authorizedWhenInUse: // 앱을 사용동안 허용
locationManager.requestAlwaysAuthorization()

case .restricted, .denied: // 거부 상태
print("위치 권한이 거부됨 - 설정에서 변경 필요")
openAppSettings()

case .authorizedAlways: // 항상 허용
locationManager.startUpdatingLocation()

@unknown default:
return
}
}
// MARK: - 앱 설정 열기
private func openAppSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }

if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ final class OpenWeatherService: NetworkService {
self.input
.sink { [weak self] input in
guard let self = self else { return }

switch input {
case .requestWeather(let location):
// 두 데이터가 모두 도착하면 전달
Publishers.Zip(
self.fetchWeatherData(location: location),
self.fetchAqiData(location: location)
Expand All @@ -55,6 +57,7 @@ final class OpenWeatherService: NetworkService {
}
} receiveValue: { [weak self] weather, aqi in
guard let self = self else { return }

self.output.send(.responseWeather(weather: weather, aqi: aqi))
}
.store(in: &self.cancellables)
Expand All @@ -66,10 +69,13 @@ final class OpenWeatherService: NetworkService {

// MARK: - 날씨정보 요청
extension OpenWeatherService {

/// 위치를 전달하면 날씨 정보(WeatherResonse)를 반환하는 Publisher를 반환
private func fetchWeather(lat: Double, lon: Double) -> AnyPublisher<WeatherResponse, NetworkError> {
return request(.weather(lat: lat, lon: lon), responseType: WeatherResponse.self)
}

/// 날씨정보를 반환하는 Publisher에서 온도와 날씨 상태 데이터를 뽑아서 반환하는 Publisher를 반환
private func fetchWeatherData(location: CLLocation) -> AnyPublisher<(Int, Double), Never> {
return self.fetchWeather(
lat: location.coordinate.latitude,
Expand All @@ -90,10 +96,13 @@ extension OpenWeatherService {

// MARK: - 대기질정보 요청
extension OpenWeatherService {

/// 위치를 전달하면 대기질 정보(AQIResonse)를 반환하는 Publisher를 반환
private func fetchAqi(lat: Double, lon: Double) -> AnyPublisher<AQIResponse, NetworkError> {
return request(.airQuality(lat: lat, lon: lon), responseType: AQIResponse.self)
}

/// 대기질 정보를 반환하는 Publisher에서 대기질 상태 데이터를 뽑아서 반환하는 Publisher를 반환
private func fetchAqiData(location: CLLocation) -> AnyPublisher<Int, Never> {
return self.fetchAqi(
lat: location.coordinate.latitude,
Expand Down
8 changes: 8 additions & 0 deletions RunLog/RunLog/Sources/Data/PedometerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Foundation
import Combine
import CoreMotion

/// 사용자가 걸은 걸음수를 받아오는 매니저
final class PedometerManager {

// MARK: - Singleton
Expand Down Expand Up @@ -44,6 +45,7 @@ final class PedometerManager {
switch input {
case .requestPedometerStart:
self.pedometerUpdateStart()

case .requestPedometerStop:
self.pedometerUpdateStop()
}
Expand All @@ -53,15 +55,21 @@ final class PedometerManager {

// MARK: - 걸음 수 측정 시작
private func pedometerUpdateStart() {

/// 걸음수를 측정 가능한 기기 확인
guard CMPedometer.isStepCountingAvailable() else {
print("측정 불가 기기")
return
}

/// 측정가능한 기기의 경우 측정 시작
pedometer.startUpdates(from: Date()) { [weak self] data, error in
guard let self = self,
let data = data,
error == nil
else { return }

// 걸음수를 Int로 변경
let stepCount = data.numberOfSteps.intValue
self.output.send(.responseSteps(stepCount))
}
Expand Down
Loading