(SwiftUI) 串接 Firebase 製作 可不可飲料訂購 APP

目標:實現 App 帳號註冊功能、App 帳號登入功能、新增資料存在 FireStore、讀取 FireStore 的資料、修改 FireStore 的資料、讀取 FireStore 的照片

FireBase 基本設置教學

開啟 APP 時,完成 Firebase 的相關設定

import SwiftUI
import Firebase

@main
struct TeaApp: App {

init() {
FirebaseApp.configure()
}

var body: some Scene {
WindowGroup {
ContentView()
}
}

}

若是啟動 APP 一直閃退,請檢查放入的 Xcode 的 GoogleService-Info.plist 名稱,是否有多 (1) ex: GoogleService-Info (1).plist

製作第一個登入畫面 ContentView

struct ContentView: View {

@State private var email = ""
@State private var password = ""
@State private var alertTitle = ""
@State private var showAlert = false
@State var showBool = false

var body: some View {
NavigationStack {
ZStack {
// 背景底色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()
VStack(spacing: 20) {
Spacer()
Image("可不可封面")
.resizable()
.scaledToFill()
.frame(width: 400, height: 400)
TextField("Email", text: $email, prompt: Text("Email"))
.textFieldStyle(.roundedBorder)
.padding()
SecureField("Password", text: $password)
.textFieldStyle(.roundedBorder)
.padding()
Spacer()

HStack {

Spacer()

Button {
// 登入功能

} label: {
Text(" Log in ")
.foregroundColor(.white)
.font(.title2)
.padding()
.border(Color(red: 188/255, green: 150/255, blue: 92/255), width: 4)
.frame(width: 150, height: 100)
}

Spacer()

NavigationLink {

} label: {
Text("Sign Up")
.padding()
.background(Color(red: 188/255, green: 150/255, blue: 92/255))
.foregroundColor(.white)
.font(.title2)
.frame(width: 150, height: 100)
}
Spacer()
}
}
}
}
}
}

輸入密碼時,我希望能夠保有隱私,所以我採用的是 SecureField

SecureField("Password", text: $password)
.textFieldStyle(.roundedBorder)
.padding()

製作註冊畫面 SignUpView

import SwiftUI

struct SignUpView: View {

@State private var email = ""
@State private var name = ""
@State private var password = ""
@State private var copassword = ""
@State private var alertTitle = ""
// 控制圖片放大縮小
@State private var isEditing = false
@State private var showAlert = false

var body: some View {
ZStack {
// 背景底色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()
VStack {
Image("可不可註冊頁面")
.resizable()
.scaledToFill()
.frame(width: isEditing ? 100 : 300, height: isEditing ? 100 : 300)
.clipShape(Circle())
// 添加動畫效果
.animation(.easeInOut(duration: 0.3))


Form {
Section("Email") {
TextField("", text: $email, onEditingChanged: { editing in
self.isEditing = editing
})
.background(.white)
.foregroundColor(.black)
}
Section("Username") {
TextField("", text: $name, onEditingChanged: { editing in
self.isEditing = editing
})
.background(.white)
.foregroundColor(.black)
}
Section("Password") {
TextField("", text: $password, onEditingChanged: { editing in
self.isEditing = editing
})
.background(.white)
.foregroundColor(.black)
}
Section("Confirm Password") {
TextField("", text: $copassword, onEditingChanged: { editing in
self.isEditing = editing
})
.background(.white)
.foregroundColor(.black)
}
}
.foregroundColor(.white)
.scrollContentBackground(.hidden)

Button {

} label: {
Text("Register")
.padding()
.background(Color(red: 188/255, green: 150/255, blue: 92/255))
.foregroundColor(.white)
.font(.title3)
.frame(width: 150, height: 100)
}
.alert(alertTitle, isPresented: $showAlert) {
Button("OK") {}
} message: {
Text("")
}
}
}
}
}

為了避免使用 TextField 時,擋住輸入的資訊,我讓圖片能夠隨著我點選 TextField 放大與縮小


