μ΄μ λ§μ κ°λ°μ
, push ν΄μΌλλλ° μμ΄λ²λ¦° λ§κ° κΈν κ°λ°μ λ±λ±μ μν 1μΌ 1μ»€λ° κ°μμ±
μ ν¬ CYC(Check your commit)λ ν¬κ² commit μλ¦Ό κΈ°λ₯, κ°λ¨ν todo listλ₯Ό κ°κ³ μμ΅λλ€.
κ°μΉμ° | κΉλͺ ν | μ΄λ―Όμ | ν©λ―Όμ± | ν©μ±μ§ |
---|---|---|---|---|
- κΉνλΈ OAuthλ₯Ό ν΅ν λ‘κ·ΈμΈ μ°λ
- OAuth AcessTokenμ λ°νμΌλ‘ μ μ μ 보λ₯Ό νμ©
- μ μ μ 보λ₯Ό custom ProgressView, GrassView λ± νμ©
- λͺ©νλ¬μ±μ λμμ£Όλ μ±λ¦°μ§ μ€μ
- MainViewμμ D-Dayλ₯Ό μ 곡ν¨μΌλ‘, λͺ©νλ₯Ό κ°μμ μΌλ‘ νμΈ
- μ€λ 컀λ°μ μν΄ ν μΌμ κΈ°λ‘νλ TodoList
- μλμ ν΅ν΄ μΌμ μκ°λ§λ€ μ»€λ° μ²΄ν¬
μ± νλ©΄ |
---|
λΌμ΄νΈ λͺ¨λ | λ€ν¬ λͺ¨λ |
---|---|
Step 1 νμλΌμΈ
- 23.12.5 ~ 23.12.6
- νλΉλ©
- μμ΄λμ΄ ν μ
- μμ΄λμ΄ κ΅¬ν λ°©μ ν μ
Step 2 νμλΌμΈ
- 23.12.06 ~ 23.12.07
- Figmaλ₯Ό κΈ°λ³Έ λμμΈ νλ‘ν νμ μ μ
- κ° κΈ°λ₯λ³ κ΅¬ν λ°©μ ν μ
- κ° ννΈλ³ μν λΆλ°°
- νλ‘μ νΈ κ°λ° μμ
- 23.12.12 ~ 23.12.13
- μ± μμ΄μ½ μ μ
Step 3 νμλΌμΈ
- 23.12.06
- κΈ°λ³Έ μ± κ΅¬μ‘° μ μ
- 컀μ€ν ν°νΈ, μ»¬λ¬ Aseet μ μ©
- 23.12.07 ~ 23.12.11
- κΉνλΈ OAuth λ‘κ·ΈμΈ κ΅¬ν
- OAuth λ°μ΄ν°λ₯Ό ν΅ν΄ μ μ μ 보 λ°μμ€λ λΆλΆ ꡬν
- 23.12.07 ~ 23.12.14
- μλ¦ΌκΈ°λ₯ ꡬν
- Todo List ꡬν
- 23.12.11 ~ 23.12.14
- κΉνλΈ APIλ₯Ό μ΄μ©ν GrassView ꡬν
- κΉνλΈ APIλ‘ λ°μμ¨ μ»€λ°μΌμλ‘ D-day κ³μ°κΈ° ꡬν
- 23.12.14
- λΌμ΄νΈ λͺ¨λ, λ€ν¬λͺ¨λ λ³ν λ²νΌ ꡬν
APIλ₯Ό ν΅ν΄ JSON μ μ λ°μ΄ν°κ° μ μμ μΌλ‘ λΆλ¬μμ§μ§ μμ
Git API
λ₯Ό ν΅ν΄ μ μ λ°μ΄ν°κ° JSON νμμΌλ‘ λΆλ¬μμ§μ§ μλ λ¬Έμ
func getUser() {
let accessToken = KeychainSwift().get("accessToken") ?? ""
let headers: HTTPHeaders = ["Accept": "application/vnd.github.v3+json",
"Authorization": "token \(accessToken)"]
AF.request(githubApiURL+ApiPath.USER.rawValue,
method: .get,
parameters: [:],
headers: headers).responseJSON(completionHandler: { (response) in
switch response.result {
case .success(let json):
print(json as! [String: Any])
case .failure:
print("")
}
})
}
- κΉνλΈ μ μ API 곡μλ¬Έμ ν΄λΉ λ¬Έμμ ννλ‘ curl μ μ¬μ©νλ©΄ μ μμ μΌλ‘ JSON ννμ λ°μ΄ν°κ° λ°μμ μ§λ κ²μ νμΈ
- APIλ₯Ό λ°μμ€λ κ³Όμ μμ responseJSON μ ννκ° μλλΌ responseString νΉμ responseDecodable μΌλ‘ μ¬μ©νλ©΄ μ μμ μΌλ‘ λ°μ΄ν°κ° λ°μμ μ§λ κ²μ νμΈ
- structλ₯Ό ν΅ν΄ Userλ₯Ό μ μΈνκ³ responseDecodable λ‘ ν΄λΉ λ°μ΄ν°λ₯Ό ν λΉμν€λ λ°©λ²μΌλ‘ νμ©
struct User: Decodable {
let login: String
let name: String
}
func getUser() {
let headers: HTTPHeaders = ["Accept": "application/vnd.github+json",
"Authorization": "Bearer \(access_token!)"]
AF.request("https://api.github.com/user",
method: .get, parameters: [:],
headers: headers).responseDecodable(of: User.self) { response in
switch response.result {
case .success(let user):
self.userLogin = user.login
self.userName = user.name
self.getCommitData()
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
}
- REST APIμ μ£Όμκ° λͺ ννμ§ νμΈνκΈ°μν΄ curlμ νμ©λ²μ μκ²λ¨.
GitHub contribution graphμ SwiftSoup μ¬μ©ν μ΄μ
let parsedHtml = try SwiftSoup.parse(htmlURL)
let dailyContribution = try parsedHtml.select("td")
let validCommits = dailyContribution.compactMap { element -> (String, String)? in
guard
let dateString = try? element.attr("data-date"),
let levelString = try? element.attr("data-level"),
!dateString.isEmpty
else { return nil }
return (dateString, levelString)
}
- Github profileμ μλ κΉν μλμ λν λ°μ΄ν°λ₯Ό apiλ‘ μ 곡ν΄μ£Όμ§ μμ
- commits historyλ§ μ 곡νμ§λ§ κ° repoλ³λ‘ historyλ‘ μ 곡νκ±°λ, user eventsλ‘ μ 체 commitμ 볡μ‘ν κ΅¬μ‘°λ‘ μ 곡
- νμ§λ§ μ μΌ μ€μν건 무μλ³΄λ€ apiμ μ λ°μ΄νΈκ° λλ €μ commitμ ν ν μ΅λ 8μκ° νμ λ°μ λ¨
- κ·Έλ¬λ―λ‘ κ°λ₯ν 빨리 λ°μλλ λ©μΈμ contribution graphλ₯Ό ν΅ν΄ λ°μμ€κΈ° μν΄ μΉ ν¬λ‘€λ§ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νμ¬ dataλ₯Ό λ°μ
UserDefaultsμ μ¬μ©
API
λ₯Ό νμ©νκΈ° μν΄μλ μ‘μΈμ€ν ν° κ°μ΄ μ λμ μΌλ‘ νμ, μ±μ μ’ λ£ μμΌλ ν΄λΉ κ°μ μ ν¨ν΄μΌ λ¨- AppStorageλ₯Ό μ¬μ©νλ € νμ§λ§ λ€λ₯Έ λ·°μμλ μ¬μ©νκ³ μ°Έμ‘°ν΄μΌ λκΈ° λλ¬Έμ μ¬μ©μ΄ μ΄λ €μ
class LoginModel: ObservableObject {
static let shared = LoginModel()
@Published var code: String?
@Published var access_token: String?
@Published var userLogin: String?
- UserDefaults λ‘ ν΄λΉ λ³μλ€μ μ μΈνκ³ extensionμ ν΅ν΄ set, get λΆλΆμ μ μ©
- init() λΆλΆμ ν΅ν΄ μ μΈλ λ³μλ₯Ό μ΄κΈ°ν
@Published var access_token: String? {
didSet {
UserDefaults.standard.setAccessToken(access_token ?? "")
}
}
@Published var userName: String? {
didSet {
UserDefaults.standard.setUserName(userName ?? "")
}
}
@Published var userLogin: String? {
didSet {
UserDefaults.standard.setUserLogin(userLogin ?? "")
}
}
var results: [(String, String)] = []
@Published var testCase:[String:Int] = [:]
// UserDefaultsλ‘ μ μΈλ λ³μλ₯Ό μ¬μ©νκΈ° μν init λΆλΆ
init() {
self.userLogin = UserDefaults.standard.getUserLogin()
self.access_token = UserDefaults.standard.getAccessToken()
self.userName = UserDefaults.standard.getUserName()
}
// UserDefaultsμ extension λΆλΆ
extension UserDefaults {
private static let userLoginKey = "userLoginKey"
func setUserLogin(_ login: String) {
set(login, forKey: UserDefaults.userLoginKey)
}
func getUserLogin() -> String? {
return string(forKey: UserDefaults.userLoginKey)
}
}
extension UserDefaults {
private static let userAcessToken = "acessToken"
func setAccessToken(_ token: String) {
set(token, forKey: UserDefaults.userAcessToken)
}
func getAccessToken() -> String? {
return string(forKey: UserDefaults.userAcessToken)
}
}
extension UserDefaults {
private static let userNickname = "userNickname"
func setUserName(_ name: String) {
set(name, forKey: UserDefaults.userNickname)
}
func getUserName() -> String? {
return string(forKey: UserDefaults.userNickname)
}
}
FCMμμ userNotificationsλ‘ μ νν μ΄μ
μ²μ ꡬννκ³ μ νλ κΈ°λ₯μ μμλ λ€μκ³Ό κ°μλ€.
APNs
μ λλ°μ΄μ€ν ν°
μ μμ²APNs
μμ λ°μ λλ°μ΄μ€ν ν°
μPush server
μ λκΉAPNs
μ νΈμ¬μλ¦Όμ λ³΄λΌ λ°μ΄ν°λ₯Ό μ λ¬APNs
μ μλ λ°μ΄ν°λ₯Ό λ°μμ μ μ μ ν°μμ μλ¦Ό μ λ¬
import SwiftUI
import FirebaseCore
import FirebaseMessaging
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
// μ격 μλ¦Ό λ±λ‘
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
// Firebase κ° νΈμ λ©μμ§λ₯Ό λμ μ μ‘ν μ μλλ‘ λ리μλ₯Ό μ€μ νλ κ³Όμ (MessagingDelegate)
Messaging.messaging().delegate = self
// νΈμ ν¬κ·ΈλΌμ΄λ μ€μ
UNUserNotificationCenter.current().delegate = self
return true
//Messagingμ λ±λ‘λ ν ν°μ messaging:didReceiveRegistrationToken νλ‘ν μ½ λ©μλλ₯Ό 1ν νΈμΆν¨ - μλ‘ λ±λ‘λ ν ν°μ΄λΌλ©΄ μ ν리μΌμ΄μ
μλ²λ‘ μ μ‘/ μλλΌλ©΄ λ±λ‘λ ν ν°μ ꡬλ
μ²λ¦¬ν΄μ€
}
// fcm ν ν°μ΄ λ±λ‘ λμμ λ
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
}
@main
struct CYCApp: App {
struct YourApp: App {
// register app delegate for Firebase setup
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
AboutCYC()
}
}
}
extension AppDelegate : MessagingDelegate {
// fcm λ±λ‘ ν ν°μ λ°μμ λ
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("Firebase registration token: \(String(describing: fcmToken))")
let dataDict: [String: String] = ["token": fcmToken ?? ""]
NotificationCenter.default.post(
name: Notification.Name("FCMToken"),
object: nil,
userInfo: dataDict
)
}
}
extension AppDelegate : UNUserNotificationCenterDelegate {
// νΈμλ©μΈμ§κ° μ±μ΄ μΌμ Έ μμλ λμ¬λ
// completionHandlerλ‘ "UNNotificationPresentationOptions"λ₯Ό λ°νν¨
// μ¬μ©μκ° λ¨Έλ¬΄λ₯΄κ³ μλ νλ©΄μ λ°λΌ ν¬κ·ΈλΌμ΄λ μνμμμ νΈμλ₯Ό 보μ¬μ€μ§ μλμ§μ λν λΆκΈ°μ²λ¦¬κ° κ°λ₯(ex.μΉ΄ν‘μ±ν
λ°©μμ νΈμλ₯Ό λμ°μ§ μλ λ±)
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
print("willPresent: userInfo: ", userInfo)
completionHandler([.banner, .sound, .badge])
// Notification λΆκΈ°μ²λ¦¬
if userInfo[AnyHashable("Check Your Commit")] as? String == "project" {
print("CYC project")
}else {
print("NOTHING")
}
}
// νΈμλ©μΈμ§λ₯Ό λ°μμ λ
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
print("didReceive: userInfo: ", userInfo)
completionHandler()
}
}
μ μ½λλ‘ ν ν°μ λ°μ μλμΌλ‘ Firebase messiging μλ²μ μ§μ λ±λ‘νκ³ μ±μ μλ¦Όμ λ°λλ°μ μ±κ³΅νλ€.νμ§λ§ λ¬Έμ λ λ€μ μ μ μ ν ν°μ μ΄λ»κ² λ°μμ λ©μμ§ μλ²μ μ¬λ €μ£Όλλμλ€. μλ²μμ΄ FCMλ§ μ¬μ©νμ¬ λ€μ λ 쑰건μ λμμ λ§μ‘±νλ μ μ μκ²λ§ μλ¦Όμ μ€ μ μλ λ°©λ²μ μκ°νμ¬μΌ νλ€.
- μ¬μ©μκ° μΌμ μκ°μ 컀λ°νμλκ°
- μ¬μ©μκ° μλ¦Ό μ€μ ν κΈμ on νμλκ°
μ¬μ©μμ μ 보λ₯Ό μλ²κ° μ μ₯νκ³ μμ΄μΌ μ λ 쑰건μ λ§μ‘±νλ κΈ°λ₯μ ꡬνν μ μλ€κ³ κ²°λ‘ μ λ΄λ Έκ³ , μ΄λ² κ°λ° κΈ°κ°μλ μ¬μ©μκ° μλ¦Ό μ€μ ν κΈμ on νμμ λ
7μ μ΄ν 맀 μκ°λ§λ€ μλ¦Όμ μ£Όλ κΈ°λ₯λ§μ ꡬννκΈ°λ‘ νμλ€. μ΄ κΈ°λ₯μ ꡬννλλ°μ FCMμ κ΅³μ΄ μ¬μ©νμ§ μκ³ λ΄λΆ λΌμ΄λΈλ¬λ¦¬μΈ userNotifications μ μ¬μ©νμλ€.
AppDelegate.swift
import SwiftUI
import UserNotifications
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// μ± μ€ν μ μ¬μ©μμκ² μλ¦Ό νμ© κΆνμ λ°μ
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // νμν μλ¦Ό κΆνμ μ€μ
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
return true
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
// Foreground(μ± μΌμ§ μν)μμλ μλ¦Ό μ€λ μ€μ
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.list, .banner])
}
}
μ±λΈλ¦¬κ²μ΄νΈμμ μλ¦ΌκΆνμ μ€μ ν΄μ£Όμλ€.
NotificationHelper.swift
import Foundation
import UIKit
import UserNotifications
//
// - Note: μ±κΈν΄μΌλ‘ ꡬν `LocalNotificationHelper.shared`λ₯Ό ν΅ν΄ μ κ·Ό
class LocalNotificationHelper {
static let shared = LocalNotificationHelper()
private init() {}
///Push Notificationμ λν μΈμ¦ μ€μ ν¨μ
func setAuthorization() {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // νμν μλ¦Ό κΆνμ μ€μ
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
}
// ν루λ₯Ό μ£ΌκΈ°λ‘ νΉμ μκ°μ Notificationμ 보λ΄λ μ½λ
func pushScheduledNotification(title: String, body: String, hour: Int, identifier: String) {
assert(hour >= 0 || hour <= 24, "μκ°μ 0μ΄μ 24μ΄νλ‘ μ
λ ₯ν΄μ£ΌμΈμ.")
let notificationContent = UNMutableNotificationContent()
notificationContent.title = title
notificationContent.body = body
var dateComponents = DateComponents()
dateComponents.hour = hour // μλ¦Όμ λ³΄λΌ μκ° (24μκ° νμ)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: identifier,
content: notificationContent,
trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Notification Error: ", error)
}
}
}
/// λκΈ°μ€μΈ Push Notificationμ μΆλ ₯
func printPendingNotification() {
UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
for request in requests {
print("Identifier: \(request.identifier)")
print("Title: \(request.content.title)")
print("Body: \(request.content.body)")
print("Trigger: \(String(describing: request.trigger))")
print("---")
}
}
}
//μλ¦Ό μ 체μμ
func removeAllNotifications() {
UNUserNotificationCenter
.current().removeAllDeliveredNotifications()
UNUserNotificationCenter
.current().removeAllPendingNotificationRequests()
}
}
NotificationHelper ν΄λμ€μμ μλ¦Όμ νμν ν¨μλ₯Ό ꡬννμλ€.
NotificationView
class NotificationSettings: ObservableObject {
@Published var isOnNotification: Bool {
didSet {
UserDefaults.standard.set(isOnNotification, forKey: "isOnNotification")
}
}
init() {
self.isOnNotification = UserDefaults.standard.bool(forKey: "isOnNotification")
}
}
.
.
VStack(alignment: .leading) {
Toggle(isOn: $isOnNotification, label: {
// MARK: - μλ¦Ό μ€μ ν κΈ
Text("μλ¦Ό μ€μ ")
.font(.pretendardBold_25)
}).onChange(of: isOnNotification, initial: false, techNotification)
.
.
func techNotification() {
if isOnNotification {
LocalNotificationHelper.shared.printPendingNotification()
LocalNotificationHelper
.shared
.pushScheduledNotification(title: "Check Your Commit",
body: "컀λ°ν΄μ€μ¬..π«Ά",
hour: 18,
identifier: "SCHEDULED_NOTI18")
} else if {
LocalNotificationHelper.shared.removeAllNotifications()
}
}
.
.
μλ¦Ό μ€μ λ·°μμ ν κΈκ°μ΄ onμΌ λ μλ¦Όμ΄ μλ¦ΌμΌν°μ μ¬λΌκ°λλ‘ κ΅¬ννκ³ , off μμ μλ¦ΌμΌν°μ μλ¦Όμ λͺ¨λ μμ νλλ‘ κ΅¬ννμλ€.
OnTapGestureμ¬μ©
- TodoList μ¬μ© μ λΉ νλ©΄ ν°μΉ νμλ, ν μ€νΈνλλ₯Ό μμ±νλ €νμ§λ§ 리μ€νΈ μ€μμ΄ν μμ ν λλ ν μ€νΈνλκ° μμ±λ¨.
@State var isTextFieldShown = false
.onTapGesture {
if !isTextFieldShown {
isTextFieldShown.toggle()
}
}
- TodoList μ¬μ© μ ν μ€νΈνλμ ν μ€νΈλ₯Ό μ λ ₯νκ³ λΉ νλ©΄μ ν°μΉνλ©΄ ν μ€νΈ μ μ₯μ ꡬννλ € νμ§λ§, 리μ€νΈ μ€μμ΄ν μμ ν λλ ν¨μκ° μλ.
func addTodo() {
withAnimation {
let newTodo = TodoModel(title: textFieldText)
if !newTodo.title.isEmpty {
modelContext.insert(newTodo)
isTextFieldShown.toggle()
}
}
}
.onTapGesture {
withAnimation{
addTodo() // ν
μ€νΈ μΆκ° ν¨μ
textFieldText = "" // μΆκ° ν ν
μ€νΈνλ λΉμμ£ΌκΈ°
}
}
DispatchQueue.main.asyncλ‘ UIView μλ ν₯μ
// μ€λΉλλ©΄ λ°λ‘ μ°μμΌμ λΏλ¦¬κΈ°, 곡룑 μμ§μ΄κΈ° -> MainViewμμ λ°λ‘ μ²λ¦¬
DispatchQueue.main.async {
if self.dataToDictionary(validCommits){
self.commitDay = self.findConsecutiveDates(withData: self.testCase)
ModalView().moveDinosaur() // νλ‘κ·Έλμ€λ°μ κ³΅λ£‘μ΄ μμ§μ΄λ ν¨μ
}
}
- onAppearμ UIViewμ μ λ°μ΄νΈ ν¨μλ₯Ό λ£μμ§λ§, μ»€λ° μ°μ μΌμμ νλ‘κ·Έλμ€λ°κ° λ€λ₯Έ λ·°μ λ€μ΄κ°λ€κ° λμμΌμ§λ§ μ λλ‘ λμ€λ λ¬Έμ κ° μμμ
- Alamofireλ‘ api μμ² ν¨μλ μλμΌλ‘ λΉλκΈ° μ²λ¦¬λλ―λ‘ main threadμμ λ°μ΄ν°λ₯Ό κ°μ Έμ€μ§ μμκ³
- UIViewκ° onAppearλλ μμ κ³Ό λ°μ΄ν°κ° λ€μ΄μ€λ μμ μ°¨μ΄κ° μκΈ°λ©΄μ λ€λ₯Έ λ·°μ λ€μ΄κ°λ€κ° UIviewλ₯Ό λ€μ νμν λ μ λλ‘ μκΈ°λ κ²μ΄ λ°κ²¬λ¨
- UIViewλ₯Ό μ λ°μ΄νΈνλ λ°μ΄ν° ν¨μλ DispatchQueue.main.asyncλ‘ λΉΌμ μ¬μ©ν΄μ£Όκ³ await μ¬μ©μ΄ λ―Έμν΄ ifλ¬ΈμΌλ‘ λ°μ΄ν°κ° λ€μ΄μλμ§ νλ³ν¨
SwiftUI
Xcode 15.1
iOS 17.1
Language - Swift 5.5.3
μλ - UserNotification
API - Alamofire
Todo - SwiftData
GrassView - SwiftSoup
π¦CYC
β£ π Main
β β π MainView.swift
β£ π Login
β β β£ π extension
β β β π extensionOfUserDefaults.ttf
β β£ π OnboardingTabView.swift
β β£ π LoginView.swift
β β π LoginModel.swift
β β β£ π Font
β£ π Setting
β β£ π PersonProfile
β β β£ π View
β β β β£ π PersonGridView.swift
β β β β π AboutCYC.swift
β β β£ π Model
β β β β π PersonModel.swift
β β£ π ViewModel
β β β£ π LicenseViewModel.swift
β β β π SettingViewModel.swift
β β£ π View
β β β£ π LicenseView.swift
β β β£ π NotificationView.swift
β β β π SettingView.swift
β β£ π Model
β β β£ π LicenseModel.swift
β β β π SettingModel.swift
β£ π Grass
β β£ π View
β β β π CommitView.swift
β£ π Todo
β β£ π View
β β β£ π TodoView.swift
β β β π TodoPreView.swift
β β£ π Model
β β β π TodoModel.swift
β£ π Progress
β β£ π View
β β β£ π ProgressView.swift
β β β£ π ModalView.swift
β β β£ π ProgressBarView.swift
β β β£ π DdayButtonView.swift
β β β π ProgressTextView.swift
β£ π Helper
β β£ π NotificationHelper
β β β π LocalNotificationHelper.swift
β β£ π DarkLightMode
β β β£ π DLMode.swift
β β β π UIButton.swift
β β£ π Extensions
β β β£ π fontExtension.swift
β β β£ π CustomSpacing.swift
β β β£ π colorExtension.swift
β β β π DismissGesture.swift
β β£ π Fonts
β β β£ π Pretendard-Black.otf
β β β£ π Pretendard-Bold.otf
β β β£ π Pretendard-ExtraBold.otf
β β β£ π Pretendard-ExtraLight.otf
β β β£ π Pretendard-Light.otf
β β β£ π Pretendard-Medium.otf
β β β£ π Pretendard-Regular.otf
β β β£ π Pretendard-SemiBold.otf
β β β π Pretendard-Thin.otf.swift
β£ π CYCAPP.swift
β£ π AppDelegate.swift
β£ π StartView.swift
β πΌοΈ Assets