From a332c039baa45db4b381e0eb9eb896c3725cc55f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Mon, 16 Jun 2025 16:20:27 +0900
Subject: [PATCH 01/54] =?UTF-8?q?=EB=A6=AC=EB=93=9C=EB=AF=B8=20=EC=9E=91?=
=?UTF-8?q?=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 297 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 297 insertions(+)
create mode 100644 README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d4af0da
--- /dev/null
+++ b/README.md
@@ -0,0 +1,297 @@
+# ๐๐ Run Mile / ๋ฐ ๋ง์ผ
+๋ฌ๋๋ค์๊ฒ ๊ฐ์ฅ ์ค์ํ ๊ฒ์ ๋ฌ๋ํ๐
+
+์์ดํฐ์ ๋ฑ๋ก๋์ด์๋ ์ด๋ ๊ธฐ๋ก์ผ๋ก ์ ์ฝ๊ฒ ๋ฌ๋ํ ๋ง์ผ๋ฆฌ์ง๋ฅผ ์ถ์ ํด์ค๋๋ค!๐ฅ๐ฅ
+
+|์ํ|์ฑ์คํ ์ด ๋ฐฐํฌ ์๋ฃ ๋ฐ ์ ์ง๋ณด์ ์งํ ์ค(v1.0.0)|
+|:--|:--|
+|๊ธฐ์ ์คํ|SwiftUI, HealthKit, Realm, UserNotifications, Vision(์ด๋ฏธ์ง ๋ฐฐ๊ฒฝ ์ ๊ฑฐ)|
+|์ฑ์คํ ์ด|[Run Mile](https://apps.apple.com/kr/app/run-mile/id6747099791)|
+|์ด๋ฉ์ผ ๋ฌธ์|dlsqja567@naver.com|
+
+### ์ ๋ฐ ๊ด๋ฆฌ๋ฅผ ์ํ 3 Stpes
+1. ์ ๋ฐ์ ๋ฑ๋กํ๊ณ
+2. ์ด๋์ ์ถ๊ฐํ์ธ์!
+3. ์๋ ๋ฑ๋ก์ ํตํด ๋์ฑ ํธ๋ฆฌํ๊ฒ!
+
+
+
+
+
+
+## ๊ฐ๋ฐ ์ผ์ง
+|์ด๋ฆ|๋งํฌ|
+|:--|:--|
+|**0. ์ฌ์ด๋ ํ๋ก์ ํธ Run Mile ์ฑ ๊ฐ๋ฐ๊ธฐ**|https://velog.io/@mooninbeom/0.-์ฌ์ด๋-ํ๋ก์ ํธ-Run-Mile-์ฑ-๊ฐ๋ฐ๊ธฐ|
+|**1. HealthKit ๋ฐ์ดํฐ ์ฌ์ฉ(with Continuation)**|https://velog.io/@mooninbeom/1.-HealthKit-๋ฐ์ดํฐ-์ฌ์ฉwith-Continuation|
+|**2. ๋ฐฑ๊ทธ๋ผ์ด๋์์ HealthKit ํ์ฉํ๊ธฐ**|https://velog.io/@mooninbeom/2.-๋ฐฑ๊ทธ๋ผ์ด๋์์-HealthKit-ํ์ฉํ๊ธฐ-o2p1gg9l|
+
+
+
+
+
+## ์ด๋ฒ ํ๋ก์ ํธ์์ ๋ด๊ฐ ๋ฐฐ์ด ๊ฒ
+
+### ํด๋ฆฐ ์ํคํ
์ณ๋ฅผ ๋ด ์
๋ง์ ๋ง๊ฒ ์ฌ์ฉ
+
+
+
+### HealthKit ํ์ฉ
+์ด๋ฒ ํ๋ก์ ํธ์ ํต์ฌ์ ๊ธฐ๊ธฐ ๋ด์ ์๋ ์ด๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ ์
๋๋ค.
+๋๋ฌธ์ **HealthKit**์ ํ์ฉํด ์ํ๋ ๋ค์ํ ๊ธฐ๋ฅ๋ค์ ๊ตฌํํ์ต๋๋ค.
+
+**1๏ธโฃ ๊ถํ ์ค์ **
+
+๊ธฐ๋ณธ์ ์ผ๋ก ๊ฑด๊ฐ ๋ฐ์ดํฐ๋ Privacy ์์ญ์ด๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์์ ๊ถํ ํ์ฉ์ด ํ์ํ๊ณ ๋ค์ํ ๋ฐ์ดํฐ์ ์ข
๋ฅ ์ค ํ์ํ ์์ญ๋ง ์ถ๊ฐํด ์์ฒญ์ ํด์ผํฉ๋๋ค.
+๋ํ ์ฝ๊ธฐ(Read), ์ฐ๊ธฐ(Share) ์์ญ์ด ๋๋์ด์ ธ ์๊ธฐ ๋๋ฌธ์ ์ฑ์์ ํ์ํ ๋ถ๋ถ๋ง ๊ณ ๋ คํด ํ์ํ ํ์
๋ง ์ถ๊ฐํด ์์ฒญํด์ผ ํฉ๋๋ค.
+Run Mile์์๋ ์ฐ๊ธฐ๋ ํ์ฌ ์ฌ์ฉํ์ง ์๊ณ ์ด๋ ํ์
์ ์์ด์ ์ฝ๊ธฐ ๋ถ๋ถ๋ง ๊ถํ์ ์์ฒญํ๊ณ ์์ต๋๋ค.
+
+Run Mile์์๋ 2๊ฐ์ ์คํ
์ผ๋ก ๊ถํ์ ์์ฒญํฉ๋๋ค.
+
+
+
+**1. ๊ถํ ์์ฒญ ํ์ธ**
+
+๊ถํ ์์ฒญ์ด ์งํ๋ ์ํ์ธ์ง ํ์ธํ๊ณ ์์ฒญ์ด ๋์ง ์์์ ๊ฒฝ์ฐ ์์ฒญ์ ์งํํฉ๋๋ค.
+
+๊ถํ ์์ฒญ์ ๊ฒฝ์ฐ ํ์ฉ/ํ์ฉ์ํจ ์ฌ๋ถ์ ์๊ด์์ด ํ ๋ฒ ์์ฒญ์ด ์งํ๋๊ณ ๋๋ฉด ๋ค์ ์ฑ์์๋ ์์ฒญ์ ํ ์ ์์ต๋๋ค.
+
+๊ทธ๋์ ๋ถํ์ํ ์์ฒญ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์์ฒญ ์ ์์ฒญ ์ฌ๋ถ๋ฅผ ํ์ธํฉ๋๋ค.
+
+๋ง์ฝ ์ด๋ฏธ ์์ฒญ๋ ์ํ์ด๋ฉด ์ฌ์ฉ์๊ฐ ์ง์ ์ค์ ์์ ๋ฐ๊พธ์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฐฉํฅ์ผ๋ก ์ ๋ํ๋ UX๊ฐ ํ์ํฉ๋๋ค.
+
+```swift
+/// in HealthDataUseCase.swift
+
+/// Health ๋ฐ์ดํฐ ์ฌ์ฉ ๊ถํ ์์ฒญ์ด ์ด๋ฃจ์ด์ก๋์ง ํ์ธํฉ๋๋ค.
+private func checkAuthorizationStatus() async throws -> Bool {
+ return try await withCheckedThrowingContinuation { continuation in
+ store.getRequestStatusForAuthorization(
+ toShare: Set(),
+ read: Set([.workoutType()])
+ ) { status, error in
+ if let _ = error {
+ continuation.resume(throwing: HealthError.unknownError)
+ }
+
+ switch status {
+ case .shouldRequest:
+ continuation.resume(returning: true)
+ default:
+ continuation.resume(returning: false)
+ }
+ }
+ }
+}
+```
+
+
+
+**2. ๊ถํ ์์ฒญ**
+
+๊ถํ ์์ฒญ์ ์งํํฉ๋๋ค.
+
+์์ฒญ์ ์งํํ๊ธฐ ์ Health ๋ฐ์ดํฐ๊ฐ ์๋ ๊ธฐ๊ธฐ์ธ์ง ์ฌ๋ถ๋ฅผ ํ๋ณํฉ๋๋ค.
+
+```swift
+/// in HealthDataUseCase.swift
+
+/// Health ๋ฐ์ดํฐ ์ฌ์ฉ ๊ถํ์ ์์ฒญํฉ๋๋ค.
+private func requestAuthorization() async throws {
+ if HKHealthStore.isHealthDataAvailable() {
+ try await store.requestAuthorization(
+ toShare: Set(),
+ read: Set([.workoutType()])
+ )
+ } else {
+ throw HealthError.notAvailableDevice
+ }
+}
+```
+
+**3. info.plist ์์ **
+
+์์ฒญ ์ ๋์ค๋ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค.
+
+ํด๋น ๊ณผ์ ์ ํ์์ ์ด๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ์ถ๊ฐ๊ฐ ํ์ํฉ๋๋ค.
+
+๋ง์ฝ ๋๋ฝ์ด ๋์ด์๊ฑฐ๋ ๊ถํ์ด ํ์ํ ์์ธํ ์ด์ ๋ฅผ ์์ ํด๋์ง ์์ ๊ฒฝ์ฐ ์ถ์ ์ฌ์ฌ ์ ๋ฆฌ์ ์ฌ์ ๊ฐ ๋ ์ ์์ต๋๋ค.
+
+์ค์ ๋ก ๊ฑด๊ฐ ๋ฐ์ดํฐ ์ชฝ์ ์๋์ง๋ง ์นด๋ฉ๋ผ ์ฌ์ฉ ๊ด๋ จ ๋ฉ์์ง์ `์ฌ์ง์ ์ฐ๊ธฐ ์ํด ์นด๋ฉ๋ผ ํ์ฉ์ด ํ์ํฉ๋๋ค.`๋ผ๊ณ ์ ์ด๋์ผ๋ ๊ตฌ์ฒด์ ์ด์ง ์๋ค๊ณ ๋ฆฌ์ ์ ๋นํ์ต๋๋ค.
+
+๋ํ ์ฑ์ ์ฌ๋ฆฌ๋ ๊ณผ์ ์์ ์ ํฌ ์ฑ์ ์ฝ๊ธฐ ๋ถ๋ถ๋ง ์ฌ์ฉํ๊ธฐ์ ํด๋น info ๋ง ์
๋ฐ์ดํธ ํ์ง๋ง ์ฐ๊ธฐ ๋ถ๋ถ์ด ๋๋ฝ๋์ด ์๋ค๊ณ ์
๋ก๋์ ์คํจํ์ต๋๋ค.
+
+๊ฒฐ๊ณผ์ ์ผ๋ก info์ Privacy์ ๊ด๋ จ๋ ํด๋น ๋ด์ฉ์ ์ถ๊ฐํ ๋ ๊ฐ๋ฅํ ๊ตฌ์ฒด์ ์ธ ์์ฑ์ด ํ์ํ๊ณ Health์ ๊ฒฝ์ฐ Update, Share ๋ ๋ค ์์ฑํ ํ์๊ฐ ์์ต๋๋ค.
+
+
+
+
+
+
+
+
+**2๏ธโฃ ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ**
+
+`HKSampleQuery`๋ฅผ ํตํด ์ผ๋ฐ์ ์ธ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ ์ฟผ๋ฆฌ๋ฌธ์ ๋ง๋ค ์ ์์ต๋๋ค.
+
+ํด๋น ์ฟผ๋ฆฌ์ ํ๋ผ๋ฏธํฐ๋ก ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ํํฐ๋ง ํ ์ ์์ต๋๋ค.
+
+Run Mile ์์๋ ์ด๋ ๋ฐ์ดํฐ๋ง ํ์ํ๋ฏ๋ก workoutType์ผ๋ก ์ ํํ๊ณ ๋ ์ง ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ์ ์งํํ์ต๋๋ค.
+
+์ฟผ๋ฆฌ ์ฌ์ฉ์ ์ ์ํ ์ ์ ๋ฐํ ํ์
์ผ๋ก ๋์ค๋ `HKSample`์ ๋ค์ํ ๊ฑด๊ฐ ๋ฐ์ดํฐ๋ค์ ์ถ์ํ ํ์
์ด๊ธฐ ๋๋ฌธ์ ๋ณํํ์ง ์์ผ๋ฉด ์ ๋ณด ์ ๊ทผ์ด ์ ํ ๋ฉ๋๋ค.(์๊ฐ ๊ด๋ จ ์ ๋ณด๋ง ์ ๊ณต)
+
+๋๋ฌธ์ ์ ๋๋ก ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํน์ ํ์
์ ๋ง์ถ์ด ํ์
์บ์คํ
์ ๊ผญ ํด์ผ ํฉ๋๋ค!(ํด๋น ํ๋ก์ ํธ์์๋ ์ด๋ ๋ฐ์ดํฐ๋ฅผ ๋ค๊ณ ์ค๋ฏ๋ก ๊ฑฐ๊ธฐ์ ๋ง๋ `HKWorkout` ํ์
์บ์คํ
์งํ)
+
+```swift
+/// in WorkoutDataRepositoryImpl.swift
+
+private let store = HKHealthStore()
+
+public func fetchWorkoutData() async throws -> [RunningData] {
+ let predicate = HKQuery.predicateForWorkouts(with: .running)
+ let descriptor = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]
+
+ let result: [HKWorkout] = try await store.fetchData(
+ sampleType: .workoutType(),
+ predicate: predicate,
+ limit: HKObjectQueryNoLimit,
+ sortDescriptors: descriptor
+ )
+
+ return convertToRunningData(result)
+}
+```
+
+๊ตฌํ๋ถ์์ ๊ฐ๋จํ๊ณ ๋ฒ์ฉ์ฑ ์๋ ์ฌ์ฉ์ ์ํด ์ ๋ค๋ฆญ ๋ฉ์๋ ์ฌ์ฉ
+```swift
+// in HealthKit+.swift
+
+public func fetchData(
+ sampleType: HKSampleType,
+ predicate: NSPredicate? = nil,
+ limit: Int,
+ sortDescriptors: [NSSortDescriptor]? = nil
+) async throws -> [T] {
+ let predicate = HKQuery.predicateForWorkouts(with: .running)
+
+ let data = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[HKSample], any Error>) in
+ let query = HKSampleQuery(
+ sampleType: sampleType,
+ predicate: predicate,
+ limit: limit,
+ sortDescriptors: sortDescriptors) { query, samples, error in
+ if let _ = error {
+ continuation.resume(with: .failure(HealthError.failedToLoadWorkoutData))
+ }
+
+ guard let samples = samples else {
+ continuation.resume(with: .failure(HealthError.failedToLoadWorkoutData))
+ return
+ }
+
+ continuation.resume(with: .success(samples))
+ }
+ self.execute(query)
+ }
+
+ guard let result = data as? [T] else { throw HealthError.failedToLoadWorkoutData }
+
+ return result
+}
+```
+
+
+
+**3๏ธโฃ ๋ฐฑ๊ทธ๋ผ์ด๋ ์
๋ฐ์ดํธ ์ ์ฉ**
+
+๋ํ์ ์ธ ์ด๋ ๊ธฐ๋ก ์ฑ `Strava`์ ๊ฒฝ์ฐ ์๋ก์ด ์ด๋ ๋ฐ์ดํฐ๊ฐ ์๊ธฐ๋ฉด ์ฑ์ผ๋ก ์๋ ์
๋ฐ์ดํธ๋ฅผ ์์ผ์ค๋๋ค.
+
+์ด ๊ธฐ๋ฅ์ ๋ํด ๊ถ๊ธ์ฆ๊ณผ ํธ๊ธฐ์ฌ์ด ์๊ฒจ ๊ณต๋ถ ํ ํ๋ก์ ํธ์ ๋์
ํ์ต๋๋ค.
+
+`HKHealthStore`์ `enableBackgroundDelivery()`๋ฉ์๋๋ฅผ ํตํด ํน์ ํ์
์ ๊ฑด๊ฐ ๋ฐ์ดํฐ๊ฐ ์
๋ฐ์ดํธ ๋์์ ๋๋ฅผ ํธ๋ฆฌ๊ฑฐ๋ก ์ฑ์ ๊นจ์ Background Task ๋ฅผ ์งํ์ํฌ ์ ์์ต๋๋ค.
+
+์ ์ํ ์ ์ ํด๋น ๊ธฐ๋ฅ์ ํน์ ๋ฐ์ดํฐ์ ์
๋ฐ์ดํธ ์ฌ๋ถ๋ง ์๋ ค์ฃผ๊ธฐ ๋๋ฌธ์ ์
๋ฐ์ดํธ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Background Task ์์์ ์๋ก์ด Sample Query๋ก ๋ฐ์ดํฐ๋ฅผ fetchํด์ผ ํฉ๋๋ค.
+
+๋ํ ์ฑ์ด ์์ ์ข
๋ฃ(suspended)๋๋ฉด background ๋ฑ๋ก๋ ๊ฐ์ด ์ข
๋ฃ๋๊ธฐ ๋๋ฌธ์ ์ด๋์ด ์ธ์ ์งํ๋ ์ง ์ ์ ์๋ ํน์ฑ ์ ํญ์ ํธ๋ฆฌ๊ฑฐ๊ฐ ์คํ๋ ์ ์๋๋ก ์ฑ ์คํ ์(AppDelegate)์ ๋ฑ๋ก๋๋๋ก ๊ตฌํํ์ต๋๋ค.
+
+Run Mile์์ ํด๋น ๊ธฐ๋ฅ์ผ๋ก ๊ตฌํํ๊ณ ์ ํ feature๋
+
+์๋ก์ด ์ด๋(๋ฌ๋)์ด ์ถ๊ฐ๋์์ ๋
+* ์๋ ๋ฑ๋ก ๊ธฐ๋ฅ์ด ์ผ์ ธ ์์ ๊ฒฝ์ฐ -> ์ ๋ฐ์ ์ด๋ ์ถ๊ฐ ํ, ์ถ๊ฐ ์๋ฃ noti ์์ฑ
+* ์๋ ๋ฑ๋ก ๊ธฐ๋ฅ x -> ์ด๋ ์๋ฃ noti ์์ฑ(์ฑ์ผ๋ก ์ ๋ํ๊ธฐ ์ํจ)
+
+์
๋๋ค.
+
+```swift
+/// in AppDelegate.swift
+
+public static func setHealthBackgroundTask() async {
+ let store = HKHealthStore()
+
+ do {
+ try await store.enableBackgroundDelivery(for: .workoutType(), frequency: .immediate)
+ let query = HKObserverQuery(
+ sampleType: .workoutType(),
+ predicate: nil
+ ) { query, completionHandler, error in
+ if let error = error {
+ print(error)
+ return
+ }
+
+ let sort = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
+ let sampleQuery = HKSampleQuery(queryDescriptors: [.init(sampleType: .workoutType(), predicate: nil)], limit: 1, sortDescriptors: [sort]) { _, samples, error in
+
+ ...
+
+ guard let workout = samples?.first as? HKWorkout else {
+ return
+ }
+
+ guard case .running = workout.workoutActivityType else {
+ return
+ }
+
+ let workoutId = workout.uuid.uuidString
+ let currentId = UserDefaults.standard.recentWorkoutID
+
+ if !UserDefaults.standard.isFirstLaunch {
+ UserDefaults.standard.recentWorkoutID = workoutId
+ } else {
+ if workoutId != currentId {
+ let distance = workout.getKilometerDistance()
+ if !UserDefaults.standard.selectedShoesID.isEmpty {
+
+ UNUserNotificationCenter.requestNotification(
+ title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
+ body: distance == nil
+ ? "์ ๋ฐ์ ์๋ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค!"
+ : String(format: "์ ๋ฐ์ ์๋ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค. ๋ฌ๋ ํ ์คํธ๋ ์นญ ๊ผญ ์์ง ๋ง์ธ์!", distance!)
+ )
+
+ autoRegisterShoes(workout: workout)
+ } else {
+ UNUserNotificationCenter.requestNotification(
+ title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
+ body: distance == nil
+ ? "์ ๋ฐ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฑ๋กํ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค. ๋ฑ๋กํ๋ฌ ๊ฐ๋ณผ๊น์?"
+ : String(format: "%.2fkm, ์์ง ๋ง๊ณ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฑ๋กํ๋ฌ ์ค์ธ์!", distance!)
+ )
+ }
+ UserDefaults.standard.recentWorkoutID = workoutId
+ }
+ }
+ }
+
+ store.execute(sampleQuery)
+
+ completionHandler()
+ }
+
+ store.execute(query)
+ } catch {
+ print(error)
+ }
+}
+```
+
From 351232d8274eddc1ea7266725ee8f0a28c870b3e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Mon, 16 Jun 2025 18:11:53 +0900
Subject: [PATCH 02/54] =?UTF-8?q?[Fix]=20README=20=EB=82=B4=EC=9A=A9=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d4af0da..3dae04c 100644
--- a/README.md
+++ b/README.md
@@ -32,9 +32,66 @@
## ์ด๋ฒ ํ๋ก์ ํธ์์ ๋ด๊ฐ ๋ฐฐ์ด ๊ฒ
-### ํด๋ฆฐ ์ํคํ
์ณ๋ฅผ ๋ด ์
๋ง์ ๋ง๊ฒ ์ฌ์ฉ
+### ํด๋ฆฐ ์ํคํ
์ณ ์ ์ฉ
+๊ธฐ์กด์๋ State์ Binding์ ์ฌ์ฉํด SwiftUI์์ ์ ๊ณตํ๋ ํ๋กํผํฐ ๋ํผ๋ฅผ ํ์ฉํด ์ํ๊ด๋ฆฌ๋ฅผ ํ์ต๋๋ค.
+๋ํ `@FetchRequest`๋ฅผ ํ์ฉํด View์์ ๋ฐ๋ก ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ ์ฌ์ฉํ์ต๋๋ค.
+
+ํ์ง๋ง ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ทฐ๋ค์ด ์ฌ๋ฌ๊ฐ ์๊ธฐ๋ฉด์ ์ค๋ณต๋ ์ฝ๋๋ค์ด ๋์ด๋ฌ๊ณ , ๋ทฐ ํ๋์ ๋ค์ํ ๋ชฉ์ ์ ์ฝ๋๋ค์ด ์๊ธฐ๋ค๋ณด๋ ๋ทฐ ํ๋๊ฐ ๋๋ฌด ์ปค์ ธ๋ฒ๋ฆฌ๋ ์ํฉ์ด ์๊ฒผ์ต๋๋ค.
+
+์ด๋ฌ๋ค๋ณด๋ ์ด๋ ํ ๋ถ๋ถ์์ ๋ฌธ์ ๊ฐ ์ผ์ด๋๋ฉด ๊ทธ ์ฝ๋๋ฅผ ์ฐพ๊ธฐ ์ํด ๋ํดํ View์ ์ฝ๋ ์์์ ํ์ํ ๋ถ๋ถ์ ์ฐพ๊ธฐ๊ฐ ์ด๋ ค์์ก๊ณ ์ ์ง๋ณด์์ฑ์ด ๋ฎ์์ก์ต๋๋ค.
+
+๊ฐ์์ ๊ด์ฌ์ฌ์ ๋ง๊ฒ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํด์ผ ํ ํ์์ฑ์ ๋๋ผ๊ณ ์ด๋ฒ ํ๋ก์ ํธ์์ ์ด๋ฐ ์ํคํ
์ณ์ ๋ํ๊ฒฉ์ด๋ผ๊ณ ํ ์ ์๋ **ํด๋ฆฐ ์ํคํ
์ณ**๋ฅผ ์ ์ฉํ์ต๋๋ค.
+
+ํด๋ฆฐ ์ํคํ
์ณ์์ ํด๋น ํ๋ก์ ํธ์ ์ ์ผ ์ค์ํ๋ค๊ณ ์๊ฐํ ๋ถ๋ถ์ ์๋์ ๊ฐ์ต๋๋ค.
+* ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ถ๋ถ(Repository)์ ์ฌ์ฉํ๋ ๋ถ๋ถ(UseCase)์ ๋ถ๋ฆฌ
+* ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ UseCase๋ฅผ ํตํด ์ฃผ์
+* View์ ๋ชฉ์ ์ฑ(Presentation, ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ฒ๋ง ๋ฃ๊ธฐ)์ ํ์คํ ํ๊ธฐ ์ํด View์ ViewModel๋ก ๋ถ๋ฆฌ
+
+
+
+**๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ถ๋ถ(Repository)์ ์ฌ์ฉํ๋ ๋ถ๋ถ(UseCase)์ ๋ถ๋ฆฌ**
+
+๊ด์ฌ์ฌ ๋ถ๋ฆฌ๋ฅผ ์ํด ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ถ๋ถ์ **Repository ํจํด**์ ์ฌ์ฉํด ๋ถ๋ฆฌํ์ต๋๋ค.
+
+์ถํ ํ
์คํธ ์ฉ์ด์ฑ์ ์ฆ์ง์ํค๊ณ UseCase๊ฐ Repository๋ฅผ ์์กดํ๋ ํํ๋ฅผ ํผํ๊ธฐ ์ํด ํ๋กํ ์ฝ์ ํ์ฉํ์ต๋๋ค.
+
+๋ํ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ ๋ง๋ค๊ธฐ ์ํด Swift Concurrency์์ ์ง์ํด๋ `actor`๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
+
+์ด๋ฒ ํ๋ก์ ํธ์์ ์ฌ์ฉ๋ Repository๋ ์ด 2๊ฐ ์
๋๋ค.
+* WorkoutDataRepository(HealthKit์ผ๋ก ์ด๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ ํฌ์งํ ๋ฆฌ)
+* ShoesDataRepository(Realm์ผ๋ก ๊ด๋ฆฌ๋๋ ์ ๋ฐ ๋ฐ์ดํฐ๋ฅผ CRUDํ๋ ๋ ํฌ์งํ ๋ฆฌ)
+
+
+
+**ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ UseCase๋ฅผ ํตํด ์ฃผ์
**
+
+์์ ๋น์ทํ๊ฒ ์ถํ ์ ๋ ํ
์คํธ๋ฅผ ์ถ๊ฐํ ๊ฒฝ์ฐ ํ๋ก์ ํธ์ ๋น์ฆ๋์ค ๋ก์ง์ ํ
์คํธ ์ฉ์์ฑ์ ์ฆ์ง์ํค๊ธฐ ์ํด UseCase๋ก ๋น์ฆ๋์ค ๋ก์ง ๊ตฌํํ์ต๋๋ค.
+
+๋น์ทํ ๊ธฐ๋ฅ์ ํ์๋ก ํ๋ ์ฌ๋ฌ๊ฐ์ ๋ทฐ์์ ํ๋์ UseCase๋ฅผ ์ฌํ์ฉํ ์ ์์ด ์์ฐ์ฑ ํฅ์๊ณผ ์ผ๊ด์ฑ์ ์ ์งํ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
+
+ํ์ง๋ง ์์ง ๊ฐ์ธ์ ์ธ ์๊ฐ์ ๋ณผ๋ฅจ์ด ํฌ์ง ์์ ํ๋ก์ ํธ๋ผ์ UseCase๋ผ๋ ํ๋จ๊ณ ๋ ๊ฑฐ์ณ ๋ก์ง์ ์คํ ์ํค๋ ๊ฒ์ด ์คํ๋ ค ๋ถํ์ํ ๊ณผ์ ์ด๋ผ๊ณ ๋๊ผ์ต๋๋ค.
+
+ํ๋ก์ ํธ์ ๊ท๋ชจ์ ๋ฐ๋ผ ๋ถ๋ถ์ ์ธ ์ ์ฉ์ด ํ์ํด ๋ณด์์ต๋๋ค.
+
+
+
+**View์ ๋ชฉ์ ์ฑ(Presentation, ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ฒ๋ง ๋ฃ๊ธฐ)์ ํ์คํ ํ๊ธฐ ์ํด View์ ViewModel๋ก ๋ถ๋ฆฌ**
+
+View๋ ๋ง **๊ทธ๋๋ก ๋ณด์ฌ์ง๋ ๊ฒ** ์ด๊ธฐ ๋๋ฌธ์ ๋ณด์ฌ์ง๋๋ ํ์ํ ํ๋กํผํฐ๋ฅผ ์ ์ธํ ์ํ๊ด๋ฆฌ ๋ณ์๋ค๊ณผ ๋ฉ์๋๋ฅผ ๋ชจ๋ ViewModel๋ก ๋ถ๋ฆฌํ์ต๋๋ค.
+
+๋ํ View์ action์ผ๋ก ์คํ๋๋ ๋ฉ์๋์ ๋ค์ด๋ฐ์ `000ButtonTapped`์ ๊ฐ์ด ์์ฑํ์ฌ View์์ ํน์ ์ก์
์ด ์ด๋ค ๊ธฐ๋ฅ์ ์ํํ๋์ง๋ฅผ ์ต๋ํ ์จ๊ฒผ์ต๋๋ค.
+
+์ด๋ฅผ ํตํด View์ ๋ชฉ์ ์ฑ์ธ **๋ณด์ฌ์ง๋ ๊ฒ**์ ์ต๋ํ ์งํฌ ์ ์์๋ค๊ณ ์๊ฐํฉ๋๋ค.
+
+ํ์ง๋ง ์์ ๋น์ทํ๊ฒ ๋ณผ๋ฅจ์ด ์์ ํ๋ก์ ํธ์์๋ ์์ ๋งํ๋ ์ด๋ฐ MVVM ํจํด์ด SwiftUI ํ๋ก์ ํธ๋ผ๋ฉด ํ์ํ ๊ฐ? ์๋ฌธ์ ์ด ๋ค์์ต๋๋ค. ์คํ๋ ค ์ค๋ฒ์์ง๋์ด๋ง ์ด๋ผ๋ ๋๋์ด ๋ค์์ฃ .
+
+SwiftUI๊ฐ ์ง์ํ๋ ์ํ๊ด๋ฆฌ ๊ธฐ๋ฅ๋ค์ ๋ณด๋ฉด MVVM ํจํด ๋ณด๋จ View ํ๋์์ ๋ชจ๋ ๊ฒ์ ํด๊ฒฐ ํ ์ ์๋๋ก ๊ตฌ์ฑ์ ํด ๋์๋๋ ์ด์ ๊ฐ ์์ ๊ฒ์ด๋ผ ์๊ฐํฉ๋๋ค.
+
+์ ๊ฐ ๋๋ ์ ์ ๋ ๋ค ์ฅ๋จ์ด ์๋ ๋งํผ ๊ฐ์์ ์ฌ์ฉ์ฑ์ ์ดํดํ๊ณ ์ํฉ์ ๋ง๊ฒ ๋์์ธ ํจํด์ ์ฑํํ ์ ์๋ ๋ฅ๋ ฅ์ ๊ธฐ๋ฅด๋ ๊ฒ์ด ์ข์๋ณด์์ต๋๋ค.
+
+
### HealthKit ํ์ฉ
์ด๋ฒ ํ๋ก์ ํธ์ ํต์ฌ์ ๊ธฐ๊ธฐ ๋ด์ ์๋ ์ด๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ ์
๋๋ค.
From 7c7e59cc0bfbfae1d62fe2106507a1ad7265b261 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Mon, 16 Jun 2025 21:47:44 +0900
Subject: [PATCH 03/54] =?UTF-8?q?[#33]=20README=20=EB=A0=88=ED=8D=BC?=
=?UTF-8?q?=EB=9F=B0=EC=8A=A4=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile.xcodeproj/project.pbxproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index 0d85d63..2b1714c 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -11,7 +11,6 @@
2C079FBF2DB2505700B1EE0D /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2C079FBE2DB2505700B1EE0D /* RealmSwift */; };
2C079FC02DB2507700B1EE0D /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 2C079FBE2DB2505700B1EE0D /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
2CE46E212DAE78D7008C6FBC /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 2CE46E202DAE78D7008C6FBC /* .gitignore */; };
- 2CF57F5C2DF9483100F59463 /* PrivacyPolicy.md in Resources */ = {isa = PBXBuildFile; fileRef = 2CF57F5B2DF9483100F59463 /* PrivacyPolicy.md */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -47,6 +46,7 @@
/* Begin PBXFileReference section */
2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Run_MileApp.swift; sourceTree = ""; };
+ 2C7633922E0049A30005D988 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
2CE46DF32DAE7780008C6FBC /* Run Mile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Run Mile.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2CE46E002DAE7782008C6FBC /* Run MileTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
2CE46E0A2DAE7782008C6FBC /* Run MileUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -101,6 +101,7 @@
2CE46DEA2DAE7780008C6FBC = {
isa = PBXGroup;
children = (
+ 2C7633922E0049A30005D988 /* README.md */,
2CF57F5B2DF9483100F59463 /* PrivacyPolicy.md */,
2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */,
2CE46E202DAE78D7008C6FBC /* .gitignore */,
@@ -246,7 +247,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 2CF57F5C2DF9483100F59463 /* PrivacyPolicy.md in Resources */,
2CE46E212DAE78D7008C6FBC /* .gitignore in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
From 07a473db058d7133090490c03e87d688a576b871 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Mon, 16 Jun 2025 22:21:13 +0900
Subject: [PATCH 04/54] [#33] fastlane init
---
Gemfile | 3 +
Gemfile.lock | 227 +++++++++++++++++++++++++++++
Run Mile.xcodeproj/project.pbxproj | 10 ++
fastlane/Appfile | 8 +
fastlane/Fastfile | 25 ++++
5 files changed, 273 insertions(+)
create mode 100644 Gemfile
create mode 100644 Gemfile.lock
create mode 100644 fastlane/Appfile
create mode 100644 fastlane/Fastfile
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..7a118b4
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "fastlane"
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..bb63134
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,227 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.7)
+ base64
+ nkf
+ rexml
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ artifactory (3.0.17)
+ atomos (0.1.3)
+ aws-eventstream (1.4.0)
+ aws-partitions (1.1116.0)
+ aws-sdk-core (3.225.2)
+ aws-eventstream (~> 1, >= 1.3.0)
+ aws-partitions (~> 1, >= 1.992.0)
+ aws-sigv4 (~> 1.9)
+ base64
+ jmespath (~> 1, >= 1.6.1)
+ logger
+ aws-sdk-kms (1.105.0)
+ aws-sdk-core (~> 3, >= 3.225.0)
+ aws-sigv4 (~> 1.5)
+ aws-sdk-s3 (1.189.1)
+ aws-sdk-core (~> 3, >= 3.225.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.5)
+ aws-sigv4 (1.12.1)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
+ base64 (0.3.0)
+ claide (1.1.0)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander (4.6.0)
+ highline (~> 2.0.0)
+ declarative (0.0.20)
+ digest-crc (0.7.0)
+ rake (>= 12.0.0, < 14.0.0)
+ domain_name (0.6.20240107)
+ dotenv (2.8.1)
+ emoji_regex (3.2.3)
+ excon (0.112.0)
+ faraday (1.10.4)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0)
+ faraday-multipart (~> 1.0)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.0)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ faraday-retry (~> 1.0)
+ ruby2_keywords (>= 0.0.4)
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
+ http-cookie (~> 1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-multipart (1.1.0)
+ multipart-post (~> 2.0)
+ faraday-net_http (1.0.2)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday-retry (1.0.3)
+ faraday_middleware (1.2.1)
+ faraday (~> 1.0)
+ fastimage (2.4.0)
+ fastlane (2.228.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.8, < 3.0.0)
+ artifactory (~> 3.0)
+ aws-sdk-s3 (~> 1.0)
+ babosa (>= 1.0.3, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored (~> 1.2)
+ commander (~> 4.6)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 4.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 1.0)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 1.0)
+ fastimage (>= 2.1.0, < 3.0.0)
+ fastlane-sirp (>= 1.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-apis-androidpublisher_v3 (~> 0.3)
+ google-apis-playcustomapp_v1 (~> 0.1)
+ google-cloud-env (>= 1.6.0, < 2.0.0)
+ google-cloud-storage (~> 1.31)
+ highline (~> 2.0)
+ http-cookie (~> 1.0.5)
+ json (< 3.0.0)
+ jwt (>= 2.1.0, < 3)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multipart-post (>= 2.0.0, < 3.0.0)
+ naturally (~> 2.2)
+ optparse (>= 0.1.1, < 1.0.0)
+ plist (>= 3.1.0, < 4.0.0)
+ rubyzip (>= 2.0.0, < 3.0.0)
+ security (= 0.1.5)
+ simctl (~> 1.6.3)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (~> 3)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.4.1)
+ xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
+ fastlane-sirp (1.0.0)
+ sysrandom (~> 1.0)
+ gh_inspector (1.1.3)
+ google-apis-androidpublisher_v3 (0.54.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-apis-core (0.11.3)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ google-apis-iamcredentials_v1 (0.17.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.13.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-apis-storage_v1 (0.31.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-cloud-core (1.8.0)
+ google-cloud-env (>= 1.0, < 3.a)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.6.0)
+ faraday (>= 0.17.3, < 3.0)
+ google-cloud-errors (1.5.0)
+ google-cloud-storage (1.47.0)
+ addressable (~> 2.8)
+ digest-crc (~> 0.4)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.31.0)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
+ mini_mime (~> 1.0)
+ googleauth (1.8.1)
+ faraday (>= 0.17.3, < 3.a)
+ jwt (>= 1.4, < 3.0)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (>= 0.16, < 2.a)
+ highline (2.0.3)
+ http-cookie (1.0.8)
+ domain_name (~> 0.5)
+ httpclient (2.9.0)
+ mutex_m
+ jmespath (1.6.2)
+ json (2.12.2)
+ jwt (2.10.1)
+ base64
+ logger (1.7.0)
+ mini_magick (4.13.2)
+ mini_mime (1.1.5)
+ multi_json (1.15.0)
+ multipart-post (2.4.1)
+ mutex_m (0.3.0)
+ nanaimo (0.4.0)
+ naturally (2.3.0)
+ nkf (0.2.0)
+ optparse (0.6.0)
+ os (1.1.4)
+ plist (3.7.2)
+ public_suffix (6.0.2)
+ rake (13.3.0)
+ representable (3.2.0)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rexml (3.4.1)
+ rouge (3.28.0)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.4.1)
+ security (0.1.5)
+ signet (0.20.0)
+ addressable (~> 2.8)
+ faraday (>= 0.17.5, < 3.a)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.10)
+ CFPropertyList
+ naturally
+ sysrandom (1.0.5)
+ terminal-notifier (2.0.0)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ trailblazer-option (0.1.2)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.2)
+ tty-spinner (0.9.3)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unicode-display_width (2.6.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.27.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.4.0)
+ rexml (>= 3.3.6, < 4.0)
+ xcpretty (0.4.1)
+ rouge (~> 3.28.0)
+ xcpretty-travis-formatter (1.0.1)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ arm64-darwin-24
+ ruby
+
+DEPENDENCIES
+ fastlane
+
+BUNDLED WITH
+ 2.6.9
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index 2b1714c..af30b97 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -47,6 +47,8 @@
/* Begin PBXFileReference section */
2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Run_MileApp.swift; sourceTree = ""; };
2C7633922E0049A30005D988 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
+ 2C7635B02E0051AD0005D988 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile; sourceTree = ""; };
+ 2C7635B12E0051AD0005D988 /* Gemfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile.lock; sourceTree = ""; };
2CE46DF32DAE7780008C6FBC /* Run Mile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Run Mile.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2CE46E002DAE7782008C6FBC /* Run MileTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
2CE46E0A2DAE7782008C6FBC /* Run MileUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -55,6 +57,11 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 2C7635AF2E0051AD0005D988 /* fastlane */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = fastlane;
+ sourceTree = "";
+ };
2CE46DF52DAE7780008C6FBC /* Run Mile */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = "Run Mile";
@@ -101,6 +108,9 @@
2CE46DEA2DAE7780008C6FBC = {
isa = PBXGroup;
children = (
+ 2C7635AF2E0051AD0005D988 /* fastlane */,
+ 2C7635B02E0051AD0005D988 /* Gemfile */,
+ 2C7635B12E0051AD0005D988 /* Gemfile.lock */,
2C7633922E0049A30005D988 /* README.md */,
2CF57F5B2DF9483100F59463 /* PrivacyPolicy.md */,
2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */,
diff --git a/fastlane/Appfile b/fastlane/Appfile
new file mode 100644
index 0000000..4ace4da
--- /dev/null
+++ b/fastlane/Appfile
@@ -0,0 +1,8 @@
+app_identifier("com.mooni.Run-Mile") # The bundle identifier of your app
+apple_id("dlsqja567@naver.com") # Your Apple Developer Portal username
+
+itc_team_id("126391461") # App Store Connect Team ID
+team_id("VF8R3A969C") # Developer Portal Team ID
+
+# For more information about the Appfile, see:
+# https://docs.fastlane.tools/advanced/#appfile
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
new file mode 100644
index 0000000..3e1cf10
--- /dev/null
+++ b/fastlane/Fastfile
@@ -0,0 +1,25 @@
+# This file contains the fastlane.tools configuration
+# You can find the documentation at https://docs.fastlane.tools
+#
+# For a list of all available actions, check out
+#
+# https://docs.fastlane.tools/actions
+#
+# For a list of all available plugins, check out
+#
+# https://docs.fastlane.tools/plugins/available-plugins
+#
+
+# Uncomment the line if you want fastlane to automatically update itself
+# update_fastlane
+
+default_platform(:ios)
+
+platform :ios do
+ desc "Push a new beta build to TestFlight"
+ lane :beta do
+ increment_build_number(xcodeproj: "Run Mile.xcodeproj")
+ build_app(scheme: "Run Mile")
+ upload_to_testflight
+ end
+end
From 3ca3fd68d3488724ad4c759d2da8ca59e259a8a4 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 13:04:28 +0900
Subject: [PATCH 05/54] =?UTF-8?q?[#33]=20Fastlane=20=EC=84=A4=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile.xcodeproj/project.pbxproj | 38 ++++++++++-------
fastlane/Fastfile | 65 +++++++++++++++++++++++++++++-
fastlane/Matchfile | 13 ++++++
3 files changed, 100 insertions(+), 16 deletions(-)
create mode 100644 fastlane/Matchfile
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index af30b97..872e465 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -443,9 +443,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 202506113;
- DEVELOPMENT_TEAM = VF8R3A969C;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 202506182;
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Run-Mile-Info.plist";
@@ -457,7 +460,7 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
- INFOPLIST_KEY_UIStatusBarStyle = "";
+ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UIUserInterfaceStyle = Dark;
@@ -466,9 +469,11 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.0;
+ MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -485,9 +490,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 202506113;
- DEVELOPMENT_TEAM = VF8R3A969C;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 202506182;
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Run-Mile-Info.plist";
@@ -499,7 +507,7 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
- INFOPLIST_KEY_UIStatusBarStyle = "";
+ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UIUserInterfaceStyle = Dark;
@@ -508,9 +516,11 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.0;
+ MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -526,7 +536,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.4;
@@ -545,7 +555,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.4;
@@ -563,7 +573,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -580,7 +590,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 3e1cf10..c4fdd8c 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -18,8 +18,69 @@ default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
- increment_build_number(xcodeproj: "Run Mile.xcodeproj")
- build_app(scheme: "Run Mile")
+
+ today = Time.now.strftime("%Y%m%d")
+
+ current_build_number = "#{latest_testflight_build_number}"
+
+ if current_build_number.length == 9
+ current_date = current_build_number[0,8] # ์ 8์๋ฆฌ๊ฐ ๋ ์ง
+ current_count = current_build_number[8].to_i # ๋ง์ง๋ง 1์๋ฆฌ๊ฐ ์ซ์
+ else
+ # ๋น๋ ๋๋ฒ๊ฐ ์๊ฑฐ๋ ํ์์ด ๋ค๋ฅด๋ฉด ์ด๊ธฐํ
+ current_date = ""
+ current_count = 0
+ end
+
+ if current_date == today
+ next_count = current_count + 1
+ else
+ next_count = 1
+ end
+
+ new_build_number = "#{today}#{next_count}"
+
+ increment_build_number(
+ xcodeproj: "Run Mile.xcodeproj",
+ build_number: new_build_number
+ )
+
+ # notification(
+ # subtitle: "Build Number: #{new_build_number}",
+ # message: "ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ ์ค๋น"
+ # )
+
+ app_store_connect_api_key(
+ key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}",
+ issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}",
+ key_content: "#{ENV[APP_STORE_CONNECT_KEY]}",
+ is_key_content_base64: true
+ )
+
+ match(type: "appstore")
+
+ build_app(
+ scheme: "Run Mile",
+ xcodebuild_formatter: "xcpretty",
+ export_options: {
+ provisioningProfiles: {
+ "com.mooni.Run-Mile" => "com.mooni.Run-Mile AppStore"
+ }
+ }
+ )
+
upload_to_testflight
+
+ # notification(
+ # subtitle: "Testflight ๋ฐฐํฌ ์ฑ๊ณต!",
+ # message: "Build Number : #{new_build_number}, ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ์ ์ฑ๊ณตํ์ต๋๋ค!"
+ # )
end
+
+ # error do |lane, exception, options|
+ # notification(
+ # subtitle: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.",
+ # message: "#{exception}"
+ # )
+ # end
end
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
new file mode 100644
index 0000000..4b50f33
--- /dev/null
+++ b/fastlane/Matchfile
@@ -0,0 +1,13 @@
+git_url("https://github.com/mooninbeom/fastlane-match")
+
+storage_mode("git")
+
+type("appstore") # The default type, can be: appstore, adhoc, enterprise or development
+
+app_identifier("com.mooni.Run-Mile")
+username("dlsqja567@naver.com") # Your Apple Developer Portal username
+
+# For all available options run `fastlane match --help`
+# Remove the # in the beginning of the line to enable the other options
+
+# The docs are available on https://docs.fastlane.tools/actions/match
From 28942033dab9c74be14d7b1fc344d5c0e712955e Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 13:16:49 +0900
Subject: [PATCH 06/54] =?UTF-8?q?[#33]=20=EC=9D=B8=EC=A6=9D=EC=84=9C=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95,=20README=20fastlane=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 3 ++-
Run Mile.xcodeproj/project.pbxproj | 8 ++++----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 3dae04c..f58cffc 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
|์ํ|์ฑ์คํ ์ด ๋ฐฐํฌ ์๋ฃ ๋ฐ ์ ์ง๋ณด์ ์งํ ์ค(v1.0.0)|
|:--|:--|
-|๊ธฐ์ ์คํ|SwiftUI, HealthKit, Realm, UserNotifications, Vision(์ด๋ฏธ์ง ๋ฐฐ๊ฒฝ ์ ๊ฑฐ)|
+|๊ธฐ์ ์คํ|SwiftUI, HealthKit, Realm, Fastlane, UserNotifications, Vision(์ด๋ฏธ์ง ๋ฐฐ๊ฒฝ ์ ๊ฑฐ)|
|์ฑ์คํ ์ด|[Run Mile](https://apps.apple.com/kr/app/run-mile/id6747099791)|
|์ด๋ฉ์ผ ๋ฌธ์|dlsqja567@naver.com|
@@ -25,6 +25,7 @@
|**0. ์ฌ์ด๋ ํ๋ก์ ํธ Run Mile ์ฑ ๊ฐ๋ฐ๊ธฐ**|https://velog.io/@mooninbeom/0.-์ฌ์ด๋-ํ๋ก์ ํธ-Run-Mile-์ฑ-๊ฐ๋ฐ๊ธฐ|
|**1. HealthKit ๋ฐ์ดํฐ ์ฌ์ฉ(with Continuation)**|https://velog.io/@mooninbeom/1.-HealthKit-๋ฐ์ดํฐ-์ฌ์ฉwith-Continuation|
|**2. ๋ฐฑ๊ทธ๋ผ์ด๋์์ HealthKit ํ์ฉํ๊ธฐ**|https://velog.io/@mooninbeom/2.-๋ฐฑ๊ทธ๋ผ์ด๋์์-HealthKit-ํ์ฉํ๊ธฐ-o2p1gg9l|
+|**3. Fastlane์ผ๋ก Testflight ๋ฐฐํฌ ์๋ํ**|https://velog.io/@mooninbeom/3.-Fastlane์ผ๋ก-Testflight-๋ฐฐํฌ-์๋ํ|
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index 872e465..ef15089 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -444,7 +444,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = "";
@@ -473,7 +473,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -491,7 +491,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = "";
@@ -520,7 +520,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
From 61ed2116a91678d8801adfddc22aa3701f83278e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Wed, 18 Jun 2025 14:06:58 +0900
Subject: [PATCH 07/54] =?UTF-8?q?[#33]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?=
=?UTF-8?q?=EC=9A=A9=20Workflow=20=EC=83=9D=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 50 ++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 .github/workflows/testflight.yml
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
new file mode 100644
index 0000000..5892970
--- /dev/null
+++ b/.github/workflows/testflight.yml
@@ -0,0 +1,50 @@
+name: ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ ๐
+
+on:
+ push:
+ branches:
+ - main
+ # ์ก์
ํ
์คํธ์ฉ
+ pull_request:
+ branches:
+ - develop
+
+jobs:
+ build-upload-testflight:
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up RUBY
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '2.7'
+
+ - name: Install Bundler
+ run: gem install bundler
+
+ - name: Install Fastlane
+ run: brew install fastlane
+
+ - name: Check Fastlane
+ run: fastlane --version
+
+ - name: Install Dependencies
+ run: bundle install
+
+ - name: Upload to Testflight โ๏ธ
+ env:
+ APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY}}
+ APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
+ APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ run: bundle exec fastlane beta
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: ipa-and-dsym
+ path: |
+ ./Run\ Mile.ipa
+ ./Run\ Mile.app.dSYM.zip
From c4aacb083e9e770d4a987b548cdf791e25b55b81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Wed, 18 Jun 2025 14:09:36 +0900
Subject: [PATCH 08/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=201?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๋ฃจ๋น ๋ฒ์ ์ฌ์กฐ์ (2.7 -> 3.1)
---
.github/workflows/testflight.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index 5892970..a3657a9 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -19,7 +19,7 @@ jobs:
- name: Set up RUBY
uses: ruby/setup-ruby@v1
with:
- ruby-version: '2.7'
+ ruby-version: '3.1'
- name: Install Bundler
run: gem install bundler
From 3e39cad1bd8bbc7f21bec6ece273e9451bdc0d31 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 14:14:26 +0900
Subject: [PATCH 09/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(App?=
=?UTF-8?q?store=20connect=20api=20key=20=EC=9C=84=EC=B9=98=20=EC=A1=B0?=
=?UTF-8?q?=EC=A0=95)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index c4fdd8c..cca80eb 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -19,6 +19,13 @@ platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
+ app_store_connect_api_key(
+ key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}",
+ issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}",
+ key_content: "#{ENV[APP_STORE_CONNECT_KEY]}",
+ is_key_content_base64: true
+ )
+
today = Time.now.strftime("%Y%m%d")
current_build_number = "#{latest_testflight_build_number}"
@@ -50,13 +57,6 @@ platform :ios do
# message: "ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ ์ค๋น"
# )
- app_store_connect_api_key(
- key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}",
- issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}",
- key_content: "#{ENV[APP_STORE_CONNECT_KEY]}",
- is_key_content_base64: true
- )
-
match(type: "appstore")
build_app(
From 8c5c65b6ac817389371c5bdc3e7fd0de5be9a8ca Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 14:20:49 +0900
Subject: [PATCH 10/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(?=
=?UTF-8?q?=ED=99=98=EA=B2=BD=20=EB=B3=80=EC=88=98=20=EC=8C=8D=EB=94=B0?=
=?UTF-8?q?=EC=98=B4=ED=91=9C=20=EC=B6=94=EA=B0=80)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index cca80eb..2e701c6 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -20,9 +20,9 @@ platform :ios do
lane :beta do
app_store_connect_api_key(
- key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}",
- issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}",
- key_content: "#{ENV[APP_STORE_CONNECT_KEY]}",
+ key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}",
+ issuer_id: "#{ENV["APP_STORE_CONNECT_ISSUER_ID"]}",
+ key_content: "#{ENV["APP_STORE_CONNECT_KEY"]}",
is_key_content_base64: true
)
From 75f1d22d0c2b613b6cbeb3ae9e87dedd9b39d1b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Wed, 18 Jun 2025 14:56:05 +0900
Subject: [PATCH 11/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=202?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
ssh ์ฐ๊ฒฐ ์ถ๊ฐ
---
.github/workflows/testflight.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index a3657a9..5429083 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -16,6 +16,12 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Set up SSH
+ uses: shimataro/ssh-key-action@v2
+ with:
+ key: ${{ secrets.SSH_PRIVATE_KEY }}
+ known_hosts: ${{ secrets.KNOWN_HOSTS }}
+
- name: Set up RUBY
uses: ruby/setup-ruby@v1
with:
From ab3d922969756ab51037c9d84f05929c619e635f Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 15:08:58 +0900
Subject: [PATCH 12/54] =?UTF-8?q?[#33]=20Matchfile=20=EC=88=98=EC=A0=95(gi?=
=?UTF-8?q?thub=20url=20HTTPS=20->=20SSH)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Matchfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
index 4b50f33..3da1c80 100644
--- a/fastlane/Matchfile
+++ b/fastlane/Matchfile
@@ -1,4 +1,4 @@
-git_url("https://github.com/mooninbeom/fastlane-match")
+git_url("git@github.com:mooninbeom/fastlane-match.git")
storage_mode("git")
From 116632d1efbe178d1c1484a46ea6e4380a182e5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Wed, 18 Jun 2025 15:20:38 +0900
Subject: [PATCH 13/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=203?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index 5429083..0e3d6e5 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -11,7 +11,7 @@ on:
jobs:
build-upload-testflight:
- runs-on: macos-latest
+ runs-on: macos-15
steps:
- uses: actions/checkout@v4
From 8ef3adf3db3b235a5a07d859fc7139226e04f267 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Wed, 18 Jun 2025 15:27:28 +0900
Subject: [PATCH 14/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=204?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index 0e3d6e5..c559960 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -26,6 +26,14 @@ jobs:
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.1'
+
+ - name: Set up Xcode
+ uses: maxim-lobanov/setup-xcode@v1.6.0
+ with:
+ xcode-version: '16.2'
+
+ - name: Check Xcode version
+ run: xcodebuild -version
- name: Install Bundler
run: gem install bundler
From 4f2a99d32f902c32314c7925c8652e25609dca30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Wed, 18 Jun 2025 15:38:10 +0900
Subject: [PATCH 15/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=204?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index c559960..302512e 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -27,10 +27,13 @@ jobs:
with:
ruby-version: '3.1'
- - name: Set up Xcode
- uses: maxim-lobanov/setup-xcode@v1.6.0
- with:
- xcode-version: '16.2'
+ # - name: Set up Xcode
+ # uses: maxim-lobanov/setup-xcode@v1.6.0
+ # with:
+ # xcode-version: '16.2'
+
+ - name: Select Xcode version
+ run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
- name: Check Xcode version
run: xcodebuild -version
From d16b348c4073f853cb2b840ceeab1298271af6d9 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 17:21:57 +0900
Subject: [PATCH 16/54] =?UTF-8?q?[#33]=20=EC=9D=B8=EC=A6=9D=EC=84=9C=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile.xcodeproj/project.pbxproj | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index ef15089..acf749b 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -444,7 +444,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = "";
@@ -473,7 +473,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -491,7 +491,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506182;
DEVELOPMENT_TEAM = "";
@@ -520,7 +520,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
From 50bcb9383040f9d51eb55f962758f7796c568aa6 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 19:51:18 +0900
Subject: [PATCH 17/54] =?UTF-8?q?[#33]=20Fastfile=20Slack=20=EB=85=B8?=
=?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile/Helper/HealthKit+.swift | 2 +-
fastlane/Fastfile | 28 ++++++++++++++++++++++++++--
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift
index 227bc6b..3d423f3 100644
--- a/Run Mile/Helper/HealthKit+.swift
+++ b/Run Mile/Helper/HealthKit+.swift
@@ -8,7 +8,7 @@
import HealthKit
-extension HKHealthStore {
+extension HKHealthStore: Sendable {
public func fetchData(
sampleType: HKSampleType,
predicate: NSPredicate? = nil,
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 2e701c6..29c5cf1 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -57,8 +57,18 @@ platform :ios do
# message: "ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ ์ค๋น"
# )
+ slack(
+ message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค๋น ์์",
+ slack_url: "https://hooks.slack.com/services/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"
+ )
+
match(type: "appstore")
+ slack(
+ message: "Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น ์๋ฃ",
+ slack_url: "https://hooks.slack.com/services/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"
+ )
+
build_app(
scheme: "Run Mile",
xcodebuild_formatter: "xcpretty",
@@ -69,18 +79,32 @@ platform :ios do
}
)
+ slack(
+ message: "Build Number: #{new_build_number} ์์นด์ด๋ธ ์๋ฃ",
+ slack_url: "https://hooks.slack.com/services/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"
+ )
+
upload_to_testflight
# notification(
# subtitle: "Testflight ๋ฐฐํฌ ์ฑ๊ณต!",
# message: "Build Number : #{new_build_number}, ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ์ ์ฑ๊ณตํ์ต๋๋ค!"
# )
+
+ slack(
+ message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ฑ๊ณต!",
+ slack_url: "https://hooks.slack.com/services/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"
+ )
end
- # error do |lane, exception, options|
+ error do |lane, exception, options|
# notification(
# subtitle: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.",
# message: "#{exception}"
# )
- # end
+ slack(
+ message: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. #{exception}",
+ slack_url: "https://hooks.slack.com/services/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"
+ )
+ end
end
From f617c0ac8c1f8939062d2cc4b5105f7b79497a48 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 18 Jun 2025 19:59:18 +0900
Subject: [PATCH 18/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95=204?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 29c5cf1..4d1ff6f 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -59,14 +59,14 @@ platform :ios do
slack(
message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค๋น ์์",
- slack_url: "https://hooks.slack.com/services/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"
+ slack_url: "https://hooks.slack.com/services/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"
)
match(type: "appstore")
slack(
message: "Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น ์๋ฃ",
- slack_url: "https://hooks.slack.com/services/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"
+ slack_url: "https://hooks.slack.com/services/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"
)
build_app(
@@ -81,7 +81,7 @@ platform :ios do
slack(
message: "Build Number: #{new_build_number} ์์นด์ด๋ธ ์๋ฃ",
- slack_url: "https://hooks.slack.com/services/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"
+ slack_url: "https://hooks.slack.com/services/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"
)
upload_to_testflight
@@ -93,7 +93,7 @@ platform :ios do
slack(
message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ฑ๊ณต!",
- slack_url: "https://hooks.slack.com/services/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"
+ slack_url: "https://hooks.slack.com/services/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"
)
end
@@ -104,7 +104,7 @@ platform :ios do
# )
slack(
message: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. #{exception}",
- slack_url: "https://hooks.slack.com/services/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"
+ slack_url: "https://hooks.slack.com/services/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"
)
end
end
From 107fee5e4b3c5a4d713e8b5b3f5150a11898449d Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sat, 21 Jun 2025 22:37:05 +0900
Subject: [PATCH 19/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(sla?=
=?UTF-8?q?ck=20url=20=EB=B3=80=EA=B2=BD)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 1 +
fastlane/Fastfile | 13 +++++++++----
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index 302512e..dec17ac 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -56,6 +56,7 @@ jobs:
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ SLACK_URL: ${{ secrets.SLACK_URL }}
run: bundle exec fastlane beta
- name: Upload Artifacts
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 4d1ff6f..f198155 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -59,14 +59,16 @@ platform :ios do
slack(
message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค๋น ์์",
- slack_url: "https://hooks.slack.com/services/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"
+ channel: "#run_mile",
+ slack_url: "#{ENV["SLACK_URL"]}"
)
match(type: "appstore")
slack(
message: "Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น ์๋ฃ",
- slack_url: "https://hooks.slack.com/services/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"
+ channel: "#run_mile",
+ slack_url: "#{ENV["SLACK_URL"]}"
)
build_app(
@@ -93,7 +95,8 @@ platform :ios do
slack(
message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ฑ๊ณต!",
- slack_url: "https://hooks.slack.com/services/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"
+ channel: "#run_mile",
+ slack_url: "#{ENV["SLACK_URL"]}"
)
end
@@ -104,7 +107,9 @@ platform :ios do
# )
slack(
message: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. #{exception}",
- slack_url: "https://hooks.slack.com/services/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"
+ channel: "#run_mile",
+ success: false,
+ slack_url: "#{ENV["SLACK_URL"]}"
)
end
end
From 1ef58b605e5c16180fa4c7218caeb02b2e633852 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sat, 21 Jun 2025 22:59:14 +0900
Subject: [PATCH 20/54] =?UTF-8?q?[#33]=20Realm=20Dependency=20rule=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95(master=20->=20=ED=8A=B9=EC=A0=95=20=EB=B2=84?=
=?UTF-8?q?=EC=A0=84)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile.xcodeproj/project.pbxproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index acf749b..351b8ad 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -649,8 +649,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/realm-swift.git";
requirement = {
- branch = master;
- kind = branch;
+ kind = exactVersion;
+ version = 10.54.5;
};
};
/* End XCRemoteSwiftPackageReference section */
From c43db55352562005936961a37ae5fb135ac6fe8c Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Mon, 23 Jun 2025 14:39:29 +0900
Subject: [PATCH 21/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(?=
=?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=86=8C=EB=93=9C=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80,=20build=5Fapp=20=EC=88=98=EC=A0=95)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile.xcodeproj/project.pbxproj | 12 +++---
fastlane/Fastfile | 65 +++++++++++++++++++++---------
2 files changed, 51 insertions(+), 26 deletions(-)
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index 351b8ad..4664ded 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -446,7 +446,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 202506182;
+ CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C;
ENABLE_PREVIEWS = YES;
@@ -493,7 +493,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 202506182;
+ CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C;
ENABLE_PREVIEWS = YES;
@@ -536,7 +536,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 202506182;
+ CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.4;
@@ -555,7 +555,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 202506182;
+ CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.4;
@@ -573,7 +573,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 202506182;
+ CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -590,7 +590,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 202506182;
+ CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = VF8R3A969C;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index f198155..08607c3 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -16,6 +16,50 @@
default_platform(:ios)
platform :ios do
+ lane :local do
+ app_store_connect_api_key(
+ key_id: "",
+ issuer_id: "",
+ key_content: ""
+ )
+
+ today = Time.now.strftime("%Y%m%d")
+
+ current_build_number = "#{latest_testflight_build_number}"
+
+ if current_build_number.length == 9
+ current_date = current_build_number[0,8] # ์ 8์๋ฆฌ๊ฐ ๋ ์ง
+ current_count = current_build_number[8].to_i # ๋ง์ง๋ง 1์๋ฆฌ๊ฐ ์ซ์
+ else
+ # ๋น๋ ๋๋ฒ๊ฐ ์๊ฑฐ๋ ํ์์ด ๋ค๋ฅด๋ฉด ์ด๊ธฐํ
+ current_date = ""
+ current_count = 0
+ end
+
+ if current_date == today
+ next_count = current_count + 1
+ else
+ next_count = 1
+ end
+
+ new_build_number = "#{today}#{next_count}"
+
+ increment_build_number(
+ xcodeproj: "Run Mile.xcodeproj",
+ build_number: new_build_number
+ )
+
+ match(type: "appstore")
+
+ build_app(
+ scheme: "Run Mile",
+ xcodebuild_formatter: "xcpretty"
+ )
+
+ upload_to_testflight
+ end
+
+
desc "Push a new beta build to TestFlight"
lane :beta do
@@ -52,11 +96,6 @@ platform :ios do
build_number: new_build_number
)
- # notification(
- # subtitle: "Build Number: #{new_build_number}",
- # message: "ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ ์ค๋น"
- # )
-
slack(
message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค๋น ์์",
channel: "#run_mile",
@@ -73,12 +112,7 @@ platform :ios do
build_app(
scheme: "Run Mile",
- xcodebuild_formatter: "xcpretty",
- export_options: {
- provisioningProfiles: {
- "com.mooni.Run-Mile" => "com.mooni.Run-Mile AppStore"
- }
- }
+ xcodebuild_formatter: "xcpretty"
)
slack(
@@ -88,11 +122,6 @@ platform :ios do
upload_to_testflight
- # notification(
- # subtitle: "Testflight ๋ฐฐํฌ ์ฑ๊ณต!",
- # message: "Build Number : #{new_build_number}, ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ์ ์ฑ๊ณตํ์ต๋๋ค!"
- # )
-
slack(
message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ฑ๊ณต!",
channel: "#run_mile",
@@ -101,10 +130,6 @@ platform :ios do
end
error do |lane, exception, options|
- # notification(
- # subtitle: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.",
- # message: "#{exception}"
- # )
slack(
message: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. #{exception}",
channel: "#run_mile",
From 1f0bd2414fd20e07e85057a538606e873503f636 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Mon, 23 Jun 2025 14:41:23 +0900
Subject: [PATCH 22/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(Sla?=
=?UTF-8?q?ck=20url)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 08607c3..d19a94f 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -117,7 +117,8 @@ platform :ios do
slack(
message: "Build Number: #{new_build_number} ์์นด์ด๋ธ ์๋ฃ",
- slack_url: "https://hooks.slack.com/services/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"
+ channel: "#run_mile",
+ slack_url: "#{ENV["SLACK_URL"]}"
)
upload_to_testflight
From 48999f4462bff0f332b02417aa9b48dd5fa7c673 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Mon, 23 Jun 2025 14:50:31 +0900
Subject: [PATCH 23/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(set?=
=?UTF-8?q?up=5Fci=20=EC=B6=94=EA=B0=80)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index d19a94f..0ad93ff 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -102,6 +102,8 @@ platform :ios do
slack_url: "#{ENV["SLACK_URL"]}"
)
+ setup_ci
+
match(type: "appstore")
slack(
From 1366903ecba549014747590b919ed43ed9b35ea7 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Mon, 23 Jun 2025 15:01:41 +0900
Subject: [PATCH 24/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(lan?=
=?UTF-8?q?e=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 2 +-
Run Mile.xcodeproj/project.pbxproj | 4 ++--
fastlane/Fastfile | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index dec17ac..f12fbe0 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -57,7 +57,7 @@ jobs:
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
SLACK_URL: ${{ secrets.SLACK_URL }}
- run: bundle exec fastlane beta
+ run: bundle exec fastlane testflight
- name: Upload Artifacts
uses: actions/upload-artifact@v4
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index 4664ded..9745616 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -649,8 +649,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/realm-swift.git";
requirement = {
- kind = exactVersion;
- version = 10.54.5;
+ kind = upToNextMajorVersion;
+ minimumVersion = 10.54.5;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 0ad93ff..da94af5 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -61,7 +61,7 @@ platform :ios do
desc "Push a new beta build to TestFlight"
- lane :beta do
+ lane :testflight do
app_store_connect_api_key(
key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}",
From 7692c77f9d07bb2f4ea08731e59af2e8c633e939 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Mon, 23 Jun 2025 15:12:27 +0900
Subject: [PATCH 25/54] =?UTF-8?q?[#33]=20workflow=20=EC=88=98=EC=A0=95(?=
=?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=ED=8A=B8=EB=A6=AC?=
=?UTF-8?q?=EA=B1=B0=20=EC=A0=9C=EA=B1=B0)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 4 ----
1 file changed, 4 deletions(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index f12fbe0..dca5719 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -4,10 +4,6 @@ on:
push:
branches:
- main
- # ์ก์
ํ
์คํธ์ฉ
- pull_request:
- branches:
- - develop
jobs:
build-upload-testflight:
From af0020f100f4dceaf30c517ae37daed2b05a61be Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Tue, 24 Jun 2025 21:41:14 +0900
Subject: [PATCH 26/54] =?UTF-8?q?[#35]=20noti=20=EC=95=A1=EC=85=98=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../xcdebugger/Breakpoints_v2.xcbkptlist | 6 +++
.../Health/WorkoutDataRepositoryImpl.swift | 30 +-------------
Run Mile/Helper/Delegate/AppDelegate.swift | 29 +++++++++++++-
Run Mile/Helper/HealthKit+.swift | 10 ++++-
.../Helper/UNUserNotificationCenter+.swift | 39 ++++++++++++++++++-
5 files changed, 81 insertions(+), 33 deletions(-)
create mode 100644 Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist
diff --git a/Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist b/Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist
new file mode 100644
index 0000000..a375fdc
--- /dev/null
+++ b/Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -0,0 +1,6 @@
+
+
+
diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
index 5abc71c..09f9942 100644
--- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
+++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
@@ -23,34 +23,6 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository {
sortDescriptors: descriptor
)
- return convertToRunningData(result)
- }
-}
-
-
-extension WorkoutDataRepositoryImpl {
- /// HKWorkout ์ RunningData ์ํฐํฐ๋ก ๋ณํํฉ๋๋ค.
- private func convertToRunningData(_ workouts: [HKWorkout]) -> [RunningData] {
- var resultArray = [RunningData]()
- workouts.forEach {
- if let statistics = $0.statistics(for: HKQuantityType(.distanceWalkingRunning)),
- let sumDistance = statistics.sumQuantity()?.doubleValue(for: .meter()) {
- let localStartDate = Calendar.current.date(
- byAdding: .second,
- value: TimeZone.current.secondsFromGMT(),
- to: $0.startDate
- )
-
- let result = RunningData(
- id: $0.uuid,
- distance: sumDistance,
- date: localStartDate
- )
-
- resultArray.append(result)
- }
- }
-
- return resultArray
+ return result.map { $0.toEntity }
}
}
diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift
index e6806e7..e475a79 100644
--- a/Run Mile/Helper/Delegate/AppDelegate.swift
+++ b/Run Mile/Helper/Delegate/AppDelegate.swift
@@ -98,6 +98,7 @@ extension AppDelegate {
if !UserDefaults.standard.selectedShoesID.isEmpty {
UNUserNotificationCenter.requestNotification(
+ category: .autoRegister(workout.toEntity),
title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
body: distance == nil
? "์ ๋ฐ์ ์๋ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค!"
@@ -107,6 +108,7 @@ extension AppDelegate {
autoRegisterShoes(workout: workout)
} else {
UNUserNotificationCenter.requestNotification(
+ category: .manualRegister(workout.toEntity),
title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
body: distance == nil
? "์ ๋ฐ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฑ๋กํ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค. ๋ฑ๋กํ๋ฌ ๊ฐ๋ณผ๊น์?"
@@ -137,7 +139,7 @@ extension AppDelegate {
let shoesID = UUID(uuidString: UserDefaults.standard.selectedShoesID)!
let shoes = try await shoesDataRepository.fetchSingleShoes(id: shoesID)
var workouts = shoes.workouts
- workouts.append(workout.toEntity())
+ workouts.append(workout.toEntity)
let newShoes = Shoes(
id: shoes.id,
@@ -152,6 +154,7 @@ extension AppDelegate {
try await shoesDataRepository.updateShoes(shoes: newShoes)
} catch {
UNUserNotificationCenter.requestNotification(
+ category: .manualRegister(workout.toEntity),
title: "๋ง์ผ๋ฆฌ์ง ์๋ ๋ฑ๋ก์ ์คํจํ์ต๋๋ค.",
body: "์ฑ์์ ์๋์ผ๋ก ๋ฑ๋ก ๋ถํ๋๋ฆฝ๋๋ค."
)
@@ -167,6 +170,30 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
+ let userInfo = response.notification.request.content.userInfo
+
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
+
+
+ if let category = userInfo["category"] as? String,
+ category == "ManualRegister",
+ let uuidString = userInfo["id"] as? String,
+ let uuid = UUID(uuidString: uuidString),
+ let dateString = userInfo["date"] as? String,
+ let date = dateFormatter.date(from: dateString),
+ let distanceString = userInfo["distance"] as? String,
+ let distance = Double(distanceString)
+ {
+ let runningData = RunningData(
+ id: uuid,
+ distance: distance,
+ date: date
+ )
+
+ NavigationCoordinator.shared.push(.chooseShoes([runningData], {}))
+ }
+
completionHandler()
}
diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift
index 3d423f3..c8221c1 100644
--- a/Run Mile/Helper/HealthKit+.swift
+++ b/Run Mile/Helper/HealthKit+.swift
@@ -52,14 +52,20 @@ extension HKWorkout {
return nil
}
- public func toEntity() -> RunningData {
+ public var toEntity: RunningData {
let statistics = self.statistics(for: HKQuantityType(.distanceWalkingRunning))!
let sumDistance = statistics.sumQuantity()!
+ let localStartDate = Calendar.current.date(
+ byAdding: .second,
+ value: TimeZone.current.secondsFromGMT(),
+ to: self.startDate
+ )
+
return .init(
id: self.uuid,
distance: sumDistance.doubleValue(for: .meter()),
- date: self.startDate
+ date: localStartDate
)
}
}
diff --git a/Run Mile/Helper/UNUserNotificationCenter+.swift b/Run Mile/Helper/UNUserNotificationCenter+.swift
index 4f6c22c..221365f 100644
--- a/Run Mile/Helper/UNUserNotificationCenter+.swift
+++ b/Run Mile/Helper/UNUserNotificationCenter+.swift
@@ -9,7 +9,12 @@ import UserNotifications
extension UNUserNotificationCenter {
- static func requestNotification(id: String = UUID().uuidString, title: String, body: String) {
+ static func requestNotification(
+ category: NotificationCategory = .none,
+ id: String = UUID().uuidString,
+ title: String,
+ body: String
+ ) {
let center = self.current()
let content = UNMutableNotificationContent()
@@ -17,8 +22,40 @@ extension UNUserNotificationCenter {
content.body = body
content.sound = .default
+ switch category {
+ case let .manualRegister(runningData):
+ let formatter = DateFormatter()
+
+ formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
+ content.userInfo = [
+ "id": runningData.id.uuidString,
+ "date": formatter.string(from: runningData.date ?? .now),
+ "distance": "\(runningData.distance)",
+ "category": category.rawValue
+ ]
+ case .autoRegister, .none:
+ break
+ }
+
let request = UNNotificationRequest(identifier: id, content: content, trigger: nil)
center.add(request)
}
+
+ public enum NotificationCategory: Hashable {
+ case autoRegister(RunningData)
+ case manualRegister(RunningData)
+ case none
+
+ public var rawValue: String {
+ switch self {
+ case .autoRegister:
+ "AutoRegister"
+ case .manualRegister:
+ "ManualRegister"
+ case .none:
+ "None"
+ }
+ }
+ }
}
From 133069fb9d0143a0e6196161a41c26e40c9bdf1e Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Tue, 24 Jun 2025 22:24:24 +0900
Subject: [PATCH 27/54] =?UTF-8?q?[#35]=20Refreshable=20=EC=95=A1=EC=85=98?=
=?UTF-8?q?=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile.xcodeproj/project.pbxproj | 8 ++++----
Run Mile/Helper/HealthKit+.swift | 13 +++++++------
Run Mile/Presentation/1.Shoes/ShoesListView.swift | 3 +++
.../Presentation/2.Workout/WorkoutListView.swift | 5 +++++
4 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index 9745616..b09dae7 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -444,7 +444,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = "";
@@ -473,7 +473,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -491,7 +491,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = "";
@@ -520,7 +520,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift
index c8221c1..e2f6584 100644
--- a/Run Mile/Helper/HealthKit+.swift
+++ b/Run Mile/Helper/HealthKit+.swift
@@ -56,16 +56,17 @@ extension HKWorkout {
let statistics = self.statistics(for: HKQuantityType(.distanceWalkingRunning))!
let sumDistance = statistics.sumQuantity()!
- let localStartDate = Calendar.current.date(
- byAdding: .second,
- value: TimeZone.current.secondsFromGMT(),
- to: self.startDate
- )
+ // ์ค๊ธฐ๊ธฐ ๊ฒ์ฆ ํ์
+// let localStartDate = Calendar.current.date(
+// byAdding: .second,
+// value: TimeZone.current.secondsFromGMT(),
+// to: self.startDate
+// )
return .init(
id: self.uuid,
distance: sumDistance.doubleValue(for: .meter()),
- date: localStartDate
+ date: self.startDate
)
}
}
diff --git a/Run Mile/Presentation/1.Shoes/ShoesListView.swift b/Run Mile/Presentation/1.Shoes/ShoesListView.swift
index 79f6135..f9e49f7 100644
--- a/Run Mile/Presentation/1.Shoes/ShoesListView.swift
+++ b/Run Mile/Presentation/1.Shoes/ShoesListView.swift
@@ -67,6 +67,9 @@ private struct CurrentShoesListView: View {
}
.padding(.horizontal, 20)
}
+ .refreshable {
+ viewModel.onAppear()
+ }
}
}
diff --git a/Run Mile/Presentation/2.Workout/WorkoutListView.swift b/Run Mile/Presentation/2.Workout/WorkoutListView.swift
index 90bcca8..98ff086 100644
--- a/Run Mile/Presentation/2.Workout/WorkoutListView.swift
+++ b/Run Mile/Presentation/2.Workout/WorkoutListView.swift
@@ -110,6 +110,11 @@ private struct WorkoutScrollView: View {
}
.padding(.horizontal, 20)
}
+ .refreshable {
+ Task {
+ await viewModel.onAppear()
+ }
+ }
}
}
From 7efd73d9c3649d5f72d34ccaba2ec2c89eb8d957 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 25 Jun 2025 12:15:46 +0900
Subject: [PATCH 28/54] =?UTF-8?q?[#35]=20UserNotifications=20=EC=9D=98?=
=?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift | 3 +--
Run Mile/Helper/Delegate/AppDelegate.swift | 7 +++----
Run Mile/Helper/UNUserNotificationCenter+.swift | 4 ++--
3 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift
index d2efe16..3b2658d 100644
--- a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift
+++ b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift
@@ -7,7 +7,6 @@
import Foundation
import RealmSwift
-import UserNotifications
actor ShoesDataRepositoryImpl: ShoesDataRepository {
@@ -104,7 +103,7 @@ actor ShoesDataRepositoryImpl: ShoesDataRepository {
dto.workouts = list
if !shoes.isGradutate, shoes.isOverGoal {
- UNUserNotificationCenter.requestNotification(
+ UserNotificationsManager.requestNotification(
title: "\(shoes.nickname)์ ๋ชฉํ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฌ์ฑํ์ต๋๋ค!",
body: "์ถํ๋๋ฆฝ๋๋ค! ์ด์ ๋ช
์์ ์ ๋น์ผ๋ก ๊ฐ ์ผ๋ง ๋จ์์ต๋๋ค. ๊ฐ๋ณด์ค๊น์?"
)
diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift
index e475a79..322faba 100644
--- a/Run Mile/Helper/Delegate/AppDelegate.swift
+++ b/Run Mile/Helper/Delegate/AppDelegate.swift
@@ -8,7 +8,6 @@
import UIKit
import HealthKit
import SwiftUI
-import UserNotifications
final class AppDelegate: NSObject, UIApplicationDelegate {
@@ -97,7 +96,7 @@ extension AppDelegate {
let distance = workout.getKilometerDistance()
if !UserDefaults.standard.selectedShoesID.isEmpty {
- UNUserNotificationCenter.requestNotification(
+ UserNotificationsManager.requestNotification(
category: .autoRegister(workout.toEntity),
title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
body: distance == nil
@@ -107,7 +106,7 @@ extension AppDelegate {
autoRegisterShoes(workout: workout)
} else {
- UNUserNotificationCenter.requestNotification(
+ UserNotificationsManager.requestNotification(
category: .manualRegister(workout.toEntity),
title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
body: distance == nil
@@ -153,7 +152,7 @@ extension AppDelegate {
try await shoesDataRepository.updateShoes(shoes: newShoes)
} catch {
- UNUserNotificationCenter.requestNotification(
+ UserNotificationsManager.requestNotification(
category: .manualRegister(workout.toEntity),
title: "๋ง์ผ๋ฆฌ์ง ์๋ ๋ฑ๋ก์ ์คํจํ์ต๋๋ค.",
body: "์ฑ์์ ์๋์ผ๋ก ๋ฑ๋ก ๋ถํ๋๋ฆฝ๋๋ค."
diff --git a/Run Mile/Helper/UNUserNotificationCenter+.swift b/Run Mile/Helper/UNUserNotificationCenter+.swift
index 221365f..0368cec 100644
--- a/Run Mile/Helper/UNUserNotificationCenter+.swift
+++ b/Run Mile/Helper/UNUserNotificationCenter+.swift
@@ -8,14 +8,14 @@
import UserNotifications
-extension UNUserNotificationCenter {
+public enum UserNotificationsManager {
static func requestNotification(
category: NotificationCategory = .none,
id: String = UUID().uuidString,
title: String,
body: String
) {
- let center = self.current()
+ let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = title
From 757edb5d42bd96154af7657a1ca90413138a6e69 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 25 Jun 2025 12:23:05 +0900
Subject: [PATCH 29/54] =?UTF-8?q?[#35]=20RunningData=20->=20Workout=20?=
=?UTF-8?q?=EB=A6=AC=EB=84=A4=EC=9D=B4=EB=B0=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift | 11 ++++++++++-
Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift | 8 ++++----
Run Mile/Domain/Entities/RunningData.swift | 2 +-
Run Mile/Domain/Entities/Shoes.swift | 4 ++--
.../Repositories/WorkoutDataRepository.swift | 8 +++++++-
Run Mile/Domain/UseCases/ChooseShoesUseCase.swift | 4 ++--
Run Mile/Domain/UseCases/HealthDataUseCase.swift | 4 ++--
Run Mile/Helper/Delegate/AppDelegate.swift | 2 +-
Run Mile/Helper/HealthKit+.swift | 2 +-
Run Mile/Helper/UNUserNotificationCenter+.swift | 4 ++--
.../Presentation/0.Main/NavigationCoordinator.swift | 2 +-
.../1-2.ShoesDetail/ShoesDetailViewModel.swift | 2 +-
.../2.Workout/2-1.ChooseShoes/ChooseShoesView.swift | 2 +-
.../2-1.ChooseShoes/ChooseShoesViewModel.swift | 4 ++--
.../Presentation/2.Workout/WorkoutListViewModel.swift | 10 +++++-----
Run Mile/Presentation/Components/WorkoutCell.swift | 2 +-
16 files changed, 43 insertions(+), 28 deletions(-)
diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
index 09f9942..4d43648 100644
--- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
+++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
@@ -12,7 +12,7 @@ import HealthKit
actor WorkoutDataRepositoryImpl: WorkoutDataRepository {
private let store = HKHealthStore()
- public func fetchWorkoutData() async throws -> [RunningData] {
+ public func fetchWorkoutData() async throws -> [Workout] {
let predicate = HKQuery.predicateForWorkouts(with: .running)
let descriptor = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]
@@ -25,4 +25,13 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository {
return result.map { $0.toEntity }
}
+
+
+ public func saveWorkoutData(workouts: [Workout]) async throws {
+
+ }
+
+ public func deleteWorkoutDate(workouts: [Workout]) async throws {
+
+ }
}
diff --git a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift
index 3b2658d..12f16a9 100644
--- a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift
+++ b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift
@@ -37,10 +37,10 @@ actor ShoesDataRepositoryImpl: ShoesDataRepository {
let fetchedResult = realm.object(ofType: ShoesDTO.self, forPrimaryKey: id)
if let result = fetchedResult {
- var workouts = [RunningData]()
+ var workouts = [Workout]()
result.workouts.forEach { workout in
workouts.append(
- RunningData(
+ Workout(
id: workout.id,
distance: workout.distance,
date: workout.date
@@ -134,10 +134,10 @@ extension ShoesDataRepositoryImpl {
private func toEntities(_ dto: Results) -> [Shoes] {
var resultArray: [Shoes] = []
dto.forEach {
- var workouts = [RunningData]()
+ var workouts = [Workout]()
$0.workouts.forEach { workout in
workouts.append(
- RunningData(
+ Workout(
id: workout.id,
distance: workout.distance,
date: workout.date
diff --git a/Run Mile/Domain/Entities/RunningData.swift b/Run Mile/Domain/Entities/RunningData.swift
index a844c2b..5ddc1d3 100644
--- a/Run Mile/Domain/Entities/RunningData.swift
+++ b/Run Mile/Domain/Entities/RunningData.swift
@@ -7,7 +7,7 @@
import Foundation
-public struct RunningData: Sendable, Identifiable, Hashable {
+public struct Workout: Sendable, Identifiable, Hashable {
public let id: UUID
public let distance: Double
public let date: Date?
diff --git a/Run Mile/Domain/Entities/Shoes.swift b/Run Mile/Domain/Entities/Shoes.swift
index 77b2209..e7aacd2 100644
--- a/Run Mile/Domain/Entities/Shoes.swift
+++ b/Run Mile/Domain/Entities/Shoes.swift
@@ -16,7 +16,7 @@ struct Shoes: Sendable, Identifiable, Hashable {
let goalMileage: Double // ๋ชฉํ ๋ง์ผ๋ฆฌ์ง
let currentMileage: Double // ์ด๊ธฐ ๋ง์ผ๋ฆฌ์ง
let isCurrentShoes: Bool // ํ์ฌ ์๋๋ฑ๋ก์ด ์ ํ๋ ์ ๋ฐ์ธ์ง ์ฌ๋ถ
- var workouts: [RunningData] // ์ ๋ฐ์ ๋ฑ๋ก๋ ์ด๋๊ธฐ๋ก
+ var workouts: [Workout] // ์ ๋ฐ์ ๋ฑ๋ก๋ ์ด๋๊ธฐ๋ก
let isGradutate: Bool // ์ ๋ฐ ์กธ์
์ฌ๋ถ
init(id: UUID,
@@ -25,7 +25,7 @@ struct Shoes: Sendable, Identifiable, Hashable {
nickname: String,
goalMileage: Double,
currentMileage: Double,
- workouts: [RunningData],
+ workouts: [Workout],
isGraduate: Bool = false
) {
self.id = id
diff --git a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
index a062250..2530da1 100644
--- a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
+++ b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
@@ -11,5 +11,11 @@ import HealthKit
protocol WorkoutDataRepository: Sendable {
/// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
- func fetchWorkoutData() async throws -> [RunningData]
+ func fetchWorkoutData() async throws -> [Workout]
+
+ /// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค. (๋ฑ๋ก๋ ์ด๋ ํํฐ๋ฆด ์ฉ)
+ func saveWorkoutData(workouts: [Workout]) async throws
+
+ /// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํฉ๋๋ค. (๋ฑ๋ก๋ ์ด๋ ํํฐ๋ฆด ์ฉ)
+ func deleteWorkoutDate(workouts: [Workout]) async throws
}
diff --git a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
index 8bf6e76..1b8447f 100644
--- a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
+++ b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
@@ -10,7 +10,7 @@ import Foundation
protocol ChooseShoesUseCase: Sendable {
func fetchShoesList() async throws -> [Shoes]
- func registerWorkouts(shoes: Shoes, workouts: [RunningData]) async throws
+ func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws
}
@@ -26,7 +26,7 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase {
try await repository.fetchCurrentShoes()
}
- public func registerWorkouts(shoes: Shoes, workouts: [RunningData]) async throws {
+ public func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws {
var newWorkouts = shoes.workouts
newWorkouts.append(contentsOf: workouts)
diff --git a/Run Mile/Domain/UseCases/HealthDataUseCase.swift b/Run Mile/Domain/UseCases/HealthDataUseCase.swift
index 3fd5c35..6f475f6 100644
--- a/Run Mile/Domain/UseCases/HealthDataUseCase.swift
+++ b/Run Mile/Domain/UseCases/HealthDataUseCase.swift
@@ -14,7 +14,7 @@ protocol HealthDataUseCase {
@discardableResult
func checkHealthAuthorization() async throws -> Bool
- func fetchWorkoutData() async throws -> [RunningData]
+ func fetchWorkoutData() async throws -> [Workout]
}
@@ -43,7 +43,7 @@ final class DefaultHealthDataUseCase: HealthDataUseCase {
return true
}
- public func fetchWorkoutData() async throws -> [RunningData] {
+ public func fetchWorkoutData() async throws -> [Workout] {
let fetchedResult = try await workoutDataRepository.fetchWorkoutData()
let shoes = try await shoesDataRepository.fetchAllShoes()
diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift
index 322faba..d33f0fe 100644
--- a/Run Mile/Helper/Delegate/AppDelegate.swift
+++ b/Run Mile/Helper/Delegate/AppDelegate.swift
@@ -184,7 +184,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
let distanceString = userInfo["distance"] as? String,
let distance = Double(distanceString)
{
- let runningData = RunningData(
+ let runningData = Workout(
id: uuid,
distance: distance,
date: date
diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift
index e2f6584..3f13bbe 100644
--- a/Run Mile/Helper/HealthKit+.swift
+++ b/Run Mile/Helper/HealthKit+.swift
@@ -52,7 +52,7 @@ extension HKWorkout {
return nil
}
- public var toEntity: RunningData {
+ public var toEntity: Workout {
let statistics = self.statistics(for: HKQuantityType(.distanceWalkingRunning))!
let sumDistance = statistics.sumQuantity()!
diff --git a/Run Mile/Helper/UNUserNotificationCenter+.swift b/Run Mile/Helper/UNUserNotificationCenter+.swift
index 0368cec..34e0c89 100644
--- a/Run Mile/Helper/UNUserNotificationCenter+.swift
+++ b/Run Mile/Helper/UNUserNotificationCenter+.swift
@@ -43,8 +43,8 @@ public enum UserNotificationsManager {
}
public enum NotificationCategory: Hashable {
- case autoRegister(RunningData)
- case manualRegister(RunningData)
+ case autoRegister(Workout)
+ case manualRegister(Workout)
case none
public var rawValue: String {
diff --git a/Run Mile/Presentation/0.Main/NavigationCoordinator.swift b/Run Mile/Presentation/0.Main/NavigationCoordinator.swift
index 5bbac0c..7190fe7 100644
--- a/Run Mile/Presentation/0.Main/NavigationCoordinator.swift
+++ b/Run Mile/Presentation/0.Main/NavigationCoordinator.swift
@@ -156,7 +156,7 @@ extension NavigationCoordinator {
var id: Self { self }
case addShoes(() -> Void)
- case chooseShoes([RunningData], () -> Void)
+ case chooseShoes([Workout], () -> Void)
case automaticRegister
}
}
diff --git a/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift b/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift
index 1981a12..ff579e4 100644
--- a/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift
+++ b/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift
@@ -99,7 +99,7 @@ extension ShoesDetailViewModel {
}
@MainActor
- public func workoutCellTapped(_ workout: RunningData) {
+ public func workoutCellTapped(_ workout: Workout) {
switch self.viewStatus {
case .workouts:
if selectedWorkouts.contains(workout.id) {
diff --git a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift
index bae151c..f73e717 100644
--- a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift
+++ b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift
@@ -14,7 +14,7 @@ struct ChooseShoesView: View {
let dismiss: () -> Void
init(
- workouts: [RunningData],
+ workouts: [Workout],
dismiss: @escaping () -> Void
) {
self.viewModel = .init(
diff --git a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift
index a261099..d7c6ad1 100644
--- a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift
+++ b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift
@@ -10,13 +10,13 @@ import Foundation
@Observable
final class ChooseShoesViewModel {
private let useCase: ChooseShoesUseCase
- private let workouts: [RunningData]
+ private let workouts: [Workout]
public var shoes: [Shoes] = []
init(
useCase: ChooseShoesUseCase,
- workouts: [RunningData]
+ workouts: [Workout]
) {
self.useCase = useCase
self.workouts = workouts
diff --git a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift
index 923ac95..2738ff8 100644
--- a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift
+++ b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift
@@ -13,7 +13,7 @@ final class WorkoutListViewModel {
private let useCase: HealthDataUseCase
public var dateHeaders: [String] = []
- public var workouts: [[RunningData]] = []
+ public var workouts: [[Workout]] = []
public var viewStatus: ViewStatus = .none
public var selectedWorkout: Set = []
@@ -76,7 +76,7 @@ extension WorkoutListViewModel {
}
@MainActor
- public func workoutCellTapped(workout: RunningData) {
+ public func workoutCellTapped(workout: Workout) {
if case .selection = self.viewStatus {
let id = workout.id
@@ -149,18 +149,18 @@ extension WorkoutListViewModel {
self.viewStatus = workouts.isEmpty ? .empty : .none
}
- public func isSelectedWorkout(_ workout: RunningData) -> Bool {
+ public func isSelectedWorkout(_ workout: Workout) -> Bool {
self.selectedWorkout.contains(workout.id)
}
}
extension WorkoutListViewModel {
- private func classifyWorkoutsByDate(workouts: [RunningData]) {
+ private func classifyWorkoutsByDate(workouts: [Workout]) {
self.workouts.removeAll()
self.dateHeaders.removeAll()
- var resultWorkouts = [RunningData]()
+ var resultWorkouts = [Workout]()
for workout in workouts {
if dateHeaders.isEmpty {
diff --git a/Run Mile/Presentation/Components/WorkoutCell.swift b/Run Mile/Presentation/Components/WorkoutCell.swift
index 7c499cd..d8c897e 100644
--- a/Run Mile/Presentation/Components/WorkoutCell.swift
+++ b/Run Mile/Presentation/Components/WorkoutCell.swift
@@ -9,7 +9,7 @@ import SwiftUI
struct WorkoutCell: View {
- let workout: RunningData
+ let workout: Workout
let action: () -> Void
From c81106ea1d7b90fc310fe255e7032391cd1c2ce3 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 25 Jun 2025 13:42:25 +0900
Subject: [PATCH 30/54] =?UTF-8?q?[#35]=20WorkoutDTO=20=EC=97=94=ED=8B=B0?=
=?UTF-8?q?=ED=8B=B0=20=EC=97=AD=EA=B4=80=EA=B3=84=20=EC=B6=94=EA=B0=80,?=
=?UTF-8?q?=20Realm=20DB=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?=
=?UTF-8?q?=EC=85=98=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile/Data/DTO/WorkoutDTO.swift | 2 +
.../Health/WorkoutDataRepositoryImpl.swift | 5 +++
.../Repositories/WorkoutDataRepository.swift | 3 ++
Run Mile/Helper/Delegate/AppDelegate.swift | 45 +++++++++++++++++++
4 files changed, 55 insertions(+)
diff --git a/Run Mile/Data/DTO/WorkoutDTO.swift b/Run Mile/Data/DTO/WorkoutDTO.swift
index 8e7ea3e..9bd3d94 100644
--- a/Run Mile/Data/DTO/WorkoutDTO.swift
+++ b/Run Mile/Data/DTO/WorkoutDTO.swift
@@ -13,4 +13,6 @@ final class WorkoutDTO: Object {
@Persisted(primaryKey: true) public var id: UUID
@Persisted public var date: Date?
@Persisted public var distance: Double
+ /// ์ญ๊ด๊ณ
+ @Persisted(originProperty: "workouts") public var shoes: LinkingObjects
}
diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
index 4d43648..a69a0c2 100644
--- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
+++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
@@ -5,6 +5,7 @@
// Created by ๋ฌธ์ธ๋ฒ on 4/15/25.
//
+import RealmSwift
import Foundation
import HealthKit
@@ -26,6 +27,10 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository {
return result.map { $0.toEntity }
}
+ public func fetchSavedWorkoutData() async throws -> [Workout] {
+ return []
+ }
+
public func saveWorkoutData(workouts: [Workout]) async throws {
diff --git a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
index 2530da1..c0ddb0f 100644
--- a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
+++ b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
@@ -13,6 +13,9 @@ protocol WorkoutDataRepository: Sendable {
/// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
func fetchWorkoutData() async throws -> [Workout]
+ /// ์ ์ฅ๋ ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
+ func fetchSavedWorkoutData() async throws -> [Workout]
+
/// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค. (๋ฑ๋ก๋ ์ด๋ ํํฐ๋ฆด ์ฉ)
func saveWorkoutData(workouts: [Workout]) async throws
diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift
index d33f0fe..c2c1c4d 100644
--- a/Run Mile/Helper/Delegate/AppDelegate.swift
+++ b/Run Mile/Helper/Delegate/AppDelegate.swift
@@ -8,6 +8,7 @@
import UIKit
import HealthKit
import SwiftUI
+import RealmSwift
final class AppDelegate: NSObject, UIApplicationDelegate {
@@ -19,6 +20,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
Task {
await self.userNotificationAuthorize()
await AppDelegate.setHealthBackgroundTask()
+ self.realmMigration()
}
return true
@@ -160,6 +162,49 @@ extension AppDelegate {
}
}
}
+
+ private func realmMigration() {
+ let config = Realm.Configuration(
+ schemaVersion: 1,
+ migrationBlock: { migration, oldSchemaVersion in
+ if oldSchemaVersion < 1 {
+
+ }
+ }
+ )
+
+ Realm.Configuration.defaultConfiguration = config
+
+ #if DEBUG
+ print(Realm.Configuration.defaultConfiguration.fileURL ?? "์ ์ ์์")
+ #endif
+ }
+
+ private func workoutDataMigration() {
+ let workoutRepository = WorkoutDataRepositoryImpl()
+ let shoesRepository = ShoesDataRepositoryImpl()
+
+ Task {
+ do {
+ let workouts = try await workoutRepository.fetchSavedWorkoutData()
+ let shoes = try await shoesRepository.fetchAllShoes()
+
+ if !shoes.isEmpty, workouts.isEmpty {
+ var workouts = [Workout]()
+
+ shoes.forEach {
+ $0.workouts.forEach {
+ workouts.append($0)
+ }
+ }
+
+ try await workoutRepository.saveWorkoutData(workouts: workouts)
+ }
+ } catch {
+ // TODO: Error ์ฒ๋ฆฌ
+ }
+ }
+ }
}
extension AppDelegate: UNUserNotificationCenterDelegate {
From 433703c92a4ef19fc76a2a4a2389b8dbaae25786 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Wed, 25 Jun 2025 22:50:15 +0900
Subject: [PATCH 31/54] =?UTF-8?q?[#35]=20=EB=A7=88=EC=9D=B4=EA=B7=B8?=
=?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=EC=97=85=EB=8D=B0=EC=9D=B4?=
=?UTF-8?q?=ED=8A=B8,=20=EC=A4=91=EB=B3=B5=20=EC=9A=B4=EB=8F=99=20?=
=?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Health/WorkoutDataRepositoryImpl.swift | 28 +++++---
.../Repositories/WorkoutDataRepository.swift | 11 ++--
.../Domain/UseCases/ChooseShoesUseCase.swift | 65 +++++++++++++++----
.../Domain/UseCases/HealthDataUseCase.swift | 9 +--
Run Mile/Helper/Delegate/AppDelegate.swift | 28 +-------
5 files changed, 79 insertions(+), 62 deletions(-)
diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
index a69a0c2..d384e59 100644
--- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
+++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift
@@ -13,7 +13,7 @@ import HealthKit
actor WorkoutDataRepositoryImpl: WorkoutDataRepository {
private let store = HKHealthStore()
- public func fetchWorkoutData() async throws -> [Workout] {
+ public func fetchAllWorkoutData() async throws -> [Workout] {
let predicate = HKQuery.predicateForWorkouts(with: .running)
let descriptor = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]
@@ -27,16 +27,28 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository {
return result.map { $0.toEntity }
}
- public func fetchSavedWorkoutData() async throws -> [Workout] {
- return []
- }
-
-
- public func saveWorkoutData(workouts: [Workout]) async throws {
+ public func fetchUnsavedWorkoutData() async throws -> [Workout] {
+ let savedWorkouts = try await fetchSavedWorkoutData()
+ let entireWorkouts = try await fetchAllWorkoutData()
+ let result = entireWorkouts.filter { first in
+ !savedWorkouts.contains(where: { $0.id == first.id })
+ }
+
+ return result
}
- public func deleteWorkoutDate(workouts: [Workout]) async throws {
+ public func fetchSavedWorkoutData() async throws -> [Workout] {
+ let realm = try await Realm.open()
+ let fetchedResult = realm.objects(WorkoutDTO.self)
+ var result = [Workout]()
+
+ fetchedResult.forEach {
+ result.append(
+ .init(id: $0.id, distance: $0.distance, date: $0.date)
+ )
+ }
+ return result
}
}
diff --git a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
index c0ddb0f..785820c 100644
--- a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
+++ b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift
@@ -11,14 +11,11 @@ import HealthKit
protocol WorkoutDataRepository: Sendable {
/// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
- func fetchWorkoutData() async throws -> [Workout]
+ func fetchAllWorkoutData() async throws -> [Workout]
+
+ ///
+ func fetchUnsavedWorkoutData() async throws -> [Workout]
/// ์ ์ฅ๋ ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
func fetchSavedWorkoutData() async throws -> [Workout]
-
- /// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค. (๋ฑ๋ก๋ ์ด๋ ํํฐ๋ฆด ์ฉ)
- func saveWorkoutData(workouts: [Workout]) async throws
-
- /// ์ด๋(๋ฌ๋) ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํฉ๋๋ค. (๋ฑ๋ก๋ ์ด๋ ํํฐ๋ฆด ์ฉ)
- func deleteWorkoutDate(workouts: [Workout]) async throws
}
diff --git a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
index 1b8447f..b5e1f98 100644
--- a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
+++ b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
@@ -28,19 +28,60 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase {
public func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws {
var newWorkouts = shoes.workouts
- newWorkouts.append(contentsOf: workouts)
- let newShoes = Shoes(
- id: shoes.id,
- image: shoes.image,
- shoesName: shoes.shoesName,
- nickname: shoes.nickname,
- goalMileage: shoes.goalMileage,
- currentMileage: shoes.currentMileage,
- workouts: newWorkouts
- )
-
- try await repository.updateShoes(shoes: newShoes)
+ if newWorkouts.contains(workouts) {
+ if workouts.count == 1 {
+ await NavigationCoordinator.shared.push(
+ .init(
+ title: "์ค๋ณต๋ ์ด๋ ๋ฐ์ดํฐ์
๋๋ค.",
+ message: "๋ค์ ์๋ํด์ฃผ์ธ์!",
+ firstButton: .cancel(title: "ํ์ธ", action: {}),
+ secondButton: nil
+ )
+ )
+ } else {
+ workouts.forEach {
+ if !newWorkouts.contains($0) {
+ newWorkouts.append($0)
+ }
+ }
+
+ let newShoes = Shoes(
+ id: shoes.id,
+ image: shoes.image,
+ shoesName: shoes.shoesName,
+ nickname: shoes.nickname,
+ goalMileage: shoes.goalMileage,
+ currentMileage: shoes.currentMileage,
+ workouts: newWorkouts
+ )
+
+ try await repository.updateShoes(shoes: newShoes)
+
+ await NavigationCoordinator.shared.push(
+ .init(
+ title: "์ค๋ณต๋ ์ด๋ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.",
+ message: "ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ ์ธํ ๋๋จธ์ง ์ด๋ ๋ฐ์ดํฐ๋ง ์ ์ฅ ์๋ฃ ํ์ต๋๋ค.",
+ firstButton: .cancel(title: "ํ์ธ", action: {}),
+ secondButton: nil
+ )
+ )
+ }
+ } else {
+ newWorkouts.append(contentsOf: workouts)
+
+ let newShoes = Shoes(
+ id: shoes.id,
+ image: shoes.image,
+ shoesName: shoes.shoesName,
+ nickname: shoes.nickname,
+ goalMileage: shoes.goalMileage,
+ currentMileage: shoes.currentMileage,
+ workouts: newWorkouts
+ )
+
+ try await repository.updateShoes(shoes: newShoes)
+ }
}
diff --git a/Run Mile/Domain/UseCases/HealthDataUseCase.swift b/Run Mile/Domain/UseCases/HealthDataUseCase.swift
index 6f475f6..a50e6a9 100644
--- a/Run Mile/Domain/UseCases/HealthDataUseCase.swift
+++ b/Run Mile/Domain/UseCases/HealthDataUseCase.swift
@@ -44,14 +44,7 @@ final class DefaultHealthDataUseCase: HealthDataUseCase {
}
public func fetchWorkoutData() async throws -> [Workout] {
- let fetchedResult = try await workoutDataRepository.fetchWorkoutData()
- let shoes = try await shoesDataRepository.fetchAllShoes()
-
- let registeredId = Set(shoes.flatMap { $0.workouts.map { $0.id } })
-
- return fetchedResult.filter ({ workout in
- !registeredId.contains(workout.id)
- })
+ try await workoutDataRepository.fetchUnsavedWorkoutData()
}
}
diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift
index c2c1c4d..40c4b52 100644
--- a/Run Mile/Helper/Delegate/AppDelegate.swift
+++ b/Run Mile/Helper/Delegate/AppDelegate.swift
@@ -16,11 +16,11 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
+ self.realmMigration()
Task {
await self.userNotificationAuthorize()
await AppDelegate.setHealthBackgroundTask()
- self.realmMigration()
}
return true
@@ -179,32 +179,6 @@ extension AppDelegate {
print(Realm.Configuration.defaultConfiguration.fileURL ?? "์ ์ ์์")
#endif
}
-
- private func workoutDataMigration() {
- let workoutRepository = WorkoutDataRepositoryImpl()
- let shoesRepository = ShoesDataRepositoryImpl()
-
- Task {
- do {
- let workouts = try await workoutRepository.fetchSavedWorkoutData()
- let shoes = try await shoesRepository.fetchAllShoes()
-
- if !shoes.isEmpty, workouts.isEmpty {
- var workouts = [Workout]()
-
- shoes.forEach {
- $0.workouts.forEach {
- workouts.append($0)
- }
- }
-
- try await workoutRepository.saveWorkoutData(workouts: workouts)
- }
- } catch {
- // TODO: Error ์ฒ๋ฆฌ
- }
- }
- }
}
extension AppDelegate: UNUserNotificationCenterDelegate {
From e1df98a60c441456d0f09fb961861bd8c17e133c Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Thu, 26 Jun 2025 14:30:24 +0900
Subject: [PATCH 32/54] =?UTF-8?q?[#37]=20=EC=9A=B4=EB=8F=99=20=EB=93=B1?=
=?UTF-8?q?=EB=A1=9D=20=EC=8B=9C=20=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?=
=?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Domain/UseCases/ChooseShoesUseCase.swift | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
index b5e1f98..06df50f 100644
--- a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
+++ b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift
@@ -27,9 +27,20 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase {
}
public func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws {
+ let currentWorkouts = shoes.workouts
+
var newWorkouts = shoes.workouts
- if newWorkouts.contains(workouts) {
+ var isDuplicated = false
+
+ for currentWorkout in currentWorkouts {
+ if workouts.contains(where: { $0.id == currentWorkout.id }) {
+ isDuplicated = true
+ break
+ }
+ }
+
+ if isDuplicated {
if workouts.count == 1 {
await NavigationCoordinator.shared.push(
.init(
@@ -40,9 +51,9 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase {
)
)
} else {
- workouts.forEach {
- if !newWorkouts.contains($0) {
- newWorkouts.append($0)
+ workouts.forEach { workout in
+ if !currentWorkouts.contains(where: { $0.id == workout.id }) {
+ newWorkouts.append(workout)
}
}
From 0358f096bc1cf20575c00daa9600a1bba6b42eb2 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Thu, 26 Jun 2025 22:33:12 +0900
Subject: [PATCH 33/54] =?UTF-8?q?[#37]=20Multiple=20Notification=20?=
=?UTF-8?q?=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95(=EC=A4=91=EB=B3=B5=20?=
=?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=8B=A4=ED=96=89=20=EB=AC=B8=EC=A0=9C,?=
=?UTF-8?q?=20nested=20query=EB=AC=B8=EC=97=90=EC=84=9C=20=EB=8B=A8?=
=?UTF-8?q?=EC=9D=BC=20anchoredquery=20=EC=82=AC=EC=9A=A9=EC=9C=BC?=
=?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile/Helper/Delegate/AppDelegate.swift | 153 ++++++++++--------
Run Mile/Helper/UserDefaults+.swift | 27 ++--
.../2.Workout/WorkoutListViewModel.swift | 2 +-
3 files changed, 95 insertions(+), 87 deletions(-)
diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift
index 40c4b52..582e8fb 100644
--- a/Run Mile/Helper/Delegate/AppDelegate.swift
+++ b/Run Mile/Helper/Delegate/AppDelegate.swift
@@ -6,8 +6,8 @@
//
import UIKit
-import HealthKit
import SwiftUI
+import HealthKit
import RealmSwift
@@ -20,7 +20,8 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
Task {
await self.userNotificationAuthorize()
- await AppDelegate.setHealthBackgroundTask()
+ await Self.setBackgroundDelivery()
+ self.setHealthBackgroundQueryTask()
}
return true
@@ -39,6 +40,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
extension AppDelegate {
+ /// UserNotification ๊ถํ ํ์ฉ
private func userNotificationAuthorize() async {
let notiCenter = UNUserNotificationCenter.current()
@@ -55,84 +57,89 @@ extension AppDelegate {
}
}
- public static func setHealthBackgroundTask() async {
+ /// ๋ฐฑ๊ทธ๋ผ์ด๋์์ Health ๋ฐ์ดํฐ ์ฌ์ฉ ์
๋ฐ์ดํธ ์ค์
+ public static func setBackgroundDelivery() async {
let store = HKHealthStore()
do {
try await store.enableBackgroundDelivery(for: .workoutType(), frequency: .immediate)
- let query = HKObserverQuery(
- sampleType: .workoutType(),
- predicate: nil
- ) { query, completionHandler, error in
- if let error = error {
- print(error)
- return
- }
-
- let sort = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
- let sampleQuery = HKSampleQuery(queryDescriptors: [.init(sampleType: .workoutType(), predicate: nil)], limit: 1, sortDescriptors: [sort]) { _, samples, error in
- if let error = error {
- print(error)
- return
- }
-
- defer {
- UserDefaults.standard.isFirstLaunch = true
- }
-
- guard let workout = samples?.first as? HKWorkout else {
- return
- }
-
- guard case .running = workout.workoutActivityType else {
- return
- }
-
- let workoutId = workout.uuid.uuidString
- let currentId = UserDefaults.standard.recentWorkoutID
-
- if !UserDefaults.standard.isFirstLaunch {
- UserDefaults.standard.recentWorkoutID = workoutId
- } else {
- if workoutId != currentId {
- let distance = workout.getKilometerDistance()
- if !UserDefaults.standard.selectedShoesID.isEmpty {
-
- UserNotificationsManager.requestNotification(
- category: .autoRegister(workout.toEntity),
- title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
- body: distance == nil
- ? "์ ๋ฐ์ ์๋ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค!"
- : String(format: "์ ๋ฐ์ ์๋ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค. ๋ฌ๋ ํ ์คํธ๋ ์นญ ๊ผญ ์์ง ๋ง์ธ์!", distance!)
- )
-
- autoRegisterShoes(workout: workout)
- } else {
- UserNotificationsManager.requestNotification(
- category: .manualRegister(workout.toEntity),
- title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
- body: distance == nil
- ? "์ ๋ฐ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฑ๋กํ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค. ๋ฑ๋กํ๋ฌ ๊ฐ๋ณผ๊น์?"
- : String(format: "%.2fkm, ์์ง ๋ง๊ณ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฑ๋กํ๋ฌ ์ค์ธ์!", distance!)
- )
- }
- UserDefaults.standard.recentWorkoutID = workoutId
- }
- }
- }
-
- store.execute(sampleQuery)
-
- completionHandler()
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+
+ /// ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฌ์ฉํ HealthKit Query ์ค์
+ private func setHealthBackgroundQueryTask() {
+ let store = HKHealthStore()
+
+ let anchoredQuery = HKAnchoredObjectQuery(
+ type: .workoutType(),
+ predicate: nil,
+ anchor: UserDefaults.standard.lastAnchor,
+ limit: HKObjectQueryNoLimit
+ ) { query, samples, deletedObjects, anchor, error in
+ if UserDefaults.standard.lastAnchor != anchor {
+ UserDefaults.standard.lastAnchor = anchor
+ }
+ }
+
+ anchoredQuery.updateHandler = { [weak self] query, samples, deletedObjects, anchor, error in
+ let currentAnchor = UserDefaults.standard.lastAnchor
+
+ if currentAnchor != anchor {
+ UserDefaults.standard.lastAnchor = anchor
+ } else {
+ return
}
- store.execute(query)
- } catch {
- print(error)
+ if let error = error {
+ print(error)
+ return
+ }
+
+ guard let samples = samples as? [HKWorkout],
+ !samples.isEmpty
+ else {
+ print(#function)
+ return
+ }
+
+ guard let workout = samples.first else {
+ return
+ }
+
+ guard case .running = workout.workoutActivityType else {
+ return
+ }
+
+ let distance = workout.getKilometerDistance()
+
+ if !UserDefaults.standard.selectedShoesID.isEmpty {
+ UserNotificationsManager.requestNotification(
+ category: .autoRegister(workout.toEntity),
+ title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
+ body: distance == nil
+ ? "์ ๋ฐ์ ์๋ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค!"
+ : String(format: "์ ๋ฐ์ ์๋ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค. ๋ฌ๋ ํ ์คํธ๋ ์นญ ๊ผญ ์์ง ๋ง์ธ์!", distance!)
+ )
+
+ self?.autoRegisterShoes(workout: workout)
+ } else {
+ UserNotificationsManager.requestNotification(
+ category: .manualRegister(workout.toEntity),
+ title: String(format: "%.2fkm ๋ฌ๋ ์๋ฃ ๐ฅ๐ฅ", distance!),
+ body: distance == nil
+ ? "์ ๋ฐ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฑ๋กํ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค. ๋ฑ๋กํ๋ฌ ๊ฐ๋ณผ๊น์?"
+ : String(format: "%.2fkm, ์์ง ๋ง๊ณ ๋ง์ผ๋ฆฌ์ง๋ฅผ ๋ฑ๋กํ๋ฌ ์ค์ธ์!", distance!)
+ )
+ }
}
+
+ store.execute(anchoredQuery)
}
- private static func autoRegisterShoes(workout: HKWorkout) {
+ /// ์
๋ฐ์ดํธ๋ ์ด๋ ์๋ ๋ฑ๋ก ๋ฉ์๋
+ private func autoRegisterShoes(workout: HKWorkout) {
let shoesDataRepository: ShoesDataRepository = ShoesDataRepositoryImpl()
Task {
@@ -163,10 +170,13 @@ extension AppDelegate {
}
}
+ /// Realm ์คํค๋ง ๋ง์ด๊ทธ๋ ์ด์
private func realmMigration() {
let config = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
+ /// Version 1
+ /// WorkoutDTO : ์ญ๊ด๊ณ ์ถ๊ฐ <-> ShoesDTO
if oldSchemaVersion < 1 {
}
@@ -176,6 +186,7 @@ extension AppDelegate {
Realm.Configuration.defaultConfiguration = config
#if DEBUG
+ // Debug์ฉ print
print(Realm.Configuration.defaultConfiguration.fileURL ?? "์ ์ ์์")
#endif
}
diff --git a/Run Mile/Helper/UserDefaults+.swift b/Run Mile/Helper/UserDefaults+.swift
index 8f8d1e8..bc75e3f 100644
--- a/Run Mile/Helper/UserDefaults+.swift
+++ b/Run Mile/Helper/UserDefaults+.swift
@@ -6,33 +6,30 @@
//
import Foundation
+import HealthKit
extension UserDefaults {
- public var isFirstLaunch: Bool {
- get {
- self.bool(forKey: "isFirstLaunch")
- }
- set {
- self.set(newValue, forKey: "isFirstLaunch")
- }
- }
-
- public var recentWorkoutID: String {
+ public var selectedShoesID: String {
get {
- self.string(forKey: "recentWorkoutID") ?? ""
+ self.string(forKey: "selectedShoesID") ?? ""
}
set {
- self.set(newValue, forKey: "recentWorkoutID")
+ self.set(newValue, forKey: "selectedShoesID")
}
}
- public var selectedShoesID: String {
+ public var lastAnchor: HKQueryAnchor? {
get {
- self.string(forKey: "selectedShoesID") ?? ""
+ self.data(forKey: "anchor").flatMap {
+ try? NSKeyedUnarchiver.unarchivedObject(ofClass: HKQueryAnchor.self, from: $0)
+ }
}
set {
- self.set(newValue, forKey: "selectedShoesID")
+ if let anchor = newValue,
+ let data = try? NSKeyedArchiver.archivedData(withRootObject: anchor, requiringSecureCoding: true) {
+ self.set(data, forKey: "anchor")
+ }
}
}
}
diff --git a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift
index 2738ff8..b7aab59 100644
--- a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift
+++ b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift
@@ -49,7 +49,7 @@ extension WorkoutListViewModel {
self.classifyWorkoutsByDate(workouts: workouts)
- await AppDelegate.setHealthBackgroundTask()
+ await AppDelegate.setBackgroundDelivery()
} catch {
if let error = error as? HealthError,
error == .unknownError || error == .notAvailableDevice {
From d2912fdb3f118f00c7b83214965fb2fac7f26997 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Thu, 26 Jun 2025 23:19:21 +0900
Subject: [PATCH 34/54] =?UTF-8?q?[#39]=20=ED=82=A4=EB=B3=B4=EB=93=9C=20?=
=?UTF-8?q?=ED=88=B4=EB=B0=94=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80(?=
=?UTF-8?q?=EC=9C=84,=20=EC=95=84=EB=9E=98,=20=EC=99=84=EB=A3=8C)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../1.Shoes/1-1.AddShoes/AddShoesView.swift | 26 +++++++
.../1-1.AddShoes/AddShoesViewModel.swift | 67 ++++++++++++++++++-
2 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift
index 20a8c99..de41290 100644
--- a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift
+++ b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift
@@ -69,6 +69,32 @@ struct AddShoesView: View {
.onDisappear {
dismissAction()
}
+ .toolbar {
+ ToolbarItemGroup(placement: .keyboard) {
+ HStack {
+ Button {
+ viewModel.keyboardToolbarUpButtonTapped(&textFieldFocus)
+ } label: {
+ Image(systemName: "chevron.up")
+ }
+ .disabled(textFieldFocus?.isUpButtonDisabled ?? false)
+
+ Button {
+ viewModel.keyboardToolbarDownButtonTapped(&textFieldFocus)
+ } label: {
+ Image(systemName: "chevron.down")
+ }
+ .disabled(textFieldFocus?.isDownButtonDisabled ?? false)
+
+
+ Spacer()
+
+ Button("์๋ฃ") {
+ viewModel.keyboardToolbarCompleteButtonTapped(&textFieldFocus)
+ }
+ }
+ }
+ }
}
}
diff --git a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift
index 67c49af..6ba1f39 100644
--- a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift
+++ b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift
@@ -50,7 +50,9 @@ final class AddShoesViewModel {
init(useCase: AddShoesUseCase) {
self.useCase = useCase
}
-
+}
+
+extension AddShoesViewModel {
enum TextFieldCategory: Hashable {
case name
case nickname
@@ -70,6 +72,50 @@ final class AddShoesViewModel {
return "์ฃผํ ๋ง์ผ๋ฆฌ์ง(Optional)"
}
}
+
+ public var isUpButtonDisabled: Bool {
+ switch self {
+ case .name:
+ true
+ default:
+ false
+ }
+ }
+
+ public var isDownButtonDisabled: Bool {
+ switch self {
+ case .runMileage:
+ true
+ default:
+ false
+ }
+ }
+
+ public var next: Self {
+ switch self {
+ case .name:
+ .nickname
+ case .nickname:
+ .goalMileage
+ case .goalMileage:
+ .runMileage
+ case .runMileage:
+ .runMileage
+ }
+ }
+
+ public var previous: Self {
+ switch self {
+ case .name:
+ .name
+ case .nickname:
+ .name
+ case .goalMileage:
+ .nickname
+ case .runMileage:
+ .goalMileage
+ }
+ }
}
}
@@ -132,6 +178,25 @@ extension AddShoesViewModel {
}
}
+ @MainActor
+ public func keyboardToolbarUpButtonTapped(_ textField: inout TextFieldCategory?) {
+ if let _ = textField {
+ textField = textField?.previous
+ }
+ }
+
+ @MainActor
+ public func keyboardToolbarDownButtonTapped(_ textField: inout TextFieldCategory?) {
+ if let _ = textField {
+ textField = textField?.next
+ }
+ }
+
+ @MainActor
+ public func keyboardToolbarCompleteButtonTapped(_ textField: inout TextFieldCategory?) {
+ textField = nil
+ }
+
public func saveButtonTapped() {
let shoes = Shoes(
id: .init(),
From f78858d5f8f19ae154c3bf0a470ca611a7a22422 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?=
<116792524+mooninbeom@users.noreply.github.com>
Date: Thu, 26 Jun 2025 23:44:32 +0900
Subject: [PATCH 35/54] =?UTF-8?q?[Fix]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index dca5719..de3542e 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -4,6 +4,9 @@ on:
push:
branches:
- main
+ pull_request:
+ branches:
+ - main
jobs:
build-upload-testflight:
@@ -26,10 +29,10 @@ jobs:
# - name: Set up Xcode
# uses: maxim-lobanov/setup-xcode@v1.6.0
# with:
- # xcode-version: '16.2'
+ # xcode-version: '16.3'
- name: Select Xcode version
- run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
+ run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer
- name: Check Xcode version
run: xcodebuild -version
From e7a1499da186341ad32a67a9782a353bca5dff99 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Fri, 27 Jun 2025 13:25:40 +0900
Subject: [PATCH 36/54] =?UTF-8?q?[#42]=20=EC=9D=B8=EC=A6=9D=EC=84=9C=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Run Mile.xcodeproj/project.pbxproj | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index b09dae7..9745616 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -444,7 +444,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = "";
@@ -473,7 +473,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -491,7 +491,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 202506231;
DEVELOPMENT_TEAM = "";
@@ -520,7 +520,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
- "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
From d3ce38c2033ee5287e9fae4291b6bb4aef296013 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Fri, 27 Jun 2025 13:39:46 +0900
Subject: [PATCH 37/54] =?UTF-8?q?[#42]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95(=ED=8A=B8=EB=A6=AC?=
=?UTF-8?q?=EA=B1=B0=20=EB=B3=80=EA=B2=BD)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index de3542e..1710328 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -1,9 +1,8 @@
name: ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ ๐
on:
- push:
- branches:
- - main
+ # main ๋ธ๋์น๋ก PR ์ ํ
์คํธ ํ๋ผ์ดํธ ์
๋ก๋
+ # CD ์ฑ๊ณต ์ฌ๋ถ๋ก ์ํฌํ๋ก์ฐ ์์ ์งํ
pull_request:
branches:
- main
From 6327dceecbaa49967f39f93680c86fc773579a8d Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Fri, 27 Jun 2025 14:48:39 +0900
Subject: [PATCH 38/54] =?UTF-8?q?[#42]=20=EC=83=88=EB=A1=9C=EA=B3=A0?=
=?UTF-8?q?=EC=B9=A8=20UI=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../2.Workout/WorkoutListView.swift | 22 ++++++-------------
1 file changed, 7 insertions(+), 15 deletions(-)
diff --git a/Run Mile/Presentation/2.Workout/WorkoutListView.swift b/Run Mile/Presentation/2.Workout/WorkoutListView.swift
index 98ff086..3ee11c3 100644
--- a/Run Mile/Presentation/2.Workout/WorkoutListView.swift
+++ b/Run Mile/Presentation/2.Workout/WorkoutListView.swift
@@ -21,16 +21,20 @@ struct WorkoutListView: View {
WorkoutNavigationView(viewModel: viewModel)
switch viewModel.viewStatus {
- case .none, .selection:
+ case .none, .selection, .loading:
WorkoutScrollView(viewModel: $viewModel)
- case .loading:
- WorkoutLoadingView()
case .empty:
WorkoutEmptyView(
viewModel: viewModel
)
}
}
+ .overlay {
+ if viewModel.viewStatus == .loading {
+ ProgressView()
+ .progressViewStyle(.circular)
+ }
+ }
.task {
await viewModel.onAppear()
}
@@ -119,18 +123,6 @@ private struct WorkoutScrollView: View {
}
-private struct WorkoutLoadingView: View {
- var body: some View {
- Group {
- Spacer()
- ProgressView()
- .progressViewStyle(.circular)
- Spacer()
- }
- }
-}
-
-
private struct WorkoutEmptyView: View {
let viewModel: WorkoutListViewModel
From 7094cffb8cbe926209229a52b7589ead424d85cb Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sat, 28 Jun 2025 14:48:14 +0900
Subject: [PATCH 39/54] =?UTF-8?q?[#44]=20Fastfile=EC=88=98=EC=A0=95(Slack?=
=?UTF-8?q?=20->=20Discord=20=EC=9B=B9=ED=9B=85=20=EB=B3=80=EA=B2=BD)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 1 +
Gemfile | 3 +++
Gemfile.lock | 36 ++++++++++++++++++++++++++++++++
fastlane/Fastfile | 32 +++++++++++++++++++++++++++-
fastlane/Pluginfile | 5 +++++
5 files changed, 76 insertions(+), 1 deletion(-)
create mode 100644 fastlane/Pluginfile
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index 1710328..7b6ecb1 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -6,6 +6,7 @@ on:
pull_request:
branches:
- main
+ - develop
jobs:
build-upload-testflight:
diff --git a/Gemfile b/Gemfile
index 7a118b4..cdd3a6b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,6 @@
source "https://rubygems.org"
gem "fastlane"
+
+plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
+eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/Gemfile.lock b/Gemfile.lock
index bb63134..6219db7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -37,9 +37,19 @@ GEM
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
+ discordrb (3.3.0)
+ discordrb-webhooks (~> 3.3.0)
+ ffi (>= 1.9.24)
+ opus-ruby
+ rbnacl (~> 3.4.0)
+ rest-client (>= 2.1.0.rc1)
+ websocket-client-simple (>= 0.3.0)
+ discordrb-webhooks (3.3.0)
+ rest-client (>= 2.1.0.rc1)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
+ event_emitter (0.2.6)
excon (0.112.0)
faraday (1.10.4)
faraday-em_http (~> 1.0)
@@ -112,8 +122,12 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
+ fastlane-plugin-discord_notifier (0.1.7)
+ discordrb (~> 3.3.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
+ ffi (1.17.2)
+ ffi (1.17.2-arm64-darwin)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
@@ -152,6 +166,7 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
+ http-accept (1.7.0)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.9.0)
@@ -161,6 +176,10 @@ GEM
jwt (2.10.1)
base64
logger (1.7.0)
+ mime-types (3.7.0)
+ logger
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
+ mime-types-data (3.2025.0624)
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
@@ -168,16 +187,26 @@ GEM
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.3.0)
+ netrc (0.11.0)
nkf (0.2.0)
optparse (0.6.0)
+ opus-ruby (1.0.1)
+ ffi
os (1.1.4)
plist (3.7.2)
public_suffix (6.0.2)
rake (13.3.0)
+ rbnacl (3.4.0)
+ ffi
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
+ rest-client (2.1.0)
+ http-accept (>= 1.7.0, < 2.0)
+ http-cookie (>= 1.0.2, < 2.0)
+ mime-types (>= 1.16, < 4.0)
+ netrc (~> 0.8)
retriable (3.1.2)
rexml (3.4.1)
rouge (3.28.0)
@@ -203,6 +232,12 @@ GEM
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.6.0)
+ websocket (1.2.11)
+ websocket-client-simple (0.9.0)
+ base64
+ event_emitter
+ mutex_m
+ websocket
word_wrap (1.0.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
@@ -222,6 +257,7 @@ PLATFORMS
DEPENDENCIES
fastlane
+ fastlane-plugin-discord_notifier
BUNDLED WITH
2.6.9
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index da94af5..7cf9059 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -97,11 +97,17 @@ platform :ios do
)
slack(
- message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค๋น ์์",
+ message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค๋น",
channel: "#run_mile",
slack_url: "#{ENV["SLACK_URL"]}"
)
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "Run Mile ๋ฐฐํฌ ์ค๋น",
+ description: "Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
+ )
+
setup_ci
match(type: "appstore")
@@ -112,6 +118,12 @@ platform :ios do
slack_url: "#{ENV["SLACK_URL"]}"
)
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "Run Mile ์ธ์ฆ์ ์๋ฃ",
+ description: "Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
+
build_app(
scheme: "Run Mile",
xcodebuild_formatter: "xcpretty"
@@ -123,6 +135,12 @@ platform :ios do
slack_url: "#{ENV["SLACK_URL"]}"
)
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "Run Mile ์์นด์ด๋ธ ์๋ฃ",
+ description: "Build Number: #{new_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
+
upload_to_testflight
slack(
@@ -130,6 +148,12 @@ platform :ios do
channel: "#run_mile",
slack_url: "#{ENV["SLACK_URL"]}"
)
+
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "Run Mile Testflight ๋ฐฐํฌ ์๋ฃ",
+ description: "Build Number: #{new_build_number} ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
end
error do |lane, exception, options|
@@ -139,5 +163,11 @@ platform :ios do
success: false,
slack_url: "#{ENV["SLACK_URL"]}"
)
+
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.",
+ description: "์๋ฌ ๋ฉ์์ง\n#{exception}"
+ )
end
end
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
new file mode 100644
index 0000000..7ef2b02
--- /dev/null
+++ b/fastlane/Pluginfile
@@ -0,0 +1,5 @@
+# Autogenerated by fastlane
+#
+# Ensure this file is checked in to source control!
+
+gem 'fastlane-plugin-discord_notifier'
From 05520021dbd7191b742e717a98b8f7dfac1761b7 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sat, 28 Jun 2025 14:55:30 +0900
Subject: [PATCH 40/54] =?UTF-8?q?[#44]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95(=ED=99=98=EA=B2=BD=20?=
=?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index 7b6ecb1..27d037a 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -56,6 +56,7 @@ jobs:
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
SLACK_URL: ${{ secrets.SLACK_URL }}
+ DISCORD_URL: ${{ secrets.DISCORD_URL }}
run: bundle exec fastlane testflight
- name: Upload Artifacts
From 94d7f918f17e5ce256c297715f7131b7f35896d9 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sat, 28 Jun 2025 15:23:15 +0900
Subject: [PATCH 41/54] =?UTF-8?q?[#44]=20CI/CD=20=EC=88=98=EC=A0=95(Slack?=
=?UTF-8?q?=20=EC=82=AD=EC=A0=9C,=20lane=EB=AA=85=20=EB=B3=80=EA=B2=BD,=20?=
=?UTF-8?q?=EA=B9=83=ED=97=88=EB=B8=8C=20=ED=8A=B8=EB=A6=AC=EA=B1=B0=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/testflight.yml | 4 +---
fastlane/Fastfile | 33 +-------------------------------
2 files changed, 2 insertions(+), 35 deletions(-)
diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml
index 27d037a..89129bb 100644
--- a/.github/workflows/testflight.yml
+++ b/.github/workflows/testflight.yml
@@ -6,7 +6,6 @@ on:
pull_request:
branches:
- main
- - develop
jobs:
build-upload-testflight:
@@ -55,9 +54,8 @@ jobs:
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
- SLACK_URL: ${{ secrets.SLACK_URL }}
DISCORD_URL: ${{ secrets.DISCORD_URL }}
- run: bundle exec fastlane testflight
+ run: bundle exec fastlane upload_testflight
- name: Upload Artifacts
uses: actions/upload-artifact@v4
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 7cf9059..c7d44f2 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -61,7 +61,7 @@ platform :ios do
desc "Push a new beta build to TestFlight"
- lane :testflight do
+ lane :upload_testflight do
app_store_connect_api_key(
key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}",
@@ -96,12 +96,6 @@ platform :ios do
build_number: new_build_number
)
- slack(
- message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค๋น",
- channel: "#run_mile",
- slack_url: "#{ENV["SLACK_URL"]}"
- )
-
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "Run Mile ๋ฐฐํฌ ์ค๋น",
@@ -112,12 +106,6 @@ platform :ios do
match(type: "appstore")
- slack(
- message: "Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น ์๋ฃ",
- channel: "#run_mile",
- slack_url: "#{ENV["SLACK_URL"]}"
- )
-
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "Run Mile ์ธ์ฆ์ ์๋ฃ",
@@ -129,12 +117,6 @@ platform :ios do
xcodebuild_formatter: "xcpretty"
)
- slack(
- message: "Build Number: #{new_build_number} ์์นด์ด๋ธ ์๋ฃ",
- channel: "#run_mile",
- slack_url: "#{ENV["SLACK_URL"]}"
- )
-
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "Run Mile ์์นด์ด๋ธ ์๋ฃ",
@@ -143,12 +125,6 @@ platform :ios do
upload_to_testflight
- slack(
- message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ฑ๊ณต!",
- channel: "#run_mile",
- slack_url: "#{ENV["SLACK_URL"]}"
- )
-
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "Run Mile Testflight ๋ฐฐํฌ ์๋ฃ",
@@ -157,13 +133,6 @@ platform :ios do
end
error do |lane, exception, options|
- slack(
- message: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. #{exception}",
- channel: "#run_mile",
- success: false,
- slack_url: "#{ENV["SLACK_URL"]}"
- )
-
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "Testflight ๋ฐฐํฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.",
From a719497c37eb871ce0022bfdc9be5aa2435c620a Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sat, 28 Jun 2025 23:55:06 +0900
Subject: [PATCH 42/54] =?UTF-8?q?[#47]=20Fastfile=20=EC=88=98=EC=A0=95(App?=
=?UTF-8?q?store=20=EB=B0=B0=ED=8F=AC=20=EB=A0=88=EC=9D=B8=20=EA=B5=AC?=
=?UTF-8?q?=ED=98=84)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 131 +++++++++++++++++++++++++++++++++-------------
1 file changed, 96 insertions(+), 35 deletions(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index c7d44f2..39ed4af 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -16,13 +16,17 @@
default_platform(:ios)
platform :ios do
- lane :local do
+
+ desc "Testflight ์๋ ๋ฐฐํฌ lane"
+ lane :upload_testflight do
+
app_store_connect_api_key(
- key_id: "",
- issuer_id: "",
- key_content: ""
+ key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}",
+ issuer_id: "#{ENV["APP_STORE_CONNECT_ISSUER_ID"]}",
+ key_content: "#{ENV["APP_STORE_CONNECT_KEY"]}",
+ is_key_content_base64: true
)
-
+
today = Time.now.strftime("%Y%m%d")
current_build_number = "#{latest_testflight_build_number}"
@@ -49,27 +53,110 @@ platform :ios do
build_number: new_build_number
)
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Testflight] Run Mile ๋ฐฐํฌ ์ค๋น",
+ description: "[Testflight] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
+ )
+
+ setup_ci
+
match(type: "appstore")
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Testflight] Run Mile ์ธ์ฆ์ ์๋ฃ",
+ description: "[Testflight] Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
+
build_app(
scheme: "Run Mile",
xcodebuild_formatter: "xcpretty"
)
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Testflight] Run Mile ์์นด์ด๋ธ ์๋ฃ",
+ description: "[Testflight] Build Number: #{new_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
+
upload_to_testflight
+
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Testflight] Run Mile Testflight ๋ฐฐํฌ ์๋ฃ",
+ description: "[Testflight] Build Number: #{new_build_number} ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
end
-
-
- desc "Push a new beta build to TestFlight"
- lane :upload_testflight do
+
+ desc "Appstore ์๋ ๋ฐฐํฌ lane"
+ lane :upload_appstore do
app_store_connect_api_key(
key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}",
issuer_id: "#{ENV["APP_STORE_CONNECT_ISSUER_ID"]}",
key_content: "#{ENV["APP_STORE_CONNECT_KEY"]}",
is_key_content_base64: true
)
+
+ current_build_number = "#{latest_testflight_build_number}"
+
+ increment_build_number(
+ xcodeproj: "Run Mile.xcodeproj",
+ build_number: current_build_number
+ )
+
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค๋น",
+ description: "[Appstore] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
+ )
+
+ setup_ci
+
+ match(type: "appstore")
+
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Appstore] Run Mile ์ธ์ฆ์ ์๋ฃ",
+ description: "[Appstore] Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
+
+ build_app(
+ scheme: "Run Mile",
+ xcodebuild_formatter: "xcpretty"
+ )
+
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Appstore] Run Mile ์์นด์ด๋ธ ์๋ฃ",
+ description: "[Appstore] Build Number: #{new_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
+
+ upload_to_app_store(
+ force: true,
+ submit_for_review: true,
+ skip_screenshots: true,
+ skip_metadata: true,
+ automatic_release: true
+ )
+
+ discord_notifier(
+ webhook_url: "#{ENV["DISCORD_URL"]}",
+ title: "[Appstore] Run Mile Appstore ๋ฐฐํฌ ์๋ฃ",
+ description: "[Appstore] Build Number: #{new_build_number} ์ฑ์คํ ์ด ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ )
+ end
+
+ desc "๋ก์ปฌ ํ
์คํธ์ฉ lane"
+ lane :local do
+ app_store_connect_api_key(
+ key_id: "",
+ issuer_id: "",
+ key_content: ""
+ )
+
today = Time.now.strftime("%Y%m%d")
current_build_number = "#{latest_testflight_build_number}"
@@ -96,40 +183,14 @@ platform :ios do
build_number: new_build_number
)
- discord_notifier(
- webhook_url: "#{ENV["DISCORD_URL"]}",
- title: "Run Mile ๋ฐฐํฌ ์ค๋น",
- description: "Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
- )
-
- setup_ci
-
match(type: "appstore")
- discord_notifier(
- webhook_url: "#{ENV["DISCORD_URL"]}",
- title: "Run Mile ์ธ์ฆ์ ์๋ฃ",
- description: "Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
- )
-
build_app(
scheme: "Run Mile",
xcodebuild_formatter: "xcpretty"
)
- discord_notifier(
- webhook_url: "#{ENV["DISCORD_URL"]}",
- title: "Run Mile ์์นด์ด๋ธ ์๋ฃ",
- description: "Build Number: #{new_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
- )
-
upload_to_testflight
-
- discord_notifier(
- webhook_url: "#{ENV["DISCORD_URL"]}",
- title: "Run Mile Testflight ๋ฐฐํฌ ์๋ฃ",
- description: "Build Number: #{new_build_number} ํ
์คํธ ํ๋ผ์ดํธ ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
- )
end
error do |lane, exception, options|
From b85934b56b1d0d2fe3c7073925f7cbad49ac4347 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 00:05:54 +0900
Subject: [PATCH 43/54] =?UTF-8?q?[#47]=20CI=EC=9A=A9=20=EB=A6=B4=EB=A6=AC?=
=?UTF-8?q?=EC=A6=88=20=EB=85=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ReleaseNote/v1.0.1.md | 4 ++++
Run Mile.xcodeproj/project.pbxproj | 6 ++++++
2 files changed, 10 insertions(+)
create mode 100644 ReleaseNote/v1.0.1.md
diff --git a/ReleaseNote/v1.0.1.md b/ReleaseNote/v1.0.1.md
new file mode 100644
index 0000000..c8fe435
--- /dev/null
+++ b/ReleaseNote/v1.0.1.md
@@ -0,0 +1,4 @@
+# What's new
+- ์ด๋ ๋ฑ๋ก ์๋์ ๋๋ ์ ๋ ๋ฐ๋ก ๋ฑ๋กํ ์ ์๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์ต๋๋ค.
+- ์๋์ด ์ฌ๋ฌ๋ฒ ์ค๋ ๋ฒ๊ทธ๋ฅผ ์์ ํ์ต๋๋ค.
+- ๊ทธ ์ธ ์์ ์ฌ๋ฌ๊ฐ์ง ๋ฒ๊ทธ๋ฅผ ์์ ํ์ต๋๋ค.
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj
index 9745616..d25d9f0 100644
--- a/Run Mile.xcodeproj/project.pbxproj
+++ b/Run Mile.xcodeproj/project.pbxproj
@@ -62,6 +62,11 @@
path = fastlane;
sourceTree = "";
};
+ 2CA9201C2E103B150049926D /* ReleaseNote */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = ReleaseNote;
+ sourceTree = "";
+ };
2CE46DF52DAE7780008C6FBC /* Run Mile */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = "Run Mile";
@@ -108,6 +113,7 @@
2CE46DEA2DAE7780008C6FBC = {
isa = PBXGroup;
children = (
+ 2CA9201C2E103B150049926D /* ReleaseNote */,
2C7635AF2E0051AD0005D988 /* fastlane */,
2C7635B02E0051AD0005D988 /* Gemfile */,
2C7635B12E0051AD0005D988 /* Gemfile.lock */,
From 5677845108dcb9e266330d3654e780a323699166 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 14:31:23 +0900
Subject: [PATCH 44/54] =?UTF-8?q?[#47]=20=EC=95=B1=EC=8A=A4=ED=86=A0?=
=?UTF-8?q?=EC=96=B4=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20=EC=9E=90=EB=8F=99?=
=?UTF-8?q?=ED=99=94=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/appstore.yml | 62 ++++++++++++++++++++++++++++++++++
fastlane/Fastfile | 19 ++++++++++-
2 files changed, 80 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/appstore.yml
diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml
new file mode 100644
index 0000000..3faa590
--- /dev/null
+++ b/.github/workflows/appstore.yml
@@ -0,0 +1,62 @@
+# Appstore ์๋ ๋ฐฐํฌ ์ํฌํ๋ก์ฐ
+
+
+on:
+ pull_request:
+ branches:
+ - release
+
+
+jobs:
+ upload-appstore:
+ runs-on: macos-15
+
+ steps:
+ - uses: actions/checkout@4
+
+ - name: Set up SSH
+ uses: shimataro/ssh-key-action@v2
+ with:
+ key: ${{ secrets.SSH_PRIVATE_KEY }}
+ known_hosts: ${{ secrets.KNOWN_HOSTS}}
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.1'
+
+ - name: Select Xcode version
+ run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer
+
+ - name: Check Xcode version
+ run: xcodebuild -version
+
+ - name: Install Bundler
+ run: gem install bundler
+
+ - name: Install Fastlane
+ run: brew install fastlane
+
+ - name: Check Fastlane
+ run: fastlane --version
+
+ - name: Install Dependencies
+ run: bundle install
+
+ - name: Upload to Appstore ๐
+ env:
+ APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY}}
+ APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
+ APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ DISCORD_URL: ${{ secrets.DISCORD_URL }}
+ run: bundle exec fastalne upload_appstore
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: ipa-and-dsym
+ path: |
+ ./Run\ Mile.ipa
+ ./Run\ Mile.app.dSYM.zip
+
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 39ed4af..af3568f 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -106,6 +106,14 @@ platform :ios do
build_number: current_build_number
)
+ current_marketing_version = get_version_number
+ current_release_note_path = "ReleaseNote/v#{current_marketing_version}.md"
+ current_release_note = "# Whats new"
+
+ if File.exists?(current_release_note_path)
+ current_release_note = File.read(current_release_note_path)
+ end
+
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค๋น",
@@ -138,7 +146,16 @@ platform :ios do
submit_for_review: true,
skip_screenshots: true,
skip_metadata: true,
- automatic_release: true
+ automatic_release: true,
+ precheck_include_in_app_purchases: false,
+ copyright: "ยฉ #{Time.now.year} Mooninbeom",
+ release_notes: current_release_note,
+ submission_information: {
+ add_id_info_uses_idfa: false,
+ export_compliance_encryption_updated: false,
+ export_compliance_uses_encryption: false,
+ content_rights_contains_third_party_content: false
+ }
)
discord_notifier(
From 3afb93162ce4b1de38e31fcd1bd5f274fab0e62f Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 15:00:41 +0900
Subject: [PATCH 45/54] =?UTF-8?q?[#51]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=951(=EC=98=A4=ED=83=80=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/appstore.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml
index 3faa590..2325a34 100644
--- a/.github/workflows/appstore.yml
+++ b/.github/workflows/appstore.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: macos-15
steps:
- - uses: actions/checkout@4
+ - uses: actions/checkout@v4
- name: Set up SSH
uses: shimataro/ssh-key-action@v2
From 439bddd77f3f5676ae407378714b4025c9e21540 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 15:03:15 +0900
Subject: [PATCH 46/54] =?UTF-8?q?[#51]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=952(=EC=98=A4=ED=83=80=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/appstore.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml
index 2325a34..37d924f 100644
--- a/.github/workflows/appstore.yml
+++ b/.github/workflows/appstore.yml
@@ -50,7 +50,7 @@ jobs:
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
DISCORD_URL: ${{ secrets.DISCORD_URL }}
- run: bundle exec fastalne upload_appstore
+ run: bundle exec fastlane upload_appstore
- name: Upload Artifacts
uses: actions/upload-artifact@v4
From 1ee2d5c1dd103722dc669a65956e1a00bfaf0dcb Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 15:05:48 +0900
Subject: [PATCH 47/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?=
=?UTF-8?q?=EB=B3=80=EC=88=98=20=EB=A6=AC=EB=84=A4=EC=9D=B4=EB=B0=8D)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index af3568f..84c552e 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -117,7 +117,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค๋น",
- description: "[Appstore] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
+ description: "[Appstore] Build Number: #{current_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
)
setup_ci
@@ -127,7 +127,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ์ธ์ฆ์ ์๋ฃ",
- description: "[Appstore] Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ description: "[Appstore] Build Number: #{current_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
)
build_app(
@@ -138,7 +138,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ์์นด์ด๋ธ ์๋ฃ",
- description: "[Appstore] Build Number: #{new_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ description: "[Appstore] Build Number: #{current_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
)
upload_to_app_store(
@@ -161,7 +161,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile Appstore ๋ฐฐํฌ ์๋ฃ",
- description: "[Appstore] Build Number: #{new_build_number} ์ฑ์คํ ์ด ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ description: "[Appstore] Build Number: #{current_build_number} ์ฑ์คํ ์ด ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
)
end
From ec5761a089f8b9ee96f9fb3940d4a00729cc0ccd Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 15:20:55 +0900
Subject: [PATCH 48/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(pri?=
=?UTF-8?q?nt=20=EC=B6=94=EA=B0=80)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 3 +++
1 file changed, 3 insertions(+)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 84c552e..2190c62 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -114,6 +114,9 @@ platform :ios do
current_release_note = File.read(current_release_note_path)
end
+ puts "Version: #{current_marketing_version}"
+ puts "Release Notes\n#{current_release_note}"
+
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค๋น",
From a1e263e595a5e01d91ca676ebea2ed075f7dbe21 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 16:25:29 +0900
Subject: [PATCH 49/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?=
=?UTF-8?q?=EB=A6=B4=EB=A6=AC=EC=A6=88=20=EB=85=B8=ED=8A=B8=20=ED=8C=A8?=
=?UTF-8?q?=EC=8A=A4=20=EB=B3=80=EA=B2=BD)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 2190c62..f83fa7f 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -107,7 +107,7 @@ platform :ios do
)
current_marketing_version = get_version_number
- current_release_note_path = "ReleaseNote/v#{current_marketing_version}.md"
+ current_release_note_path = "../ReleaseNote/v#{current_marketing_version}.md"
current_release_note = "# Whats new"
if File.exists?(current_release_note_path)
From f3656bb42ce501ecc99ee3873370f3377a7f2041 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 16:32:04 +0900
Subject: [PATCH 50/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(upl?=
=?UTF-8?q?oad=5Fto=5Fappstore=20=EC=95=84=EA=B7=9C=EB=A8=BC=ED=8A=B8=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index f83fa7f..f69a276 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -152,7 +152,9 @@ platform :ios do
automatic_release: true,
precheck_include_in_app_purchases: false,
copyright: "ยฉ #{Time.now.year} Mooninbeom",
- release_notes: current_release_note,
+ release_notes: {
+ "ko_KR" => current_release_note
+ },
submission_information: {
add_id_info_uses_idfa: false,
export_compliance_encryption_updated: false,
From 10b1fa93ea545b4f7c7f893b38c19b84db32b458 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 16:34:14 +0900
Subject: [PATCH 51/54] =?UTF-8?q?[#51]=20=EC=9B=8C=ED=81=AC=ED=94=8C?=
=?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95(=EC=9D=B4=EB=A6=84=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/appstore.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml
index 37d924f..67f619e 100644
--- a/.github/workflows/appstore.yml
+++ b/.github/workflows/appstore.yml
@@ -1,5 +1,5 @@
# Appstore ์๋ ๋ฐฐํฌ ์ํฌํ๋ก์ฐ
-
+name: ์ฑ์คํ ์ด ๋ฐฐํฌ ๐
on:
pull_request:
From 67776efab164cdc17d6e72b5aebdd44ab035300c Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 16:46:04 +0900
Subject: [PATCH 52/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?=
=?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EB=84=98=EB=B2=84=20=EB=B3=80=EA=B2=BD)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 29 ++++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index f69a276..049fae8 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -99,11 +99,30 @@ platform :ios do
is_key_content_base64: true
)
+ today = Time.now.strftime("%Y%m%d")
+
current_build_number = "#{latest_testflight_build_number}"
+
+ if current_build_number.length == 9
+ current_date = current_build_number[0,8] # ์ 8์๋ฆฌ๊ฐ ๋ ์ง
+ current_count = current_build_number[8].to_i # ๋ง์ง๋ง 1์๋ฆฌ๊ฐ ์ซ์
+ else
+ # ๋น๋ ๋๋ฒ๊ฐ ์๊ฑฐ๋ ํ์์ด ๋ค๋ฅด๋ฉด ์ด๊ธฐํ
+ current_date = ""
+ current_count = 0
+ end
+
+ if current_date == today
+ next_count = current_count + 1
+ else
+ next_count = 1
+ end
+
+ new_build_number = "#{today}#{next_count}"
increment_build_number(
xcodeproj: "Run Mile.xcodeproj",
- build_number: current_build_number
+ build_number: new_build_number
)
current_marketing_version = get_version_number
@@ -120,7 +139,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค๋น",
- description: "[Appstore] Build Number: #{current_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
+ description: "[Appstore] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์ํด ์ค๋น์ค์
๋๋ค."
)
setup_ci
@@ -130,7 +149,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ์ธ์ฆ์ ์๋ฃ",
- description: "[Appstore] Build Number: #{current_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ description: "[Appstore] Build Number: #{new_build_number} ์ธ์ฆ์ ์ค๋น๊ฐ ์๋ฃ๋์์ต๋๋ค."
)
build_app(
@@ -141,7 +160,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile ์์นด์ด๋ธ ์๋ฃ",
- description: "[Appstore] Build Number: #{current_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ description: "[Appstore] Build Number: #{new_build_number} ์์นด์ด๋ธ๊ฐ ์๋ฃ๋์์ต๋๋ค."
)
upload_to_app_store(
@@ -166,7 +185,7 @@ platform :ios do
discord_notifier(
webhook_url: "#{ENV["DISCORD_URL"]}",
title: "[Appstore] Run Mile Appstore ๋ฐฐํฌ ์๋ฃ",
- description: "[Appstore] Build Number: #{current_build_number} ์ฑ์คํ ์ด ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
+ description: "[Appstore] Build Number: #{new_build_number} ์ฑ์คํ ์ด ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ต๋๋ค."
)
end
From 0d0841a2ba11062b0348bd6df7ed18f2b767954e Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 21:09:48 +0900
Subject: [PATCH 53/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?=
=?UTF-8?q?=EC=96=B8=EC=96=B4=20=EC=B6=94=EA=B0=80)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 049fae8..5a4b8b1 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -164,6 +164,7 @@ platform :ios do
)
upload_to_app_store(
+ languages: ["ko"],
force: true,
submit_for_review: true,
skip_screenshots: true,
@@ -172,7 +173,7 @@ platform :ios do
precheck_include_in_app_purchases: false,
copyright: "ยฉ #{Time.now.year} Mooninbeom",
release_notes: {
- "ko_KR" => current_release_note
+ "ko" => current_release_note
},
submission_information: {
add_id_info_uses_idfa: false,
From b240f3f2fab3dfcaec225979211b24d4aed7a978 Mon Sep 17 00:00:00 2001
From: mooninbeom
Date: Sun, 29 Jun 2025 21:18:50 +0900
Subject: [PATCH 54/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(met?=
=?UTF-8?q?adata=20=3D>=20false)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
fastlane/Fastfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 5a4b8b1..ccdaa31 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -168,7 +168,7 @@ platform :ios do
force: true,
submit_for_review: true,
skip_screenshots: true,
- skip_metadata: true,
+ skip_metadata: false,
automatic_release: true,
precheck_include_in_app_purchases: false,
copyright: "ยฉ #{Time.now.year} Mooninbeom",