Data not showing in Swiftui using Firebase - firebase

Can someone tell me what I am doing wrong? I am using Swiftui and firebase database. I am not seeing any error or any data on the screen. I did install the Pods and checked the security rules as well in console. I tried couple other methods, but this was exactly same from youtube tutorials except the collection name and fields.
import SwiftUI
import Firebase
struct Calories: View {
#ObservedObject var data = getData()
var body: some View {
NavigationView{
ZStack(alignment: .top){
GeometryReader{_ in
// Home View....
Text("Home")
}.background(Color("Color").edgesIgnoringSafeArea(.all))
CustomSearchBar(data: self.$data.datas).padding(.top)
}.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
struct Calories_Previews: PreviewProvider {
static var previews: some View {
Calories()
}
}
struct CustomSearchBar : View {
#State var txt = ""
#Binding var data : [dataType]
var body : some View{
VStack(spacing: 0){
HStack{
TextField("Search", text: self.$txt)
if self.txt != ""{
Button(action: {
self.txt = ""
}) {
Text("Cancel")
}
.foregroundColor(.black)
}
}.padding()
if self.txt != ""{
if self.data.filter({$0.item.lowercased().contains(self.txt.lowercased())}).count == 0{
Text("No Results Found").foregroundColor(Color.black.opacity(0.5)).padding()
}
else{
List(self.data.filter{$0.item.lowercased().contains(self.txt.lowercased())}){i in
NavigationLink(destination: Detail(data: i)) {
Text(i.item)
}
}.frame(height: UIScreen.main.bounds.height / 5)
}
}
}.background(Color.white)
.padding()
}
}
class getData : ObservableObject{
#Published var datas = [dataType]()
init() {
let db = Firestore.firestore()
db.collection("HSCal").getDocuments { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
for i in snap!.documents{
let id = i.documentID
let item = i.get("item") as! String
let cal = i.get("cal") as! String
self.datas.append(dataType(id: id, item: item, cal: cal))
}
}
}
}
struct dataType : Identifiable {
var id : String
var item : String
var cal : String
}
struct Detail : View {
var data : dataType
var body : some View{
Text(data.item)
}
}

did you put app bundle?
try in
struct Calories: View {
#EnvironmentObject var List: getData()
....
}
call
Calories().environmentObject(DataList)
declare somewhere
var DataList = getData()

Related

Every time view is seen, the name keeps on adding to itself

I know that this sounds a bit dumb, but how do you call a function only once. I have a tab bar at the bottom of my app, and every time that it is called, the name that I got from my firebase database, keeps on being added. For example, the name in firebase is Bob. The app for the first time will display Bob. Then you would click on the settings, and go back to the home view. Then the app will say BobBob, and over and over again. How do I make this stop.
Code:
import SwiftUI
import Firebase
struct HomeView: View {
#State var name = ""
var body: some View {
NavigationView {
ZStack {
VStack{
Text("Welcome \(name)")
.font(.title)
Text("Upcoming Lessions/Reservations:")
.bold()
.padding()
Divider()
}
}
}
.navigationTitle("Home")
.onAppear(perform: {
downloadNameServerData()
})
}
private func downloadNameServerData() {
let db = Firestore.firestore()
db.collection("users").addSnapshotListener {(snap, err) in
if err != nil{
print("\(String(describing: err))")
return
}
for i in snap!.documentChanges {
_ = i.document.documentID
if let Name = i.document.get("Name") as? String {
DispatchQueue.main.async {
name.append(Name)
print("\(name)")
}
}
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
import SwiftUI
import Firebase
struct HomeView: View {
#State var name = ""
var body: some View {
NavigationView {
ZStack {
VStack{
Text("Welcome \(name)")
.font(.title)
Text("Upcoming Lessions/Reservations:")
.bold()
.padding()
Divider()
}
}
}
.navigationTitle("Home")
.onAppear(perform: {
downloadNameServerData()
})
}
private func downloadNameServerData() {
if !name.isEmpty { return }
let db = Firestore.firestore()
db.collection("users").addSnapshotListener {(snap, err) in
if err != nil{
print("\(String(describing: err))")
return
}
for i in snap!.documentChanges {
_ = i.document.documentID
if let Name = i.document.get("Name") as? String {
DispatchQueue.main.async {
name = Name
print("\(name)")
}
}
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
Did you consider only loading the name if you don't have one yet?
.onAppear(perform: {
if (name == null) downloadNameServerData()
})

Access Data from Firestore and Put it in SwiftUI EnvironmentObject

I am trying to use Firestore and get the data from the Firestore and then put it in EnvironmentObject. But it is not working out. I tried different approaches each ended with different errors.
Here is my code:
class FirebaseManager: ObservableObject {
#Published var tweets: [Tweet] = []
private let db: Firestore = Firestore.firestore()
init() {
db.collection("tweets")
.addSnapshotListener { snapshot, error in
if let error {
print(error.localizedDescription)
return
}
let tweets = snapshot?.documents.compactMap({ document in
try? document.data(as: Tweet.self)
})
if let tweets {
DispatchQueue.main.async {
self.tweets = tweets
}
}
}
}
}
Then I try to call FirebaseManager in the code below:
struct HomeTimelineScreen: View {
#EnvironmentObject var appState: AppState
#StateObject private var firebaseManager = FirebaseManager()
private var cancellables = Set<AnyCancellable>()
init() {
// ERROR Escaping closure captures mutating 'self' parameter
firebaseManager.$tweets.sink { tweets in
appState.tweets = tweets
}
// ERROR/WARNING ObservableObject of type AppState found. A View.environmentObject(_:) for AppState may be missing as an ancestor of this view.
firebaseManager.$tweets.assign(to: \.appState.tweets, on: self)
.store(in: &cancellables)
}
The EnvironmentObject AppState is injected in the TwitterApp main file as shown below:
#main
struct TwitterAppApp: App {
#ObservedObject var coordinator = Coordinator()
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
NavigationStack(path: $coordinator.path) {
LandingScreen()
.navigationDestination(for: Route.self) { route in
switch route {
case .login:
LoginScreen().appLogoToolbar()
case .register:
RegistrationScreen().appLogoToolbar()
case .home:
HomeScreen()
case .detail(let tweet):
TweetDetailsScreen(tweet: tweet)
}
}
}.environmentObject(coordinator)
.environmentObject(AppState())
}
}
}
AppState:
import Foundation
class AppState: ObservableObject {
#Published var tweets: [Tweet] = []
}
UPDATE: My biggest issue is on how to re-render the TweetDetailScreen since it is also using the TweetCellView.
struct TweetDetailsScreen: View {
let tweet: Tweet
var body: some View {
List {
TweetCellView(tweet: tweet)
ForEach(1...20, id: \.self) { index in
Text("\(index)")
}
}
}
}
I took your advice and added single source of truth FirebaseManager and now it works as I want it to be.
struct TweetDetailsScreen: View {
#EnvironmentObject var firebaseManager: FirebaseManager
let tweet: Tweet
var body: some View {
List {
if let tweet = firebaseManager.findByDocumentId(tweet.documentID ?? "") {
TweetCellView(tweet: tweet)
}
ForEach(1...20, id: \.self) { index in
Text("\(index)")
}
}
}
}
class FirebaseManager: ObservableObject {
#Published var tweets: [Tweet] = []
private let db: Firestore = Firestore.firestore()
init() {
db.collection("tweets")
.addSnapshotListener { snapshot, error in
if let error {
print(error.localizedDescription)
return
}
let tweets = snapshot?.documents.compactMap({ document in
try? document.data(as: Tweet.self)
})
if let tweets {
DispatchQueue.main.async {
self.tweets = tweets
}
}
}
}
func findByDocumentId(_ documentId: String) -> Tweet? {
guard let index = tweets.firstIndex(where: { $0.documentID == documentId }) else { return nil }
return tweets[index]
}
}

Show multiple users' location stored in a database on a map with SwiftUI and Firebase

As said in the title, I would like to show the location of my users on a map in my app. I would like something like Snapshat map :
Snapshat Map Picture
I used SwiftUI and Firebase.
Here is what I have done so far :
import SwiftUI
import Firebase
import CoreLocation
import MapKit
struct mapTimelineView: View {
#StateObject private var locationViewModel = LocationViewModel.shared
#State private var showNewPostView = false
#ObservedObject var viewModel = TimelineViewModel()
#ObservedObject var authViewModel = AuthViewModel()
#ObservedObject var obs = observer()
var body: some View {
ZStack (alignment: .bottomTrailing) {
// Map(coordinateRegion: $locationViewModel.region, showsUserLocation: true)
// .accentColor(Color("accentColor"))
// .edgesIgnoringSafeArea(.all)
mapView(geopoints: self.obs.data["data"] as! [String : GeoPoint]) <--- /!\ the problem occurs here /!\
Button {
showNewPostView.toggle()
} label: {
Image(systemName: "plus")
.resizable()
.renderingMode(.template)
.frame(width: 30, height: 30)
.font(.system(size: 30, weight: .bold, design: .default))
.padding()
}
.background(Color("accentColor"))
.foregroundColor(Color("backgroundColor"))
.clipShape(Circle())
.padding()
.shadow(radius: 20)
.fullScreenCover(isPresented: $showNewPostView) {
uploadPostView()
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("home")
.background(Color("backgroundColor"))
}
}
struct mapTimelineView_Previews: PreviewProvider {
static var previews: some View {
mapTimelineView()
}
}
struct mapView: UIViewRepresentable {
#ObservedObject var authViewModel = AuthViewModel()
var geopoints : [String: GeoPoint]
func makeCoordinator() -> Coordinator {
return mapView.Coordinator(parent1: self)
}
let map = MKMapView()
let manager = CLLocationManager()
func makeUIView(context: Context) -> MKMapView {
manager.delegate = context.coordinator
manager.startUpdatingLocation()
map.showsUserLocation = true
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 48.856614, longitude: 2.3522219), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
map.region = region
return map
}
func updateUIView(_ uiView: MKMapView, context: Context) {
for i in geopoints {
let point = MKPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: i.value.latitude, longitude: i.value.longitude)
point.title = i.key
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotation(point)
}
}
class Coordinator: NSObject, CLLocationManagerDelegate {
#ObservedObject var authViewModel = AuthViewModel()
var parent: mapView
init(parent1: mapView) {
parent = parent1
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let uid = self.authViewModel.userSession?.uid else { return }
let last = locations.last
Firestore.firestore().collection("locations").document("coordinate").setData(["updates" : [uid : GeoPoint(latitude: (last?.coordinate.latitude)!, longitude: (last?.coordinate.longitude)!)]],merge: true) { (err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
print("success")
}
}
}
}
class observer : ObservableObject{
#Published var data = [String : Any]()
init() {
let db = Firestore.firestore()
db.collection("locations").document("coordinate").addSnapshotListener { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
let updates = snap?.get("updates") as! [String : GeoPoint]
self.data["data"] = updates
}
}
}
It shows a map and the user location.
But my problem is that my app crash because there is "nil" on this line :
mapView(geopoints: self.obs.data["data"] as! [String : GeoPoint])
I got this error message :
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
And, I don't understand this issue because I do have data in my database :
So, if anyone has a solution, I would like to know it.

Trying to show fetched firebase data on profile view SwiftUI

I'm trying to show the data I fetched from my Firebase database. I tried creating #State var variables and add them to my function but it didn't work. I tried printing my function output in a button to print it to console and it works. I just don't know how to show them in my view my code
import SwiftUI
import Firebase
struct ProfileView: View {
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text("hello")
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
print(name)
// ...
}) { error in
print(error.localizedDescription)
}
}
}
Just create a #State variable, which contains the name. If your function changes that variable, your view will updates.
struct profile: View {
#State var name : String = ""
var body: some View {
Text("Hello " + self.name)
And then in your function, instead of printing you will assign it to your state.
let name = value?["fullName"] as? String ?? ""
print(name)
self.name = name
That should work. I do not have an example with Firebase at the moment, so I can not test it. If it is not working, please describe the behavior.
Adding an #State property profileName and assigning it in the network request function will work after tapping the Button.
// ProfileView.swift
//
//
// Created by Shahin Bararesh on 2020-09-07.
//
import SwiftUI
import Firebase
struct ProfileView: View {
#State var profileName: String = ""
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text(profileName)
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
self.profileName = name
// ...
}) { error in
print(error.localizedDescription)
}
}
}

SWIFTUI Call Key Dictionary not work with the error: 'Subscript index of type '() -> Bool' in a key path must be Hashable'

I have this view:
import SwiftUI
struct SectionView1: View {
let dateStr:String
#Binding var isSectionView:Bool
var body: some View {
HStack {
Button(action: {
self.isSectionView.toggle()
}) {
Image(systemName: isSectionView ? "chevron.down.circle" : "chevron.right.circle")
}
Text("Media del \(dateStr)")
}
}
}
which will be called from view:
import SwiftUI
import Photos
struct MediaView: View {
let geoFolder:GeoFolderCD
#State private var assetsForDate = [String :[PHAsset]]()
#State private var isSectionViewArray:[String:Bool] = [:]
var body: some View {
List {
ForEach(assetsForDate.keys.sorted(by: > ), id: \.self) { dateStr in
Section {
SectionView1(dateStr: dateStr,
isSectionView: self.$isSectionViewArray[dateStr, default: true])
}
}
}
.onAppear {
self.assetsForDate = FetchMediaUtility().fetchGeoFolderAssetsForDate(geoFolder: geoFolderStruct, numAssets: numMediaToFetch)
for dateStr in self.assetsForDate.keys.sorted() {
self.isSectionViewArray[dateStr] = true
}
}
}
}
but I have the error: Subscript index of type '() -> Bool' in a key path must be Hashable in isSectionView: self.$isSectionViewArray[dateStr, default: true]
Why isSectionViewArray:[String:Bool] = [:] is not Hasbable?
How can modify the code for work?
If I remove, in SectionView, #Binding var isSectionView:Bool, the code work fine, or if I set, from SectionView, #Binding var isSectionViewArray:[String:Bool] = [:], the code work fine.
You can write your own binding with the below code and it should work
var body: some View {
List {
ForEach(assetsForDate.keys.sorted(by: > ), id: \.self) { dateStr in
let value = Binding<Bool>(get: { () -> Bool in
return self.isSectionViewArray[dateStr, default: true]
}) { (value) in
}
Section {
SectionView1(dateStr: dateStr,
isSectionView: value)
}
}
}
.onAppear {
self.assetsForDate = FetchMediaUtility().fetchGeoFolderAssetsForDate(geoFolder: geoFolderStruct, numAssets: numMediaToFetch)
for dateStr in self.assetsForDate.keys.sorted() {
self.isSectionViewArray[dateStr] = true
}
}
}

Resources