{"id":18696122,"url":"https://github.com/jacobkosmart/creditcard-ios-practice","last_synced_at":"2026-05-17T00:35:03.898Z","repository":{"id":140609132,"uuid":"442133447","full_name":"jacobkosmart/creditCard-iOS-practice","owner":"jacobkosmart","description":"To practice firebase realtime DB and firestroe CRUD ","archived":false,"fork":false,"pushed_at":"2021-12-27T10:57:59.000Z","size":29,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-28T03:27:52.968Z","etag":null,"topics":["firebase","firebase-realtime-database","firestore-database","kingfisher","lottie"],"latest_commit_sha":null,"homepage":"https://jacobko.info/firebaseios/ios-firebase-02/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jacobkosmart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-27T10:57:20.000Z","updated_at":"2021-12-27T11:00:59.000Z","dependencies_parsed_at":"2023-04-03T20:04:05.084Z","dependency_job_id":null,"html_url":"https://github.com/jacobkosmart/creditCard-iOS-practice","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacobkosmart%2FcreditCard-iOS-practice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacobkosmart%2FcreditCard-iOS-practice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacobkosmart%2FcreditCard-iOS-practice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacobkosmart%2FcreditCard-iOS-practice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jacobkosmart","download_url":"https://codeload.github.com/jacobkosmart/creditCard-iOS-practice/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239558911,"owners_count":19658927,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["firebase","firebase-realtime-database","firestore-database","kingfisher","lottie"],"created_at":"2024-11-07T11:17:16.077Z","updated_at":"2025-10-03T17:32:00.204Z","avatar_url":"https://github.com/jacobkosmart.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 💳 creditCard-iOS-practice\n\n\u003cimg width=\"300\" alt=\"스크린샷\" src=\"https://user-images.githubusercontent.com/28912774/147460907-89c96757-b962-47c1-9964-98e8f7a946ff.gif\"\u003e\n\n## 📌 기능 상세\n\n- Firebase realtime, fireStore DB 기능을 사용하여 카드추천 페이지 를 만듭니다\n\n- Firebase 에서 데이터를 주고 받는 과정을 연습합니다\n\n## 👉 Pod library\n\n### 🔷 Kingfisher library\n\n- 이미지 서버에서 이미지를 가져다가 UI에 가져다가 표시 해줄때 사용되는 이미지 처리 오픈 라이브러리 입니다\n\n\u003e Kingfisher cheatSheet - https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet\n\n#### 설치\n\n`pod init`\n\n```ruby\n  # Pods for 09_creditCardList\n  pod 'Kingfisher'\nend\n```\n\n`pod install`\n\n#### 사용법\n\n```swift\n//  CardListViewController.swift\n\n// URL 타입으로 타입변환함\nlet imageURL = URL(string: creditCardList[indexPath.row].cardImageURL)\n// Kingfisher 를 사용해서 UI에 image 표시\ncell.cardImageView.kf.setImage(with: imageURL)\n```\n\n### 🔷 Lottie library\n\n\u003e Lottie-ios Github - https://github.com/airbnb/lottie-ios\n\n- Lottie 는 기본적으로 백터 기반 애니메미션과 아트를 실시간으로 랜더링하는 Airbnb 에서 개발한 오픈 소스 애니메이션 라이브러리 입니다\n\n- Lottie 를 `bodymovin JSON` 형식으로 보낸 에미메이션을 지원합니다\n\n#### 설치\n\n```ruby\n  # Pods for 09_creditCardList\n  pod 'lottie-ios'\nend\n```\n\n`pod install`\n\n### 사용법\n\n- storyBoard 에서 에니메이션이 보여 지는 곳에 view object 를 지정하고, class 설정을 AnimationView 으로 지정합니다\n\n![image](https://user-images.githubusercontent.com/28912774/147317043-688d5db7-d22e-4975-8974-161107f156fc.png)\n\n```swift\n// in CardDetailViewController.swift\n\noverride func viewDidLoad() {\n  super.viewDidLoad()\n\n  // lottie animation 선언 (name 은 lottie 로 불러올 json 파일 이름으로)\n  let animationView = AnimationView(name: \"card\")\n  lottieView.contentMode = .scaleAspectFit // 이미지 container 사이즈에 맞추기\n  lottieView.addSubview(animationView)\n  animationView.frame = lottieView.bounds\n  animationView.loopMode = .loop // animation 이 계속 반복\n  animationView.play() // animation 시작\n}\n```\n\n![Kapture 2021-12-24 at 14 13 33](https://user-images.githubusercontent.com/28912774/147319304-d31febc0-2673-4d81-a551-2f9a80fcfa5c.gif)\n\n## 🔑 Check Point !\n\n### 🔷 UI Structure\n\n![image](https://user-images.githubusercontent.com/28912774/147461053-3d7bfc13-6428-470a-b744-82f94bcc00a7.png)\n\n- CardListCell 은 `.xib` 파일로 작성\n\n![image](https://user-images.githubusercontent.com/28912774/147461212-fbd2f93f-b6b1-4799-88a7-bd5a6931b76d.png)\n\n![image](https://user-images.githubusercontent.com/28912774/147463794-da169f0a-0c8d-44c0-8cb1-e47c0d9c8a7f.png)\n\n### 🔷 Model\n\n```swift\nimport Foundation\n\nstruct CreditCard: Codable {\n\tlet id: Int\n\tlet rank: Int\n\tlet name: String\n\tlet cardImageURL: String\n\tlet promotionDetail: PromotionDetail\n\tlet isSelected: Bool? // 사용자가 카드를 선택 했을 때, 생성이 됨 그전에는 nil 이니까 optional 설정\n}\n\nstruct PromotionDetail: Codable {\n\tlet companyName: String\n\tlet amount: Int\n\tlet period: String\n\tlet benefitDate: String\n\tlet benefitDetail: String\n\tlet benefitCondition: String\n\tlet condition: String\n}\n\n```\n\n### 🔷 Firebase Firestore\n\n#### Firebase Firestore 설치\n\n\u003e Get started with Cloud Firestore - https://firebase.google.com/docs/firestore/quickstart#ios+\n\n```ruby\n  # Pods for 09_creditCardList\n  pod 'Firebase/Firestore'\n  pod 'FirebaseFirestoreSwift'\n```\n\n`pod install`\n\n#### Firebase firestore 에 데이터 입력\n\n- firestore 에는 json 파일을 web console 을 통해서 한번에 바로 입력하는 기능이 없기 때문에 code swift 에서 dummy data 를 import 하는 과정을 거져야 합니다.\n\n```swift\n// in CreditCardDummy.swift\n\nimport Foundation\n\nstruct CreditCardDummy {\n    static let card0 = CreditCard(id: 0, rank: 1, name: \"신한카드\", cardImageURL: \"https://www.shinhancard.com/_ICSFiles/afieldfile/2019/04/26/190426_pc_mrlife_cardplate600x380.png\", promotionDetail: PromotionDetail(companyName: \"신한\", period: \"2023.01.07(목)~2023.01.31(토)\", amount: 13, condition: \"온라인 채널을 통해 이벤트 카드를 보유하고, 혜택조건을 충족하신 분\", benefitCondition: \"이벤트 카드로 결제한 금액이 합해서 10만원이상 결제\", benefitDetail: \"현금 10만원\", benefitDate: \"2023.03.01(월)이후\"), isSelected: nil)\n    static let card1 = CreditCard(id: 1\n\t\t......\n```\n\n```swift\n// in AppDelegate.swift\n\nimport FirebaseFirestoreSwift\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\tfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n\n\t\t// firebase init\n\t\tFirebaseApp.configure()\n\n\t\t// firebase db 선언\n\t\tlet db = Firestore.firestore()\n\t\t// collection 에서 creditCardList 를 찾고, snapshot 과 error 를 불러옴(해당 db에 데이터가 없을 경우에 한번에 데이터를 넣어 주는 경우 사용)\n\t\tdb.collection(\"creditCardList\").getDocuments { snapshot, _ in\n\t\t\tguard snapshot?.isEmpty == true else { return } // snapshot 으로 db가 비어 있는 상태에서만 ture 로 설정\n\t\t\tlet batch = db.batch()\n\n\t\t\tlet card0Ref = db.collection(\"creditCardList\").document(\"card0\")\n\t\t\tlet card1Ref = db.collection(\"creditCardList\").document(\"card1\")\n\t\t\tlet card2Ref = db.collection(\"creditCardList\").document(\"card2\")\n\t\t\tlet card3Ref = db.collection(\"creditCardList\").document(\"card3\")\n\t\t\tlet card4Ref = db.collection(\"creditCardList\").document(\"card4\")\n\t\t\tlet card5Ref = db.collection(\"creditCardList\").document(\"card5\")\n\t\t\tlet card6Ref = db.collection(\"creditCardList\").document(\"card6\")\n\t\t\tlet card7Ref = db.collection(\"creditCardList\").document(\"card7\")\n\t\t\tlet card8Ref = db.collection(\"creditCardList\").document(\"card8\")\n\t\t\tlet card9Ref = db.collection(\"creditCardList\").document(\"card9\")\n\n\t\t\tdo {\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card0, forDocument: card0Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card1, forDocument: card1Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card2, forDocument: card2Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card3, forDocument: card3Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card4, forDocument: card4Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card5, forDocument: card5Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card6, forDocument: card6Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card7, forDocument: card7Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card8, forDocument: card8Ref)\n\t\t\t\ttry batch.setData(from: CreditCardDummy.card9, forDocument: card9Ref)\n\t\t\t} catch let error {\n\t\t\t\tprint(\"ERROR: wirting card to Firestore \\(error.localizedDescription)\")\n\t\t\t}\n\t\t\t// batch 에 commit 을 해주어야지 data 가 추가가 됨\n\t\t\tbatch.commit()\n\t\t}\n\t\treturn true\n\t}\n```\n\n- app build 후에 firestore 에서 data 가 import 된 것을 확인 할 수 있습니다\n\n![image](https://user-images.githubusercontent.com/28912774/147425834-4416f895-039b-4a4a-a6e1-0c176e1cbecd.png)\n\n##### firebase Firestore 읽기\n\n```swift\nimport UIKit\nimport Kingfisher\nimport FirebaseFirestore\n\n\n\n// UITableViewController 는 UITableView 에 필요한 delegate source 를 기본 연결된 상태로 제공하기 때문에 별도로 delegate 선언을 하지 않아도 됨\n// 또, rootView 로 UItableView 를 가지게 됩니다\nclass CardListViewController: UITableViewController {\n\n\t// DB 선언\n\tvar db = Firestore.firestore()\n\n\t// MARK: Variable\n\tvar creditCardList: [CreditCard] = []\n\n\t// MARK: LifeCycle\n\toverride func viewDidLoad() {\n\t\tsuper.viewDidLoad()\n\n\t\t// UITabelView Cell Register\n\t\tlet nibName = UINib(nibName: \"CardListCell\", bundle: nil)\n\t\ttableView.register(nibName, forCellReuseIdentifier: \"CardListCell\")\n\n\t\t// firestore 읽기 code 추가\n\t\tdb.collection(\"creditCardList\").addSnapshotListener { snapshot , error in\n\t\t\tguard let documents = snapshot?.documents else {\n\t\t\t\t// 값이 없을 경우에 error 처리\n\t\t\t\tprint(\"ERROR Firesotre fetching document \\(String(describing: error))\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// 데이터 처리 : compactMap 을 사용하는 것을 nil 값을 배열 안에 넣지 않게 하지 위해서\n\t\t\tself.creditCardList = documents.compactMap { doc -\u003e CreditCard? in\n\t\t\t\tdo {\n\t\t\t\t\tlet jsonData = try JSONSerialization.data(withJSONObject: doc.data(), options: [])\n\t\t\t\t\tlet creditCard = try JSONDecoder().decode(CreditCard.self, from: jsonData)\n\t\t\t\t\treturn creditCard\n\t\t\t\t} catch let error {\n\t\t\t\t\tprint(\"ERROR JSON Parsing \\(error)\")\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}.sorted { $0.rank \u003c $1.rank }\n\n\t\t\t// main tread 에서 돌아가는 tableView reload\n\t\t\tDispatchQueue.main.async {\n\t\t\t\tself.tableView.reloadData()\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n\u003cimg width=\"300\" alt=\"스크린샷\" src=\"https://user-images.githubusercontent.com/28912774/147428407-2131082e-a8a0-444e-801a-07d9c945e793.png\"\u003e\n\n##### firebase Firestore 쓰기\n\n- struct model 에 있는 `isSelected: Bool?` 을 갑을 통해 선택되면 select 가 되게끔 firestore 에 값 입력하기 입니다\n\n- 파일의 경로를 알때와 모를때 두가지 경우에 수에 따라 code 방식이 다름\n\n```swift\n// in CardListViewController.swift\n\n// didSelectRowAt: cell 을 선택 했을때, CardDetailViewController 로 넘어가는 action\noverride func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n\t// 상세화면 전달\n\tlet storyboard = UIStoryboard(name: \"Main\", bundle: Bundle.main)\n\tguard let detailViewController = storyboard.instantiateViewController(withIdentifier: \"CardDetailViewController\") as? CardDetailViewController else { return }\n\tdetailViewController.promotionDetail = creditCardList[indexPath.row].promotionDetail\n\tself.show(detailViewController, sender: nil)\n\n\t// Firestore 데이터 쓰기\n\t// option1 : 경로를 알고 있을 경우\n\tlet cardID = creditCardList[indexPath.row].id\n\tdb.collection(\"creditCardList\").document(\"card\\(cardID)\").updateData([\"isSelected\": true])\n\n\t// option2: 경로를 모르고 있을 경우\n\t// id 값을 검색한다음에 그 결과로 찾은 문서에 업데이트 해줘야함\n\tdb.collection(\"creditCardList\").whereField(\"id\", isEqualTo: cardID).getDocuments { snapshot, _ in\n\t\tguard let document = snapshot?.documents.first else {\n\t\t\t// error 처리\n\t\t\tprint(\"ERROR Firestore fetching document\")\n\t\t\treturn\n\t\t}\n\t\t// cardID 가 있다면\n\t\tdocument.reference.updateData([\"isSelected\": true])\n\t}\n}\n```\n\n항목을 클릭한 값에 data field 에 `isSelected: true` 가 생성됨을 확인\n\n![image](https://user-images.githubusercontent.com/28912774/147440455-a73e1f75-6399-443d-851e-fe8fd9c45290.png)\n\n##### firebase Firestore 삭제\n\n```swift\n// in CardListViewController.swift\n\n// forRowAt: cell delete\noverride func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {\n\tif editingStyle == .delete {\n\n\t\t// firestore 의 삭제\n\t\t// Option 1: 경로를 알고 있을때\n\t\tlet cardID = creditCardList[indexPath.row].id\n\t\t// db.collection(\"creditCardList\").document(\"card\\(cardID)\").delete()\n\n\t\t// Option 2: 경로를 모르고 있을때 : wherefield method 를 통해 문서 전체를 검색한 후에 snpshot 을 제공함\n\t\tdb.collection(\"creditCardList\").whereField(\"id\", isEqualTo: cardID).getDocuments { snapshot, _ in\n\t\t\tguard let document = snapshot?.documents.first else {\n\t\t\t\tprint(\"ERROR\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdocument.reference.delete()\n\t\t}\n\t}\n}\n```\n\n### 🔷 Firebase Realtime DB\n\n- Realtime DB 는 단일 json 파일을 관리 하기에 용의한 DB 입니다 (json import 기능 지원)\n\n#### Firebase Realtime DB 설치\n\n```ruby\n  # Pods for 09_creditCardList\n \tpod 'Firebase/Database'\n```\n\n`pod install`\n\n#### Firebase Realtime DB 읽기\n\n```swift\n//  CardListViewController.swift\nimport FirebaseDatabase\n\nclass CardListViewController: UITableViewController {\n\n\tvar ref: DatabaseReference!  // Firebase Realtime DB 참조 변수\n\n// MARK: Firebase Realtime DB READ\n/*Firebase Database 읽기*/\nself.ref = Database.database().reference()\n\nself.ref.observe(.value) { snapshot in\n\tguard let value = snapshot.value as? [String: [String: Any]] else { return }\n\tdo {\n\t\tlet jsonData = try JSONSerialization.data(withJSONObject: value)\n\t\tlet cardData = try JSONDecoder().decode([String: CreditCard].self, from: jsonData)\n\t\tlet cardList = Array(cardData.values)\n\t\tself.creditCardList = cardList.sorted { $0.rank \u003c $1.rank }\n\n\t\tDispatchQueue.main.async {\n\t\t\tself.tableView.reloadData()\n\t\t}\n\t} catch let error {\n\t\tprint(\"Error json parsing \\(error)\")\n\t}\n}\n```\n\n![image](https://user-images.githubusercontent.com/28912774/147459633-64160456-1b1c-4845-bd6d-ade1791386e9.png)\n\n#### Firebase Realtime DB 쓰기\n\n```swift\n// in CardListViewController.swift\n\n// MARK: Firebase realtime DB Write\n\t\t let cardID = creditCardList[indexPath.row].id\n\t\t //option1: 경로를 아는 경우에 쓰기\n\t\t self.ref.child(\"Item\\(cardID)/isSelected\").setValue(true)\n\t\t //option2: 경로를 모르는 경우\n\t\t self.ref.queryOrdered(byChild: \"id\").queryEqual(toValue: cardID).observe(.value) {[weak self] snapshot in\n\t\t guard let self = self,\n\t\t let value = snapshot.value as? [String: [String: Any]],\n\t\t let key = value.keys.first else { return }\n\n\t\t self.ref.child(\"\\(key)/isSelected\").setValue(true)\n\t\t }\n```\n\n#### Firebase Realtime DB 삭제\n\n```swift\n\t// forRowAt: cell delete\noverride func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {\n\tif editingStyle == .delete {\n\n\t// MARK: Firebase realtime DB Delete\n\t\tlet cardID = creditCardList[indexPath.row].id\n\t\tself.ref.queryOrdered(byChild: \"id\").queryEqual(toValue: cardID).observe(.value) {[weak self] snapshot in\n\t\tguard let self = self,\n\t\tlet value = snapshot.value as? [String: [String: Any]],\n\t\tlet key = value.keys.first else { return }\n\n\t\tself.ref.child(key).removeValue()\n\t\t}\n\t\t...\n```\n\n\u003e Describing check point in details in Jacob's DevLog - https://jacobko.info/firebaseios/ios-firebase-02/\n\n\u003c!-- ## ❌ Error Check Point\n\n### 🔶 --\u003e\n\n\u003c!-- xcode Mark template --\u003e\n\n\u003c!--\n// MARK: IBOutlet\n// MARK: LifeCycle\n// MARK: Actions\n// MARK: Methods\n// MARK: Extensions\n--\u003e\n\n---\n\n🔶 🔷 📌 🔑 👉\n\n## 🗃 Reference\n\nJacob's DevLog - [https://jacobko.info/firebaseios/ios-firebase-02/](https://jacobko.info/firebaseios/ios-firebase-02/)\n\nLEEO TIL Dev Log - [https://dev200ok.blogspot.com/2020/09/ios-kingfisher.html](https://dev200ok.blogspot.com/2020/09/ios-kingfisher.html)\n\niOS에서 Lottie 애니메이션 시작하기 - [https://ichi.pro/ko/ioseseo-lottie-aenimeisyeon-sijaghagi-29592323663035](https://ichi.pro/ko/ioseseo-lottie-aenimeisyeon-sijaghagi-29592323663035)\n\nfastcampus - [https://fastcampus.co.kr/dev_online_iosappfinal](https://fastcampus.co.kr/dev_online_iosappfinal)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacobkosmart%2Fcreditcard-ios-practice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjacobkosmart%2Fcreditcard-ios-practice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacobkosmart%2Fcreditcard-ios-practice/lists"}