版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2020.12.17 星期四 |
前言
今天翻閱蘋果的API文檔,發(fā)現(xiàn)多了一個框架SwiftUI,這里我們就一起來看一下這個框架漂羊。感興趣的看下面幾篇文章心例。
1. SwiftUI框架詳細(xì)解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細(xì)解析 (二) —— 基于SwiftUI的閃屏頁的創(chuàng)建(一)
3. SwiftUI框架詳細(xì)解析 (三) —— 基于SwiftUI的閃屏頁的創(chuàng)建(二)
4. SwiftUI框架詳細(xì)解析 (四) —— 使用SwiftUI進(jìn)行蘋果登錄(一)
5. SwiftUI框架詳細(xì)解析 (五) —— 使用SwiftUI進(jìn)行蘋果登錄(二)
6. SwiftUI框架詳細(xì)解析 (六) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(一)
7. SwiftUI框架詳細(xì)解析 (七) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(二)
8. SwiftUI框架詳細(xì)解析 (八) —— 基于SwiftUI的動畫的實現(xiàn)(一)
9. SwiftUI框架詳細(xì)解析 (九) —— 基于SwiftUI的動畫的實現(xiàn)(二)
10. SwiftUI框架詳細(xì)解析 (十) —— 基于SwiftUI構(gòu)建各種自定義圖表(一)
11. SwiftUI框架詳細(xì)解析 (十一) —— 基于SwiftUI構(gòu)建各種自定義圖表(二)
12. SwiftUI框架詳細(xì)解析 (十二) —— 基于SwiftUI創(chuàng)建Mind-Map UI(一)
13. SwiftUI框架詳細(xì)解析 (十三) —— 基于SwiftUI創(chuàng)建Mind-Map UI(二)
14. SwiftUI框架詳細(xì)解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)
源碼
1. Swift
1. AuthenticationService.swift
import Foundation
import Firebase
// 1
class AuthenticationService: ObservableObject {
// 2
@Published var user: User?
private var authenticationStateHandler: AuthStateDidChangeListenerHandle?
// 3
init() {
addListeners()
}
// 4
static func signIn() {
if Auth.auth().currentUser == nil {
Auth.auth().signInAnonymously()
}
}
private func addListeners() {
// 5
if let handle = authenticationStateHandler {
Auth.auth().removeStateDidChangeListener(handle)
}
// 6
authenticationStateHandler = Auth.auth()
.addStateDidChangeListener { _, user in
self.user = user
}
}
}
2. CardRepository.swift
import Foundation
// 1
import FirebaseFirestore
import FirebaseFirestoreSwift
import Combine
// 2
class CardRepository: ObservableObject {
// 3
private let path: String = "cards"
private let store = Firestore.firestore()
// 1
@Published var cards: [Card] = []
// 1
var userId = ""
// 2
private let authenticationService = AuthenticationService()
// 3
private var cancellables: Set<AnyCancellable> = []
init() {
// 1
authenticationService.$user
.compactMap { user in
user?.uid
}
.assign(to: \.userId, on: self)
.store(in: &cancellables)
// 2
authenticationService.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
// 3
self?.get()
}
.store(in: &cancellables)
}
func get() {
// 3
store.collection(path)
.whereField("userId", isEqualTo: userId)
.addSnapshotListener { querySnapshot, error in
// 4
if let error = error {
print("Error getting cards: \(error.localizedDescription)")
return
}
// 5
self.cards = querySnapshot?.documents.compactMap { document in
// 6
try? document.data(as: Card.self)
} ?? []
}
}
// 4
func add(_ card: Card) {
do {
var newCard = card
newCard.userId = userId
_ = try store.collection(path).addDocument(from: newCard)
} catch {
fatalError("Unable to add card: \(error.localizedDescription).")
}
}
func update(_ card: Card) {
// 1
guard let cardId = card.id else { return }
// 2
do {
// 3
try store.collection(path).document(cardId).setData(from: card)
} catch {
fatalError("Unable to update card: \(error.localizedDescription).")
}
}
func remove(_ card: Card) {
// 1
guard let cardId = card.id else { return }
// 2
store.collection(path).document(cardId).delete { error in
if let error = error {
print("Unable to remove card: \(error.localizedDescription)")
}
}
}
}
3. CardListViewModel.swift
import Foundation
// 1
import Combine
// 2
class CardListViewModel: ObservableObject {
// 1
@Published var cardViewModels: [CardViewModel] = []
// 2
private var cancellables: Set<AnyCancellable> = []
// 3
@Published var cardRepository = CardRepository()
init() {
// 1
cardRepository.$cards.map { cards in
cards.map(CardViewModel.init)
}
// 2
.assign(to: \.cardViewModels, on: self)
// 3
.store(in: &cancellables)
}
// 4
func add(_ card: Card) {
cardRepository.add(card)
}
}
4. CardViewModel.swift
import Foundation
import Combine
// 1
class CardViewModel: ObservableObject, Identifiable {
// 2
private let cardRepository = CardRepository()
@Published var card: Card
// 3
private var cancellables: Set<AnyCancellable> = []
// 4
var id = ""
init(card: Card) {
self.card = card
// 6
$card
.compactMap { $0.id }
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
func update(card: Card) {
cardRepository.update(card)
}
func remove() {
cardRepository.remove(card)
}
}
5. CardView.swift
import SwiftUI
struct CardView: View {
var cardViewModel: CardViewModel
@State var showContent: Bool = false
@State var viewState = CGSize.zero
@State var showAlert = false
var body: some View {
ZStack(alignment: .center) {
backView.opacity(showContent ? 1 : 0)
frontView.opacity(showContent ? 0 : 1)
}
.frame(width: 250, height: 400)
.background(Color.orange)
.cornerRadius(20)
.shadow(color: Color(.blue).opacity(0.3), radius: 5, x: 10, y: 10)
.rotation3DEffect(.degrees(showContent ? 180.0 : 0.0), axis: (x: 0, y: -1, z: 0))
.offset(x: viewState.width, y: viewState.height)
.animation(.spring(response: 0.6, dampingFraction: 0.8, blendDuration: 0))
.onTapGesture {
withAnimation {
showContent.toggle()
}
}
.gesture(
DragGesture()
.onChanged { value in
viewState = value.translation
}
.onEnded { value in
if value.location.y < value.startLocation.y - 40.0 {
showAlert.toggle()
}
viewState = .zero
}
)
.alert(isPresented: $showAlert) {
Alert(
title: Text("Remove Card"),
message: Text("Are you sure you want to remove this card?"),
primaryButton: .destructive(Text("Remove")) {
cardViewModel.remove()
},
secondaryButton: .cancel())
}
}
var frontView: some View {
VStack(alignment: .center) {
Spacer()
Text(cardViewModel.card.question)
.foregroundColor(.white)
.font(.system(size: 20))
.fontWeight(.bold)
.multilineTextAlignment(.center)
.padding(20.0)
Spacer()
if !cardViewModel.card.successful {
Text("You answered this one incorrectly before")
.foregroundColor(.white)
.font(.system(size: 11.0))
.fontWeight(.bold)
.padding()
}
}
}
var backView: some View {
VStack {
// 1
Spacer()
Text(cardViewModel.card.answer)
.foregroundColor(.white)
.font(.body)
.padding(20.0)
.multilineTextAlignment(.center)
.animation(.easeInOut)
Spacer()
// 2
HStack(spacing: 40) {
Button(action: markCardAsSuccesful) {
Image(systemName: "hand.thumbsup.fill")
.padding()
.background(Color.green)
.font(.title)
.foregroundColor(.white)
.clipShape(Circle())
}
Button(action: markCardAsUnsuccesful) {
Image(systemName: "hand.thumbsdown.fill")
.padding()
.background(Color.blue)
.font(.title)
.foregroundColor(.white)
.clipShape(Circle())
}
}
.padding()
}
.rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
}
// 1
private func markCardAsUnsuccesful() {
var updatedCard = cardViewModel.card
updatedCard.successful = false
update(card: updatedCard)
}
// 2
private func markCardAsSuccesful() {
var updatedCard = cardViewModel.card
updatedCard.successful = true
update(card: updatedCard)
}
// 3
func update(card: Card) {
cardViewModel.update(card: card)
showContent.toggle()
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
let card = testData[0]
return CardView(cardViewModel: CardViewModel(card: card))
}
}
6. NewCardForm.swift
import SwiftUI
struct NewCardForm: View {
@State var question: String = ""
@State var answer: String = ""
@Environment(\.presentationMode) var presentationMode
@ObservedObject var cardListViewModel: CardListViewModel
var body: some View {
VStack(alignment: .center, spacing: 30) {
VStack(alignment: .leading, spacing: 10) {
Text("Question")
.foregroundColor(.gray)
TextField("Enter the question", text: $question)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
VStack(alignment: .leading, spacing: 10) {
Text("Answer")
.foregroundColor(.gray)
TextField("Enter the answer", text: $answer)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
Button(action: addCard) {
Text("Add New Card")
.foregroundColor(.blue)
}
Spacer()
}
.padding(EdgeInsets(top: 80, leading: 40, bottom: 0, trailing: 40))
}
private func addCard() {
// 1
let card = Card(question: question, answer: answer)
// 2
cardListViewModel.add(card)
// 3
presentationMode.wrappedValue.dismiss()
}
}
struct NewCardForm_Previews: PreviewProvider {
static var previews: some View {
NewCardForm(cardListViewModel: CardListViewModel())
}
}
7. CardListView.swift
import SwiftUI
struct CardListView: View {
@ObservedObject var cardListViewModel = CardListViewModel()
@State var showForm = false
var body: some View {
NavigationView {
VStack {
Spacer()
VStack {
GeometryReader { geometry in
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(cardListViewModel.cardViewModels) { cardViewModel in
CardView(cardViewModel: cardViewModel)
.padding([.leading, .trailing])
}
}.frame(height: geometry.size.height)
}
}
}
Spacer()
}
.sheet(isPresented: $showForm) {
NewCardForm(cardListViewModel: CardListViewModel())
}
.navigationBarTitle("?? Fire Cards")
// swiftlint:disable multiple_closures_with_trailing_closure
.navigationBarItems(trailing: Button(action: { showForm.toggle() }) {
Image(systemName: "plus")
.font(.title)
})
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct CardListView_Previews: PreviewProvider {
static var previews: some View {
CardListView(cardListViewModel: CardListViewModel())
}
}
8. Card.swift
import Foundation
import FirebaseFirestoreSwift
struct Card: Identifiable, Codable {
@DocumentID var id: String?
var question: String
var answer: String
var successful: Bool = true
var userId: String?
}
#if DEBUG
let testData = (1...10).map { i in
Card(question: "Question #\(i)", answer: "Answer #\(i)")
}
#endif
9. AppDelegate.swift
import UIKit
import Firebase
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
FirebaseApp.configure()
AuthenticationService.signIn()
return true
}
10. SceneDelegate.swift
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView())
self.window = window
window.makeKeyAndVisible()
}
}
}
11. ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
CardListView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
后記
本篇主要講述了基于
Firebase Cloud Firestore
的SwiftUI iOS
程序的持久性添加,感興趣的給個贊或者關(guān)注~~~