An open API service indexing awesome lists of open source software.

https://github.com/jacobkosmart/creditcard-ios-practice

To practice firebase realtime DB and firestroe CRUD
https://github.com/jacobkosmart/creditcard-ios-practice

firebase firebase-realtime-database firestore-database kingfisher lottie

Last synced: 5 days ago
JSON representation

To practice firebase realtime DB and firestroe CRUD

Awesome Lists containing this project

README

          

# πŸ’³ creditCard-iOS-practice

스크란샷

## πŸ“Œ κΈ°λŠ₯ 상세

- Firebase realtime, fireStore DB κΈ°λŠ₯을 μ‚¬μš©ν•˜μ—¬ μΉ΄λ“œμΆ”μ²œ νŽ˜μ΄μ§€ λ₯Ό λ§Œλ“­λ‹ˆλ‹€

- Firebase μ—μ„œ 데이터λ₯Ό μ£Όκ³  λ°›λŠ” 과정을 μ—°μŠ΅ν•©λ‹ˆλ‹€

## πŸ‘‰ Pod library

### πŸ”· Kingfisher library

- 이미지 μ„œλ²„μ—μ„œ 이미지λ₯Ό κ°€μ Έλ‹€κ°€ UI에 κ°€μ Έλ‹€κ°€ ν‘œμ‹œ ν•΄μ€„λ•Œ μ‚¬μš©λ˜λŠ” 이미지 처리 μ˜€ν”ˆ 라이브러리 μž…λ‹ˆλ‹€

> Kingfisher cheatSheet - https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet

#### μ„€μΉ˜

`pod init`

```ruby
# Pods for 09_creditCardList
pod 'Kingfisher'
end
```

`pod install`

#### μ‚¬μš©λ²•

```swift
// CardListViewController.swift

// URL νƒ€μž…μœΌλ‘œ νƒ€μž…λ³€ν™˜ν•¨
let imageURL = URL(string: creditCardList[indexPath.row].cardImageURL)
// Kingfisher λ₯Ό μ‚¬μš©ν•΄μ„œ UI에 image ν‘œμ‹œ
cell.cardImageView.kf.setImage(with: imageURL)
```

### πŸ”· Lottie library

> Lottie-ios Github - https://github.com/airbnb/lottie-ios

- Lottie λŠ” 기본적으둜 λ°±ν„° 기반 μ• λ‹ˆλ©”λ―Έμ…˜κ³Ό μ•„νŠΈλ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ λžœλ”λ§ν•˜λŠ” Airbnb μ—μ„œ κ°œλ°œν•œ μ˜€ν”ˆ μ†ŒμŠ€ μ• λ‹ˆλ©”μ΄μ…˜ 라이브러리 μž…λ‹ˆλ‹€

- Lottie λ₯Ό `bodymovin JSON` ν˜•μ‹μœΌλ‘œ 보낸 μ—λ―Έλ©”μ΄μ…˜μ„ μ§€μ›ν•©λ‹ˆλ‹€

#### μ„€μΉ˜

```ruby
# Pods for 09_creditCardList
pod 'lottie-ios'
end
```

`pod install`

### μ‚¬μš©λ²•

- storyBoard μ—μ„œ μ—λ‹ˆλ©”μ΄μ…˜μ΄ 보여 μ§€λŠ” 곳에 view object λ₯Ό μ§€μ •ν•˜κ³ , class 섀정을 AnimationView 으둜 μ§€μ •ν•©λ‹ˆλ‹€