// 控制圖片放大縮小
@State private var isEditing = false



Image("可不可註冊頁面")
.resizable()
.scaledToFill()
.frame(width: isEditing ? 100 : 300, height: isEditing ? 100 : 300)
.clipShape(Circle())
// 添加動畫效果
.animation(.easeInOut(duration: 0.3))

實現 App 帳號註冊功能

import FirebaseAuth

import FirebaseAuth

製作帳號註冊時,會產生的各種視窗

    // alert 的判斷式
func allAlert() {
if email.isEmpty {
alertTitle = "信箱 不能為空白"
showAlert = true
}else if name.isEmpty {
alertTitle = "用戶名 不能為空白"
showAlert = true
}else if password.isEmpty {
alertTitle = "密碼 不能為空白"
showAlert = true
}else if copassword.isEmpty {
alertTitle = "確認密碼 不能為空白"
showAlert = true
}else if password != copassword {
alertTitle = "密碼與確認密碼 不符合"
showAlert = true
}else {
// 若該有的都有後,即可建立密碼
Auth.auth().createUser(withEmail: email, password: password) {result, error in
guard let user = result?.user,
error == nil else {
// 讓錯誤訊息,顯示到 alert 上
if let message = error?.localizedDescription {
alertTitle = message
showAlert = true
}
return
}
alertTitle = "建立成功"
showAlert = true
}
}
}

若是信箱格式正確、用戶名輸入、輸入密碼與確認密碼符合,即可建立帳號

                // 註冊按鈕 Register
Button {
allAlert()
} label: {}

檢查 FireBase Authentication 查看帳號是否已經建立完成

在 ContentView 中的 NavigationLink 新增 SignUpView()

struct ContentView: View {
...

var body: some View {
NavigationStack {

...

NavigationLink {
SignUpView()
} label: {
...
}

新增一個 Swift File Tea

import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift

class AllTea: ObservableObject {

// 所有的品項
@Published var allTea = [Tea]()

// 我的訂單
@Published var orders = [Tea]()
// firebase 上的訂單
@Published var firebaseOrders = [Tea]()

// 用來計算 orders 中的訂單金額
@Published var total = 0

// 計算訂單中的所有金額
func cal() {
total = 0
for i in orders.indices {
total += orders[i].price
}
}

// 清除下單的訂單
func removeOrder() {
orders.removeAll()
total = 0
}

// 更新所有 tea 的資料
func createTea(tea: Tea) {
let db = Firestore.firestore()
do {
try db.collection("tea").document("\(tea.name)").setData(from: tea)
} catch {
print(error)
}
}

// 更新我的最愛 tea 資料
func updateFavoriate() {
for tea in allTea {
createTea(tea: tea)
}
}

// 修改 order 的資料
func modifyOrder(tea: Tea, id: String) {
let db = Firestore.firestore()
do {
try db.collection("order").document(id).setData(from: tea)
print("success")
} catch {
print(error)
}
}

// 修改 favorite 的資料
func modifyFavorite(tea: Tea, id: String) {
let db = Firestore.firestore()
do {
try db.collection("tea").document(id).setData(from: tea)
print("success")
} catch {
print(error)
}
}


// 將遠端資料與本地資料進行同步
func updateLocalData(localData: inout [Tea], remoteData: [Tea]) {
for remoteTea in remoteData {
if let index = localData.firstIndex(where: { $0.name == remoteTea.name }) {
localData[index].isFavorite = remoteTea.isFavorite
}
}
}


}

// 對應資料庫飲料的格式
struct Tea: Codable, Identifiable {
let name: String
let description: String
var Mprice: Int
var Lprice: Int
var isFavorite: Bool = false
var comment: String = ""
@DocumentID var id: String?

// 飲料細項
var size: String = "M"
var ice: String = "正常冰"
var suger: String = "正常糖"
var price: Int = 0

}

// 對應期間限定的資料格式
struct Drink: Codable, Identifiable {
@DocumentID var id: String?
var image: String
}

新增 HomeView

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift

struct HomeView: View {

@EnvironmentObject var data: AllTea
@FirestoreQuery(collectionPath: "tea") var teas: [Tea]
@FirestoreQuery(collectionPath: "drink") var images: [Drink]


var body: some View {
let columns = Array(repeating: GridItem(), count: 2)
ZStack {
// 背景底色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()
VStack {
Text("期間限定")
.font(.title3)
.foregroundColor(.white)
ScrollView(.horizontal) {
HStack {
ForEach(images) { image in
AsyncImage(url: URL(string: image.image)) { image in
image
.resizable()
.scaledToFit()
.frame(height: 180)
} placeholder: {}
}
}
}

ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(teas) { tea in

}
}
}
// 加上 padding() 後,就不會反白
.padding()
}
}
}
}

struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
.environmentObject(AllTea())
}
}

