Skip to content

Commit 1d9490a

Browse files
committed
Adding DetailView SwiftUI to firestore sample
1 parent 1fcb152 commit 1d9490a

File tree

12 files changed

+324
-67
lines changed

12 files changed

+324
-67
lines changed

firestore/FirestoreExample.xcodeproj/project.pbxproj

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@
3131
8E4C637825EEF4CA001678A1 /* RestaurantListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E4C637725EEF4CA001678A1 /* RestaurantListViewModel.swift */; };
3232
8E4C63B225F05E76001678A1 /* StarsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E4C63B125F05E76001678A1 /* StarsView.swift */; };
3333
8E4C63D825F6D641001678A1 /* Firestore+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E4C63D725F6D641001678A1 /* Firestore+Extension.swift */; };
34-
8E4C63DE25F6D879001678A1 /* ImageThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E4C63DD25F6D879001678A1 /* ImageThumbnailView.swift */; };
3534
8E4C63E425F6D8C8001678A1 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E4C63E325F6D8C8001678A1 /* PriceView.swift */; };
35+
8E919E71260D3945007C62C4 /* RestaurantViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E919E70260D3945007C62C4 /* RestaurantViewModel.swift */; };
36+
8E919E77260D394C007C62C4 /* Review.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E919E76260D394C007C62C4 /* Review.swift */; };
37+
8E919E7F260D3956007C62C4 /* RestaurantImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E919E7C260D3956007C62C4 /* RestaurantImageView.swift */; };
38+
8E919E80260D3956007C62C4 /* RestaurantDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E919E7D260D3956007C62C4 /* RestaurantDetailView.swift */; };
39+
8E919E81260D3956007C62C4 /* ReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E919E7E260D3956007C62C4 /* ReviewView.swift */; };
3640
DE8564AE23AFBF8000611383 /* FirestoreUITest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564AD23AFBF8000611383 /* FirestoreUITest.m */; };
3741
DE8564B423AFBFA700611383 /* FIREGSignInHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564B123AFBFA700611383 /* FIREGSignInHelper.m */; };
3842
DE8564B523AFBFA700611383 /* FIREGHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564B223AFBFA700611383 /* FIREGHelper.m */; };
@@ -88,8 +92,12 @@
8892
8E4C637725EEF4CA001678A1 /* RestaurantListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestaurantListViewModel.swift; sourceTree = "<group>"; };
8993
8E4C63B125F05E76001678A1 /* StarsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarsView.swift; sourceTree = "<group>"; };
9094
8E4C63D725F6D641001678A1 /* Firestore+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Firestore+Extension.swift"; sourceTree = "<group>"; };
91-
8E4C63DD25F6D879001678A1 /* ImageThumbnailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageThumbnailView.swift; sourceTree = "<group>"; };
9295
8E4C63E325F6D8C8001678A1 /* PriceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceView.swift; sourceTree = "<group>"; };
96+
8E919E70260D3945007C62C4 /* RestaurantViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestaurantViewModel.swift; sourceTree = "<group>"; };
97+
8E919E76260D394C007C62C4 /* Review.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Review.swift; sourceTree = "<group>"; };
98+
8E919E7C260D3956007C62C4 /* RestaurantImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestaurantImageView.swift; sourceTree = "<group>"; };
99+
8E919E7D260D3956007C62C4 /* RestaurantDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestaurantDetailView.swift; sourceTree = "<group>"; };
100+
8E919E7E260D3956007C62C4 /* ReviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReviewView.swift; sourceTree = "<group>"; };
93101
DE8564AC23AFBF8000611383 /* FirestoreExampleUITests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FirestoreExampleUITests-Bridging-Header.h"; sourceTree = "<group>"; };
94102
DE8564AD23AFBF8000611383 /* FirestoreUITest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FirestoreUITest.m; sourceTree = "<group>"; };
95103
DE8564B023AFBFA700611383 /* FIREGHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIREGHelper.h; sourceTree = "<group>"; };
@@ -230,9 +238,11 @@
230238
isa = PBXGroup;
231239
children = (
232240
8E4C62D225E9CFE0001678A1 /* RestaurantListView.swift */,
241+
8E919E7D260D3956007C62C4 /* RestaurantDetailView.swift */,
242+
8E919E7C260D3956007C62C4 /* RestaurantImageView.swift */,
243+
8E919E7E260D3956007C62C4 /* ReviewView.swift */,
233244
8E4C62E625E9D191001678A1 /* RestaurantItemView.swift */,
234245
8E4C63B125F05E76001678A1 /* StarsView.swift */,
235-
8E4C63DD25F6D879001678A1 /* ImageThumbnailView.swift */,
236246
8E4C63E325F6D8C8001678A1 /* PriceView.swift */,
237247
);
238248
path = Views;
@@ -242,6 +252,7 @@
242252
isa = PBXGroup;
243253
children = (
244254
8E4C634825EDB793001678A1 /* Restaurant.swift */,
255+
8E919E76260D394C007C62C4 /* Review.swift */,
245256
);
246257
path = Model;
247258
sourceTree = "<group>";
@@ -250,6 +261,7 @@
250261
isa = PBXGroup;
251262
children = (
252263
8E4C637725EEF4CA001678A1 /* RestaurantListViewModel.swift */,
264+
8E919E70260D3945007C62C4 /* RestaurantViewModel.swift */,
253265
);
254266
path = ViewModels;
255267
sourceTree = "<group>";
@@ -473,11 +485,15 @@
473485
8E1E43B125F961EF00BC64D3 /* Restaurant+Extension.swift in Sources */,
474486
8E4C62D325E9CFE0001678A1 /* RestaurantListView.swift in Sources */,
475487
8E4C634925EDB793001678A1 /* Restaurant.swift in Sources */,
488+
8E919E80260D3956007C62C4 /* RestaurantDetailView.swift in Sources */,
476489
8E4C637825EEF4CA001678A1 /* RestaurantListViewModel.swift in Sources */,
477490
8E4C62D125E9CFE0001678A1 /* FirestoreSwiftUIExampleApp.swift in Sources */,
491+
8E919E81260D3956007C62C4 /* ReviewView.swift in Sources */,
478492
8E4C63B225F05E76001678A1 /* StarsView.swift in Sources */,
493+
8E919E77260D394C007C62C4 /* Review.swift in Sources */,
494+
8E919E7F260D3956007C62C4 /* RestaurantImageView.swift in Sources */,
479495
8E4C63E425F6D8C8001678A1 /* PriceView.swift in Sources */,
480-
8E4C63DE25F6D879001678A1 /* ImageThumbnailView.swift in Sources */,
496+
8E919E71260D3945007C62C4 /* RestaurantViewModel.swift in Sources */,
481497
8E4C62E725E9D191001678A1 /* RestaurantItemView.swift in Sources */,
482498
8E4C63D825F6D641001678A1 /* Firestore+Extension.swift in Sources */,
483499
);

