-
Firebase realtime, fireStore DB ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ์นด๋์ถ์ฒ ํ์ด์ง ๋ฅผ ๋ง๋ญ๋๋ค
-
Firebase ์์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๊ณผ์ ์ ์ฐ์ตํฉ๋๋ค
- ์ด๋ฏธ์ง ์๋ฒ์์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ๋ค๊ฐ UI์ ๊ฐ์ ธ๋ค๊ฐ ํ์ ํด์ค๋ ์ฌ์ฉ๋๋ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ์คํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๋๋ค
Kingfisher cheatSheet - https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet
pod init
# Pods for 09_creditCardList
pod 'Kingfisher'
end
pod install
// CardListViewController.swift
// URL ํ์
์ผ๋ก ํ์
๋ณํํจ
let imageURL = URL(string: creditCardList[indexPath.row].cardImageURL)
// Kingfisher ๋ฅผ ์ฌ์ฉํด์ UI์ image ํ์
cell.cardImageView.kf.setImage(with: imageURL)
Lottie-ios Github - https://github.com/airbnb/lottie-ios
-
Lottie ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐฑํฐ ๊ธฐ๋ฐ ์ ๋๋ฉ๋ฏธ์ ๊ณผ ์ํธ๋ฅผ ์ค์๊ฐ์ผ๋ก ๋๋๋งํ๋ Airbnb ์์ ๊ฐ๋ฐํ ์คํ ์์ค ์ ๋๋ฉ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๋๋ค
-
Lottie ๋ฅผ
bodymovin JSON
ํ์์ผ๋ก ๋ณด๋ธ ์๋ฏธ๋ฉ์ด์ ์ ์ง์ํฉ๋๋ค
# Pods for 09_creditCardList
pod 'lottie-ios'
end
pod install
- storyBoard ์์ ์๋๋ฉ์ด์ ์ด ๋ณด์ฌ ์ง๋ ๊ณณ์ view object ๋ฅผ ์ง์ ํ๊ณ , class ์ค์ ์ AnimationView ์ผ๋ก ์ง์ ํฉ๋๋ค
// 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 ์์
}
- CardListCell ์
.xib
ํ์ผ๋ก ์์ฑ
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
}
Get started with Cloud Firestore - https://firebase.google.com/docs/firestore/quickstart#ios+
# Pods for 09_creditCardList
pod 'Firebase/Firestore'
pod 'FirebaseFirestoreSwift'
pod install
- firestore ์๋ json ํ์ผ์ web console ์ ํตํด์ ํ๋ฒ์ ๋ฐ๋ก ์ ๋ ฅํ๋ ๊ธฐ๋ฅ์ด ์๊ธฐ ๋๋ฌธ์ code swift ์์ dummy data ๋ฅผ import ํ๋ ๊ณผ์ ์ ๊ฑฐ์ ธ์ผ ํฉ๋๋ค.
// 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
......
// 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 ๋ ๊ฒ์ ํ์ธ ํ ์ ์์ต๋๋ค
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()
}
}
}
}
-
struct model ์ ์๋
isSelected: Bool?
์ ๊ฐ์ ํตํด ์ ํ๋๋ฉด select ๊ฐ ๋๊ฒ๋ firestore ์ ๊ฐ ์ ๋ ฅํ๊ธฐ ์ ๋๋ค -
ํ์ผ์ ๊ฒฝ๋ก๋ฅผ ์๋์ ๋ชจ๋ฅผ๋ ๋๊ฐ์ง ๊ฒฝ์ฐ์ ์์ ๋ฐ๋ผ code ๋ฐฉ์์ด ๋ค๋ฆ
// 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
๊ฐ ์์ฑ๋จ์ ํ์ธ
// 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()
}
}
}
- Realtime DB ๋ ๋จ์ผ json ํ์ผ์ ๊ด๋ฆฌ ํ๊ธฐ์ ์ฉ์ํ DB ์ ๋๋ค (json import ๊ธฐ๋ฅ ์ง์)
# Pods for 09_creditCardList
pod 'Firebase/Database'
pod install
// 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)")
}
}
// 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)
}
// 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/
๐ถ ๐ท ๐ ๐ ๐
Jacob's DevLog - https://jacobko.info/firebaseios/ios-firebase-02/
LEEO TIL Dev Log - https://dev200ok.blogspot.com/2020/09/ios-kingfisher.html
iOS์์ Lottie ์ ๋๋ฉ์ด์ ์์ํ๊ธฐ - https://ichi.pro/ko/ioseseo-lottie-aenimeisyeon-sijaghagi-29592323663035
fastcampus - https://fastcampus.co.kr/dev_online_iosappfinal