期間限定所顯示的圖片,是由資料庫中的 drink 所抓到顯示的

抓取 Cloud Firestore 中的資料

    @FirestoreQuery(collectionPath: "drink") var images: [Drink]

對應 Tea 中的 Drink 格式

// 對應期間限定的資料格式
struct Drink: Codable, Identifiable {
@DocumentID var id: String?
var image: String
}

製作畫面 TeaView

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift

struct TeaView: View {

// 愛心是否填滿
@State var isFill = false
@EnvironmentObject var data: AllTea
@State var tea: Tea

var body: some View {
VStack(alignment: .leading) {
Image(tea.name)
.resizable()
.scaledToFill()
.frame(width: 170, height: 160)
.clipped()
.cornerRadius(15)


Text(tea.name)
.font(.title2)

HStack(spacing: 50) {
Text("價格:\(tea.Mprice)$")

// 下單一杯飲料
Button {
// 將 price 設為 M 尺寸的價錢
tea.price = tea.Mprice

// 加入到本地端的 tea 的 id 必須設為 nil
tea.id = nil

data.orders.append(tea)
// 每次新增一筆訂單,就計算一次總金額
data.cal()

} label: {
Image(systemName: "plus.circle.fill")
.foregroundColor(Color(red: 188/255, green: 150/255, blue: 92/255))
.font(.title)
}

}

}
.overlay(alignment: .topLeading, content: {
Button {
isFill.toggle()
tea.isFavorite.toggle()

// 更新我的最愛
modify(tea: tea)
} label: {
Image(systemName: isFill ? "heart.fill" : "heart")
.foregroundColor(.red)
.font(.title)
}
})


}

// 更新資料庫中的最愛名單
func modify(tea: Tea) {
if let id = tea.id {
data.modifyFavorite(tea: tea, id: id)
}else {
print("error")
}
}

}

struct TeaView_Previews: PreviewProvider {
static var previews: some View {
TeaView(isFill: false, tea: Tea(name: "熟成紅茶", description: "木質帶熟果香調的風味,流露熟齡男子的沈穩氣息,低調而迷人。嚴選自斯里蘭卡產區之茶葉,帶有濃郁果香及醇厚桂圓香氣;與肉類料理一同品嚐,得以化解舌尖上所殘留的油膩感。", Mprice: 30, Lprice: 35))
.environmentObject(AllTea())
}
}

這邊新增了修改資料庫中我的最愛品項修改,其中使用了 Tea 中的 modifyFavorite

    // 更新資料庫中的最愛名單
func modify(tea: Tea) {
if let id = tea.id {
data.modifyFavorite(tea: tea, id: id)
}else {
print("error")
}
}

在我修改 Firestore 資料的方式,其實是使用了一般用來新增資料的方法,因為若是丟入相同 id,並不會新增一筆新的資料,而是修改相同 id 的資料

    // 更新所有 tea 的資料
func createTea(tea: Tea) {
let db = Firestore.firestore()
do {
try db.collection("tea").document("\(tea.name)").setData(from: tea)
} catch {
print(error)
}
}