![image](https://user-images.githubusercontent.com/28912774/147317043-688d5db7-d22e-4975-8974-161107f156fc.png)

```swift
// in CardDetailViewController.swift

override func viewDidLoad() {
super.viewDidLoad()

// lottie animation μ„ μ–Έ (name 은 lottie 둜 뢈러올 json 파일 μ΄λ¦„μœΌλ‘œ)
let animationView = AnimationView(name: "card")
lottieView.contentMode = .scaleAspectFit // 이미지 container μ‚¬μ΄μ¦ˆμ— λ§žμΆ”κΈ°
lottieView.addSubview(animationView)
animationView.frame = lottieView.bounds
animationView.loopMode = .loop // animation 이 계속 반볡
animationView.play() // animation μ‹œμž‘
}
```

![Kapture 2021-12-24 at 14 13 33](https://user-images.githubusercontent.com/28912774/147319304-d31febc0-2673-4d81-a551-2f9a80fcfa5c.gif)

## πŸ”‘ Check Point !

### πŸ”· UI Structure

![image](https://user-images.githubusercontent.com/28912774/147461053-3d7bfc13-6428-470a-b744-82f94bcc00a7.png)

- CardListCell 은 `.xib` 파일둜 μž‘μ„±

![image](https://user-images.githubusercontent.com/28912774/147461212-fbd2f93f-b6b1-4799-88a7-bd5a6931b76d.png)

![image](https://user-images.githubusercontent.com/28912774/147463794-da169f0a-0c8d-44c0-8cb1-e47c0d9c8a7f.png)

### πŸ”· Model

```swift
import Foundation

struct CreditCard: Codable {
let id: Int
let rank: Int
let name: String
let cardImageURL: String
let promotionDetail: PromotionDetail
let isSelected: Bool? // μ‚¬μš©μžκ°€ μΉ΄λ“œλ₯Ό 선택 ν–ˆμ„ λ•Œ, 생성이 됨 κ·Έμ „μ—λŠ” nil μ΄λ‹ˆκΉŒ optional μ„€μ •
}

struct PromotionDetail: Codable {
let companyName: String
let amount: Int
let period: String
let benefitDate: String
let benefitDetail: String
let benefitCondition: String
let condition: String
}

```

### πŸ”· Firebase Firestore

#### Firebase Firestore μ„€μΉ˜

> Get started with Cloud Firestore - https://firebase.google.com/docs/firestore/quickstart#ios+

```ruby
# Pods for 09_creditCardList
pod 'Firebase/Firestore'
pod 'FirebaseFirestoreSwift'
```

`pod install`

#### Firebase firestore 에 데이터 μž…λ ₯

- firestore μ—λŠ” json νŒŒμΌμ„ web console 을 ν†΅ν•΄μ„œ ν•œλ²ˆμ— λ°”λ‘œ μž…λ ₯ν•˜λŠ” κΈ°λŠ₯이 μ—†κΈ° λ•Œλ¬Έμ— code swift μ—μ„œ dummy data λ₯Ό import ν•˜λŠ” 과정을 κ±°μ Έμ•Ό ν•©λ‹ˆλ‹€.

```swift
// in CreditCardDummy.swift

import Foundation

struct CreditCardDummy {
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)
static let card1 = CreditCard(id: 1
......
```

```swift
// in AppDelegate.swift

import FirebaseFirestoreSwift

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

// firebase init
FirebaseApp.configure()

// firebase db μ„ μ–Έ
let db = Firestore.firestore()
// collection μ—μ„œ creditCardList λ₯Ό μ°Ύκ³ , snapshot κ³Ό error λ₯Ό 뢈러옴(ν•΄λ‹Ή db에 데이터가 없을 κ²½μš°μ— ν•œλ²ˆμ— 데이터λ₯Ό λ„£μ–΄ μ£ΌλŠ” 경우 μ‚¬μš©)
db.collection("creditCardList").getDocuments { snapshot, _ in
guard snapshot?.isEmpty == true else { return } // snapshot 으둜 dbκ°€ λΉ„μ–΄ μžˆλŠ” μƒνƒœμ—μ„œλ§Œ ture 둜 μ„€μ •
let batch = db.batch()

let card0Ref = db.collection("creditCardList").document("card0")
let card1Ref = db.collection("creditCardList").document("card1")
let card2Ref = db.collection("creditCardList").document("card2")
let card3Ref = db.collection("creditCardList").document("card3")
let card4Ref = db.collection("creditCardList").document("card4")
let card5Ref = db.collection("creditCardList").document("card5")
let card6Ref = db.collection("creditCardList").document("card6")
let card7Ref = db.collection("creditCardList").document("card7")
let card8Ref = db.collection("creditCardList").document("card8")
let card9Ref = db.collection("creditCardList").document("card9")

do {
try batch.setData(from: CreditCardDummy.card0, forDocument: card0Ref)
try batch.setData(from: CreditCardDummy.card1, forDocument: card1Ref)
try batch.setData(from: CreditCardDummy.card2, forDocument: card2Ref)
try batch.setData(from: CreditCardDummy.card3, forDocument: card3Ref)
try batch.setData(from: CreditCardDummy.card4, forDocument: card4Ref)
try batch.setData(from: CreditCardDummy.card5, forDocument: card5Ref)
try batch.setData(from: CreditCardDummy.card6, forDocument: card6Ref)
try batch.setData(from: CreditCardDummy.card7, forDocument: card7Ref)
try batch.setData(from: CreditCardDummy.card8, forDocument: card8Ref)
try batch.setData(from: CreditCardDummy.card9, forDocument: card9Ref)
} catch let error {
print("ERROR: wirting card to Firestore \(error.localizedDescription)")
}
// batch 에 commit 을 ν•΄μ£Όμ–΄μ•Όμ§€ data κ°€ μΆ”κ°€κ°€ 됨
batch.commit()
}
return true
}
```

- app build 후에 firestore μ—μ„œ data κ°€ import 된 것을 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€

![image](https://user-images.githubusercontent.com/28912774/147425834-4416f895-039b-4a4a-a6e1-0c176e1cbecd.png)

##### firebase Firestore 읽기

```swift
import UIKit
import Kingfisher
import FirebaseFirestore

// UITableViewController λŠ” UITableView 에 ν•„μš”ν•œ delegate source λ₯Ό κΈ°λ³Έ μ—°κ²°λœ μƒνƒœλ‘œ μ œκ³΅ν•˜κΈ° λ•Œλ¬Έμ— λ³„λ„λ‘œ delegate 선언을 ν•˜μ§€ μ•Šμ•„λ„ 됨
// 또, rootView 둜 UItableView λ₯Ό κ°€μ§€κ²Œ λ©λ‹ˆλ‹€
class CardListViewController: UITableViewController {

// DB μ„ μ–Έ
var db = Firestore.firestore()

// MARK: Variable
var creditCardList: [CreditCard] = []

// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()

// UITabelView Cell Register
let nibName = UINib(nibName: "CardListCell", bundle: nil)
tableView.register(nibName, forCellReuseIdentifier: "CardListCell")

// firestore 읽기 code μΆ”κ°€
db.collection("creditCardList").addSnapshotListener { snapshot , error in
guard let documents = snapshot?.documents else {
// 값이 없을 κ²½μš°μ— error 처리
print("ERROR Firesotre fetching document \(String(describing: error))")
return
}
// 데이터 처리 : compactMap 을 μ‚¬μš©ν•˜λŠ” 것을 nil 값을 λ°°μ—΄ μ•ˆμ— λ„£μ§€ μ•Šκ²Œ ν•˜μ§€ μœ„ν•΄μ„œ
self.creditCardList = documents.compactMap { doc -> CreditCard? in
do {
let jsonData = try JSONSerialization.data(withJSONObject: doc.data(), options: [])
let creditCard = try JSONDecoder().decode(CreditCard.self, from: jsonData)
return creditCard
} catch let error {
print("ERROR JSON Parsing \(error)")
return nil
}
}.sorted { $0.rank < $1.rank }

// main tread μ—μ„œ λŒμ•„κ°€λŠ” tableView reload
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
```

스크란샷

##### firebase Firestore μ“°κΈ°

- struct model 에 μžˆλŠ” `isSelected: Bool?` 을 갑을 톡해 μ„ νƒλ˜λ©΄ select κ°€ λ˜κ²Œλ” firestore 에 κ°’ μž…λ ₯ν•˜κΈ° μž…λ‹ˆλ‹€

- 파일의 경둜λ₯Ό μ•Œλ•Œμ™€ λͺ¨λ₯Όλ•Œ 두가지 κ²½μš°μ— μˆ˜μ— 따라 code 방식이 닀름

```swift
// in CardListViewController.swift

// didSelectRowAt: cell 을 선택 ν–ˆμ„λ•Œ, CardDetailViewController 둜 λ„˜μ–΄κ°€λŠ” action
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 상세화면 전달
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let detailViewController = storyboard.instantiateViewController(withIdentifier: "CardDetailViewController") as? CardDetailViewController else { return }
detailViewController.promotionDetail = creditCardList[indexPath.row].promotionDetail
self.show(detailViewController, sender: nil)

// Firestore 데이터 μ“°κΈ°
// option1 : 경둜λ₯Ό μ•Œκ³  μžˆμ„ 경우
let cardID = creditCardList[indexPath.row].id
db.collection("creditCardList").document("card\(cardID)").updateData(["isSelected": true])

// option2: 경둜λ₯Ό λͺ¨λ₯΄κ³  μžˆμ„ 경우
// id 값을 κ²€μƒ‰ν•œλ‹€μŒμ— κ·Έ 결과둜 찾은 λ¬Έμ„œμ— μ—…λ°μ΄νŠΈ ν•΄μ€˜μ•Όν•¨
db.collection("creditCardList").whereField("id", isEqualTo: cardID).getDocuments { snapshot, _ in
guard let document = snapshot?.documents.first else {
// error 처리
print("ERROR Firestore fetching document")
return
}
// cardID κ°€ μžˆλ‹€λ©΄
document.reference.updateData(["isSelected": true])
}
}
```

ν•­λͺ©μ„ ν΄λ¦­ν•œ 값에 data field 에 `isSelected: true` κ°€ 생성됨을 확인

![image](https://user-images.githubusercontent.com/28912774/147440455-a73e1f75-6399-443d-851e-fe8fd9c45290.png)

##### firebase Firestore μ‚­μ œ

```swift
// in CardListViewController.swift

// forRowAt: cell delete
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {

// firestore 의 μ‚­μ œ
// Option 1: 경둜λ₯Ό μ•Œκ³  μžˆμ„λ•Œ
let cardID = creditCardList[indexPath.row].id
// db.collection("creditCardList").document("card\(cardID)").delete()

// Option 2: 경둜λ₯Ό λͺ¨λ₯΄κ³  μžˆμ„λ•Œ : wherefield method λ₯Ό 톡해 λ¬Έμ„œ 전체λ₯Ό κ²€μƒ‰ν•œ 후에 snpshot 을 μ œκ³΅ν•¨
db.collection("creditCardList").whereField("id", isEqualTo: cardID).getDocuments { snapshot, _ in
guard let document = snapshot?.documents.first else {
print("ERROR")
return
}
document.reference.delete()
}
}
}
```

### πŸ”· Firebase Realtime DB

- Realtime DB λŠ” 단일 json νŒŒμΌμ„ 관리 ν•˜κΈ°μ— μš©μ˜ν•œ DB μž…λ‹ˆλ‹€ (json import κΈ°λŠ₯ 지원)

#### Firebase Realtime DB μ„€μΉ˜

```ruby
# Pods for 09_creditCardList
pod 'Firebase/Database'
```

`pod install`

#### Firebase Realtime DB 읽기

```swift
// CardListViewController.swift
import FirebaseDatabase

class CardListViewController: UITableViewController {

var ref: DatabaseReference! // Firebase Realtime DB μ°Έμ‘° λ³€μˆ˜

// MARK: Firebase Realtime DB READ
/*Firebase Database 읽기*/
self.ref = Database.database().reference()

self.ref.observe(.value) { snapshot in
guard let value = snapshot.value as? [String: [String: Any]] else { return }
do {
let jsonData = try JSONSerialization.data(withJSONObject: value)
let cardData = try JSONDecoder().decode([String: CreditCard].self, from: jsonData)
let cardList = Array(cardData.values)
self.creditCardList = cardList.sorted { $0.rank < $1.rank }

DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
print("Error json parsing \(error)")
}
}
```

![image](https://user-images.githubusercontent.com/28912774/147459633-64160456-1b1c-4845-bd6d-ade1791386e9.png)

#### Firebase Realtime DB μ“°κΈ°

```swift
// in CardListViewController.swift

// MARK: Firebase realtime DB Write
let cardID = creditCardList[indexPath.row].id
//option1: 경둜λ₯Ό μ•„λŠ” κ²½μš°μ— μ“°κΈ°
self.ref.child("Item\(cardID)/isSelected").setValue(true)
//option2: 경둜λ₯Ό λͺ¨λ₯΄λŠ” 경우
self.ref.queryOrdered(byChild: "id").queryEqual(toValue: cardID).observe(.value) {[weak self] snapshot in
guard let self = self,
let value = snapshot.value as? [String: [String: Any]],
let key = value.keys.first else { return }

self.ref.child("\(key)/isSelected").setValue(true)
}
```

#### Firebase Realtime DB μ‚­μ œ

```swift
// forRowAt: cell delete
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {

// MARK: Firebase realtime DB Delete
let cardID = creditCardList[indexPath.row].id
self.ref.queryOrdered(byChild: "id").queryEqual(toValue: cardID).observe(.value) {[weak self] snapshot in
guard let self = self,
let value = snapshot.value as? [String: [String: Any]],
let key = value.keys.first else { return }

self.ref.child(key).removeValue()
}
...
```

> Describing check point in details in Jacob's DevLog - https://jacobko.info/firebaseios/ios-firebase-02/

---

πŸ”Ά πŸ”· πŸ“Œ πŸ”‘ πŸ‘‰

## πŸ—ƒ Reference

Jacob's DevLog - [https://jacobko.info/firebaseios/ios-firebase-02/](https://jacobko.info/firebaseios/ios-firebase-02/)

LEEO TIL Dev Log - [https://dev200ok.blogspot.com/2020/09/ios-kingfisher.html](https://dev200ok.blogspot.com/2020/09/ios-kingfisher.html)

iOSμ—μ„œ Lottie μ• λ‹ˆλ©”μ΄μ…˜ μ‹œμž‘ν•˜κΈ° - [https://ichi.pro/ko/ioseseo-lottie-aenimeisyeon-sijaghagi-29592323663035](https://ichi.pro/ko/ioseseo-lottie-aenimeisyeon-sijaghagi-29592323663035)

fastcampus - [https://fastcampus.co.kr/dev_online_iosappfinal](https://fastcampus.co.kr/dev_online_iosappfinal)