firestore/FirestoreSwiftUIExample/Model/Restaurant.swift

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import FirebaseFirestoreSwift
2222

2323
struct Restaurant: Identifiable, Codable {
2424
var id: String = UUID().uuidString
25+
var reference: DocumentReference?
2526

2627
var name: String
2728
var category: String // Could become an enum
@@ -32,6 +33,7 @@ struct Restaurant: Identifiable, Codable {
3233
var photo: URL
3334

3435
enum CodingKeys: String, CodingKey {
36+
case reference
3537
case name
3638
case category
3739
case city
@@ -41,19 +43,3 @@ struct Restaurant: Identifiable, Codable {
4143
case photo
4244
}
4345
}
44-
45-
struct Review: Codable {
46-
var rating: Int // Can also be enum
47-
var userID: String
48-
var username: String
49-
var text: String
50-
var date: Timestamp
51-
52-
enum CodingKeys: String, CodingKey {
53-
case rating
54-
case userID = "userId"
55-
case username = "userName"
56-
case text
57-
case date
58-
}
59-
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// Review.swift
3+
// FirestoreSwiftUIExample
4+
//
5+
// Copyright (c) 2021 Google Inc.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Firebase
21+
import FirebaseFirestoreSwift
22+
23+
struct Review: Identifiable, Codable {
24+
var id: String = UUID().uuidString
25+
26+
var rating: Int // Can also be enum
27+
var userID: String
28+
var username: String
29+
var text: String
30+
var date: Timestamp
31+
32+
enum CodingKeys: String, CodingKey {
33+
case rating
34+
case userID = "userId"
35+
case username = "userName"
36+
case text
37+
case date
38+
}
39+
}

firestore/FirestoreSwiftUIExample/ViewModels/RestaurantListViewModel.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,15 @@ class RestaurantListViewModel: ObservableObject {
4747
guard let self = self else { return }
4848
self.restaurants = documents.compactMap { document in
4949
do {
50-
return try document.data(as: Restaurant.self)
50+
var restaurant = try document.data(as: Restaurant.self)
51+
restaurant?.reference = document.reference
52+
return restaurant
5153
} catch let error {
5254
print(error)
5355
return nil
5456
}
5557
}
58+
5659
}
5760
}
5861
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// RestaurantViewModel.swift
3+
// FirestoreSwiftUIExample
4+
//
5+
// Copyright (c) 2021 Google Inc.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Firebase
21+
import Combine
22+
23+
class RestaurantViewModel: ObservableObject {
24+
var restaurant: Restaurant
25+
26+
@Published var reviews = [Review]()
27+
private var db = Firestore.firestore()
28+
private var listener: ListenerRegistration?
29+
30+
init(restaurant : Restaurant) {
31+
self.restaurant = restaurant
32+
}
33+
34+
deinit {
35+
unsubscribe()
36+
}
37+
38+
func unsubscribe() {
39+
if listener != nil {
40+
listener?.remove()
41+
listener = nil
42+
}
43+
}
44+
45+
func subscribe() {
46+
if listener == nil {
47+
48+
listener = restaurant.reference!.collection("ratings").addSnapshotListener { [weak self] (querySnapshot, error) in
49+
guard let documents = querySnapshot?.documents else {
50+
print("Error fetching documents: \(error!)")
51+
return
52+
}
53+
54+
guard let self = self else { return }
55+
self.reviews = documents.compactMap { document in
56+
do {
57+
return try document.data(as: Review.self)
58+
} catch let error {
59+
print(error)
60+
return nil
61+
}
62+
}
63+
64+
print(self.reviews)
65+
}
66+
}
67+
}
68+
}

firestore/FirestoreSwiftUIExample/Views/PriceView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import SwiftUI
2121

2222
struct PriceView: View {
2323
var price: Int
24+
var color: Color
2425

2526
var body: some View {
2627
Text(priceString(from: price))
2728
.font(.footnote)
28-
.foregroundColor(Color.gray)
29+
.foregroundColor(color)
2930
.multilineTextAlignment(.trailing)
3031
}
3132

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//
2+
// RestaurantDetailView.swift
3+
// FirestoreSwiftUIExample
4+
//
5+
// Copyright (c) 2021 Google Inc.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import SwiftUI
21+
import SDWebImageSwiftUI
22+
23+
struct RestaurantDetailView: View {
24+
@ObservedObject var viewModel: RestaurantViewModel
25+
26+
var body: some View {
27+
let restaurant = viewModel.restaurant
28+
29+
VStack {
30+
VStack {
31+
Spacer()
32+
.frame(height: 100)
33+
VStack(alignment: .leading) {
34+
HStack {
35+
Text(restaurant.name)
36+
.font(.title2)
37+
.bold()
38+
.frame(alignment: .leading)
39+
Spacer()
40+
PriceView(price: restaurant.price, color: Color.white)
41+
}
42+
StarsView(
43+
rating: Int(restaurant.averageRating.rounded()),
44+
color: Color.white,
45+
outlineColor: Color.white)
46+
HStack {
47+
Text(restaurant.category)
48+
Text("")
49+
Text(restaurant.city)
50+
}
51+
.font(.subheadline)
52+
}
53+
.padding()
54+
.foregroundColor(Color.white)
55+
.background(TransparentRectangleView())
56+
}
57+
.background(RestaurantImageView(imageURL: restaurant.photo, isThumbnail: false))
58+
List(viewModel.reviews) { review in
59+
ReviewView(review: review)
60+
}
61+
}
62+
.navigationBarTitle(restaurant.name, displayMode: .inline)
63+
.toolbar {
64+
Button("Add") {
65+
print("add")
66+
}
67+
}
68+
.onAppear() {
69+
viewModel.subscribe()
70+
}
71+
.onDisappear() {
72+
viewModel.unsubscribe()
73+
}
74+
}
75+
}
76+
77+
struct TransparentRectangleView: View {
78+
var body: some View {
79+
Rectangle()
80+
.foregroundColor(Color.black)
81+
.opacity(0.4)
82+
}
83+
}
84+
85+
struct RestaurantDetailView_Previews: PreviewProvider {
86+
static var previews: some View {
87+
let restaurant = Restaurant(name: "Pizza Place", category: "Pizza", city: "Austin", price: 2,
88+
ratingCount: 1, averageRating: 4,
89+
photo: Restaurant.imageURL(forName: "Pizza Place"))
90+
RestaurantDetailView(viewModel: RestaurantViewModel(restaurant: restaurant))
91+
}
92+
}

firestore/FirestoreSwiftUIExample/Views/ImageThumbnailView.swift renamed to firestore/FirestoreSwiftUIExample/Views/RestaurantImageView.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@
2020
import SwiftUI
2121
import SDWebImageSwiftUI
2222

23-
struct ImageThumbnailView: View {
23+
struct RestaurantImageView: View {
2424
var imageURL: URL
25+
var isThumbnail: Bool
2526

2627
var body: some View {
27-
WebImage(url: imageURL)
28-
.resizable()
29-
.placeholder(Image(systemName: "photo"))
30-
.aspectRatio(1, contentMode: .fill)
31-
.frame(width: 100, height: 100, alignment: .leading)
28+
if (isThumbnail) {
29+
WebImage(url: imageURL)
30+
.resizable()
31+
.aspectRatio(1, contentMode: .fill)
32+
.frame(width: 100, height: 100, alignment: .leading)
33+
} else {
34+
WebImage(url: imageURL)
35+
.resizable()
36+
.aspectRatio(contentMode: .fill)
37+
}
3238
}
3339
}

0 commit comments

Comments
 (0)