// 更新我的最愛 tea 資料
func updateFavoriate() {
for tea in allTea {
createTea(tea: tea)
}
}

丟入本地端的飲料的 id 必須設為 nil ,否則可能會無法新增重複的訂單

在 HomeView 使用剛剛設計的 TeaView

              ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(teas) { tea in
TeaView(isFill: tea.isFavorite,tea: tea)
.background(Color(red: 249/255, green: 249/255, blue: 249/255))
.cornerRadius(15)
}
}
}

這裡先簡單同步 FireBase 中的 tea 資料,右下角其實也有新增飲料的按鈕,每當按下新增飲料按鈕,就會新增一筆 Tea 到 本地端的 data.orders 中

新增 FavoriteView, FavoriteRowView, FavoriteDetialView

FavoriteView

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift


struct FavoriteView: View {

@FirestoreQuery(collectionPath: "tea") var teas: [Tea]

var body: some View {

NavigationStack {
ZStack {
// 背景底色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()

ScrollView {
ForEach(teas) { tea in
// 判斷 tea 是否為最愛
if tea.isFavorite {
NavigationLink {
FavoriteDetialView(tea: tea)
} label: {
FavoriteRowView(tea: tea)
}
}
}
}
.background(.white)
.cornerRadius(15)
.padding()

}
}


}

}

struct FavoriteView_Previews: PreviewProvider {
static var previews: some View {
FavoriteView()
}
}

FavoriteRowView

import SwiftUI

struct FavoriteRowView: View {

var tea: Tea

var body: some View {
ZStack {
Color.white
.ignoresSafeArea()
HStack(spacing: 20) {

HStack(spacing: 20) {
Image("\(tea.name)")
.resizable()
.scaledToFill()
.frame(width: 150, height: 150)
.clipped()
.cornerRadius(10)
Spacer()
Text("\(tea.name)")
.font(.title2)
.foregroundColor(.black)

}
.padding()

Spacer()
}
}


}
}

struct FavoriteRowView_Previews: PreviewProvider {
static var previews: some View {
FavoriteRowView(tea: Tea(name: "冷露檸果", description: "將整顆新鮮檸檬的酸爽清香,浸入古法熬煮的冬瓜,以果香勾勒甘醇,沁入喉間之際,交織出別於以往的經典組合,令人一再回味。", Mprice: 55, Lprice: 65))
}
}

FavoriteDetialView

import SwiftUI

struct FavoriteDetialView: View {
@EnvironmentObject var data: AllTea
@State private var comment = ""

@State var tea: Tea
var body: some View {

ZStack {
// 背景底色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()

VStack {
Image("\(tea.name)")
.resizable()
.scaledToFill()
.frame(width: 400, height: 300)
.clipped()

Text("介紹")
.font(.title2)
.foregroundColor(Color(red: 188/255, green: 150/255, blue: 92/255))
Text("\(tea.description)")
.padding()
.foregroundColor(.white)

TextField("Comment", text: $comment)
.textFieldStyle(.roundedBorder)
.padding()

Button {
tea.comment = comment
modify(tea: tea)
} label: {
Text("完成評論")
.foregroundColor(Color(red: 188/255, green: 150/255, blue: 92/255))
}

Spacer()
}
}
.navigationTitle("\(tea.name)")




}

// 更新資料庫中的 評價
func modify(tea: Tea) {
if let id = tea.id {
data.modifyFavorite(tea: tea, id: id)
}else {
print("error")
}
}

}

struct FavoriteDetialView_Previews: PreviewProvider {
static var previews: some View {
FavoriteDetialView(tea: Tea(name: "冷露檸果", description: "將整顆新鮮檸檬的酸爽清香,浸入古法熬煮的冬瓜,以果香勾勒甘醇,沁入喉間之際,交織出別於以往的經典組合,令人一再回味。", Mprice: 55, Lprice: 65))
.environmentObject(AllTea())
}
}

新增 OrderView 頁面

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift

struct OrderView: View {


@EnvironmentObject var data: AllTea
@State private var showAlert = false
@State private var alertTitle = ""



var body: some View {
ZStack() {
// 背景底色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()

VStack {
ScrollView {
VStack(spacing: 40) {
ForEach(data.orders.indices, id: \.self) { item in

}
}
}
.background(.white)
.cornerRadius(15)
.padding()
VStack {
Text("總金額 : \(data.total)$")
.font(.title)
.foregroundColor(.white)
}
// 訂單按鈕
Button {
// 判斷是否有訂單存在
if data.orders.isEmpty {
alertTitle = "您的購物車是空的"
}
showAlert = true
// 新增訂單
for tea in data.orders {
createOrder(tea: tea)
alertTitle = "已完成下單,若要更改訂單請點選右下角 ..."
}
data.removeOrder()
} label: {
Text("下單")
.padding()
.foregroundColor(.white)
.background(Color(red: 188/255, green: 150/255, blue: 92/255))
.cornerRadius(20)
}
.alert(alertTitle, isPresented: $showAlert) {
Button("OK") {}
} message: {
Text("")
}
}
}

}

// 產生訂單到 firestore
func createOrder(tea: Tea) {
let db = Firestore.firestore()
do {
let documentReference = try db.collection("order").addDocument(from: tea)
print(documentReference.documentID)
} catch {
print(error)
}
}

}

struct OrderView_Previews: PreviewProvider {
static var previews: some View {
OrderView()
.environmentObject(AllTea())
}
}

新增 OrderRowView 頁面

import SwiftUI

struct OrderRowView: View {

@EnvironmentObject var data: AllTea

@Binding var order: Tea

@State var selectedSize = "M"


let size = ["M", "L"]

@Binding var sugerDefault: String
@Binding var iceDefault: String


let 甜度 = [
"無糖",
"一分糖",
"二分糖",
"微糖",
"半糖",
"少糖",
"正常糖"
]

let 冰度 = [
"熱",
"溫",
"常溫",
"完全去冰",
"去冰",
"微冰",
"少冰",
"正常冰"
]


var body: some View {
HStack(alignment: .top, spacing: 20) {
Image("\(order.name)")
.resizable()
.scaledToFill()
.frame(width: 150, height: 150)
.clipped()
.cornerRadius(10)
.padding()

VStack(alignment: .leading,spacing: 15) {
Spacer()
Text("\(order.name)")
.font(.title3)
.foregroundColor(.black)
// 選單
Menu {
ForEach(甜度, id: \.self) { option in
Button {
sugerDefault = option
order.suger = option
} label: {
Text(option)
}
}
} label: {
Label {
Text("甜度:\(sugerDefault)")
} icon: {}
}
.foregroundColor(.blue)

Menu {
ForEach(冰度, id: \.self) { option in
Button {
iceDefault = option
order.ice = option
} label: {
Text(option)
}
}
} label: {
Label {
Text("冰度:\(iceDefault)")
} icon: {}
}
.foregroundColor(.blue)

// 決定中杯大杯
Picker(selection: $selectedSize) {
ForEach(size, id: \.self) { role in
Text(role)
}
} label: {
Text("")
}
.pickerStyle(.segmented)
.onChange(of: selectedSize) { newSize in
print("Selected size is now \(newSize)")
if newSize == "M" {
order.price = order.Mprice
order.size = "M"
}else {
order.price = order.Lprice
order.size = "L"
}
// 必須呼叫 cal 不然總金額會無法更新
data.cal()

}

Text("價錢:\(order.price)$")
}
.frame(height: 150)

Spacer()

}
.onAppear {
if order.price == order.Lprice {
selectedSize = "L"
}
}



}

}

struct OrderRowView_Previews: PreviewProvider {
static var previews: some View {
OrderRowView(order: .constant(Tea(name: "熟成紅茶", description: "", Mprice: 50, Lprice: 50)), sugerDefault: .constant("正常糖"), iceDefault: .constant("正常冰"))
.environmentObject(AllTea())

}
}

接下來在 OrderView 中新增剛剛設計的 OrderRowView

                ScrollView {
VStack(spacing: 40) {
ForEach(data.orders.indices, id: \.self) { item in
OrderRowView(order: $data.orders[item], sugerDefault: $data.orders[item].suger, iceDefault: $data.orders[item].ice)
}
}
}

在 OrderView 其實比較特別一些,它抓取資料是從本地端的 data.orders 中所抓取的資料,所以在設計 OrderRowView 的 order 才會使用 @Binding

新增 MoreView 頁面

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift
import FirebaseAuth

struct MoreView: View {

@FirestoreQuery(collectionPath: "order") var teas: [Tea]
@EnvironmentObject var data: AllTea
@State var myorder : [Tea] = []

@State var showLogin = false



var body: some View {

NavigationStack {

ZStack {
// 背景底色
// 確保底部的 TabView 背景顏色為 深色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()

VStack {
// 確保在還沒抓取到資料時,背景顏色為 深色
if teas.isEmpty {
// 背景底色
Color(red: 0, green: 62/255, blue: 82/255)
.ignoresSafeArea()
}else {
List {
ForEach(teas) { tea in
NavigationLink {
// ConfirmView(order: tea, sugerDefault: tea.suger, iceDefault: tea.ice)
} label: {
HStack {
Text(tea.size)
Text(tea.name)
Spacer()
Text("\(tea.price)$")
}
.onAppear {
myorder = teas
}
}
}
.onDelete { indexSet in
myorder.remove(atOffsets: indexSet)
// 保存原本所有的 id
var or = [String]()
// 保存原本所有的 id
var ne = [String]()

for i in teas.indices {
or.append(teas[i].id!)
}

for i in myorder.indices {
ne.append(myorder[i].id!)
}

for i in miss(arrayA: or, arrayB: ne) {
del(id: i)
}
}
}
.background(Color(red: 0, green: 62/255, blue: 82/255))
.scrollContentBackground(.hidden)
}

// sign out button
Button {
do {
showLogin = true
try Auth.auth().signOut()
} catch {
print(error)
}
} label: {
Text("Sign out")
}
.padding()
.foregroundColor(.white)
.background(Color(red: 188/255, green: 150/255, blue: 92/255))
.cornerRadius(40)
.fullScreenCover(isPresented: $showLogin) {
ContentView()
}

}

}

}

}

// 刪除資料
func del(id: String) {
let db = Firestore.firestore()
let documentReference = db.collection("order").document(id)
documentReference.delete()
}

// 找出被刪除的元素
func miss(arrayA: [String], arrayB: [String]) -> Set<String> {
let setA = Set(arrayA)
let setB = Set(arrayB)
return setA.subtracting(setB)
}


}

struct MoreView_Previews: PreviewProvider {
static var previews: some View {
MoreView()
.environmentObject(AllTea())

}
}

新增 ReviseView 頁面

import SwiftUI

struct ReviseView: View {

@EnvironmentObject var data: AllTea

@State var order: Tea

// 預設為 中杯尺寸
@State var selectedSize = "M"
let size = ["M", "L"]

@State var sugerDefault = "正常糖"
@State var iceDefault = "正常冰"

//
let 甜度 = [
"無糖",
"一分糖",
"二分糖",
"微糖",
"半糖",
"少糖",
"正常糖"
]

let 冰度 = [
"熱",
"溫",
"常溫",
"完全去冰",
"去冰",
"微冰",
"少冰",
"正常冰"
]


var body: some View {
HStack(alignment: .top, spacing: 20) {
Image("\(order.name)")
.resizable()
.scaledToFill()
.frame(width: 150, height: 150)
.clipped()
.cornerRadius(10)
.padding()

VStack(alignment: .leading,spacing: 15) {
Spacer()
Text("\(order.name)")
.font(.title3)
.foregroundColor(.black)
// 選單
Menu {
ForEach(甜度, id: \.self) { option in
Button {
sugerDefault = option
order.suger = option
// 修改訂單
modify()
} label: {
Text(option)
}
}
} label: {
Label {
Text("甜度:\(sugerDefault)")

} icon: {
}
}
.foregroundColor(.blue)

Menu {
ForEach(冰度, id: \.self) { option in
Button {
iceDefault = option
order.ice = option
// 修改訂單
modify()
} label: {
Text(option)
}
}
} label: {
Label {
Text("冰度:\(iceDefault)")
} icon: {
}
}
.foregroundColor(.blue)

// 決定中杯大杯
Picker(selection: $selectedSize) {
ForEach(size, id: \.self) { role in
Text(role)
}

} label: {
Text("")
}
.pickerStyle(.segmented)
.onChange(of: selectedSize) { newSize in
print("Selected size is now \(newSize)")
if newSize == "M" {
order.price = order.Mprice
order.size = "M"
}else {
order.price = order.Lprice
order.size = "L"
}

// 修改 訂單
modify()
}


Text("價錢:\(order.price)$")


}
.frame(height: 150)

Spacer()

}
.onAppear {
if order.price == order.Lprice {
selectedSize = "L"
}
}



}

// 修改訂單
func modify() {
if let id = order.id {
data.modifyOrder(tea: order, id: id)
}else {
print("error")
}
}

}

struct ReviseView_Previews: PreviewProvider {
static var previews: some View {
ReviseView(order: Tea(name: "熟成紅茶", description: "", Mprice: 50, Lprice: 50), sugerDefault: "正常糖", iceDefault: "正常冰")
.environmentObject(AllTea())

}
}

將剛剛設計的 ReviseView 加入到 MoreView 中

                          ForEach(teas) { tea in
NavigationLink {
ReviseView(order: tea, sugerDefault: tea.suger, iceDefault: tea.ice)

新增 AppView 頁面

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift

struct AppView: View {

@EnvironmentObject var data: AllTea

var body: some View {
TabView {
HomeView()
.tabItem {
Label("", systemImage: "house")
}
FavoriteView()
.tabItem {
Label("", systemImage: "heart")
}
OrderView()
.tabItem {
Label("", systemImage: "bag")
}
MoreView()
.tabItem {
Label("", systemImage: "ellipsis")
}
}
.tabViewStyle(DefaultTabViewStyle())
.accentColor(Color(red: 188/255, green: 150/255, blue: 92/255))
.environmentObject(data)



}
}

struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView()
.environmentObject(AllTea())
}
}

執行結果

將 AppView 加入到 ContentView 中

在 ContentView 新增以下 function ,以及 import FirebaseAuth

// 記得 import FirebaseAuth
import FirebaseAuth


...

// 判斷是否登入成功
func login() {
Auth.auth().signIn(withEmail: email, password: password) {result, error in
guard error == nil else {
if let message = error?.localizedDescription {
alertTitle = message
}
showAlert = true
return
}
alertTitle = "success"
showBool = true
}
}

在 Button Log in 底下新增以下程式碼

                        Button {
// 登入功能
login()
} label: {
Text(" Log in ")
.foregroundColor(.white)
.font(.title2)
.padding()
.border(Color(red: 188/255, green: 150/255, blue: 92/255), width: 4)
.frame(width: 150, height: 100)
}
.fullScreenCover(isPresented: $showBool) {
AppView()
}
.alert(alertTitle, isPresented: $showAlert) {
Button("OK") {}
} message: {
Text("")
}

最終在 APP 初始頁面,新增是否登入判斷式

import SwiftUI
import Firebase
import FirebaseAuth

@main
struct TeaApp: App {

@StateObject var data = AllTea()

init() {
FirebaseApp.configure()
}

var body: some Scene {
WindowGroup {
// 判斷是否已經登入過了
// 判斷帳號是否登入過,登入過的話,就自動填寫 信箱欄位
if Auth.auth().currentUser != nil {
AppView()
.environmentObject(data)
}else {
ContentView()
.environmentObject(data)
}
}
}
}

最終執行結果

GitHub

--

--