Update toggle SwiftUI toggle switch with Firebase - firebase

Despite the simplicity of the question I have not been able to find a satisfactory answer yet. I want to update toggle switches based on value in Firebase. I have added listeners but run into problems converting a Bool to Binding Bool, any help is appreciated.
struct oneSeqeuncer : Identifiable{
var id: String
var status: Bool
}
struct Sequencers: View {
#ObservedObject var seqModel = SequencerModel()
#State private var novaseq404A :Bool = true
#State private var novaseq404B :Bool = true
#State private var novaseq297A :Bool = true
#State private var novaseq297B :Bool = true
var body: some View {
ZStack{
VStack{
Text("Sequencers")
.foregroundColor(.white)
.font(.title)
.fontWeight(.bold)
.padding()
List{
HStack{
Text("404")
.font(.title)
.padding()
Toggle("", isOn: $novaseq404A)
.onChange(of: novaseq404A) { newValue in
updateStatus(name: "404A", status: novaseq404A)
}
Toggle("", isOn: $novaseq404B)
.padding()
.onChange(of: novaseq404B) { newValue in
updateStatus(name: "404B", status: novaseq404B)
}
}
HStack{
Text("297")
.font(.title)
.padding()
Toggle("", isOn: $novaseq297A)
.onChange(of: novaseq297A) { newValue in
updateStatus(name: "297A", status: novaseq297A)
}
Toggle("", isOn: $novaseq297B)
.padding()
.onChange(of: novaseq297B) { newValue in
updateStatus(name: "297B", status: novaseq297B)
}
}
}
}
}.onAppear(){
self.seqModel.fetchData()
for seq in seqModel.seqs{
if seq.id == "404A"{
novaseq404A = seq.status
}
if seq.id == "404B"{
novaseq404A = seq.status
}
if seq.id == "297A"{
novaseq297A = seq.status
}
if seq.id == "297B"{
novaseq297B = seq.status
}
}
func updateStatus(name: String, status: Bool){
let timeInterval = NSDate().timeIntervalSince1970
let myInt = Int(timeInterval)
let db = Firestore.firestore()
if status == false{
db.collection("Sequencers").document(name).updateData([
"status": false,
"lastChange" : myInt
]){ error in
if error != nil{
print(error!)
}
}
}
else{
let docRef = db.collection("Sequencers").document(name)
docRef.getDocument {(document, error) in
if error != nil {
print(error!)
}
if let document = document, document.exists{
let data = document.data()
if let lastChange = data!["lastChange"]! as? Int{
let timeOff = myInt - lastChange
if let timeOffTotal = data!["timeOff"]! as? Int{
let newTimeOff = timeOffTotal + timeOff
db.collection("Sequencers").document(name).updateData([
"timeOff" : newTimeOff
])
}
}
db.collection("Sequencers").document(name).updateData([
"previousChange": data!["lastChange"]!,
"status": true ,
"lastChange" : myInt
])
}
}
}
}
}
struct Sequencers_Previews: PreviewProvider {
static var previews: some View {
Sequencers()
}
}
Below is my model for storing 'sequencers'
import Foundation
import FirebaseFirestore
import Firebase
class SequencerModel : ObservableObject {
#Published var seqs = [oneSeqeuncer]()
private var db = Firestore.firestore()
func fetchData(){
db.collection("Sequencers").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.seqs = documents.map { queryDocumentSnapshot -> oneSeqeuncer in
let data = queryDocumentSnapshot.data()
let id = queryDocumentSnapshot.documentID
let status = data["status"] as? Bool
print(id)
print(status as Any)
return oneSeqeuncer(id: id, status: status!)
}
}
}
}

My solution was not ideal but solved, I realized the function .fetchData() that I was calling to was taking too long to respond. Ideally I should use some completion handler... However I simply changed my TabView on ContentView to display another page first, to allow time for my call to Firebase to finish, which allowed my for loop in .onAppear to have a non empty iterable. Again not sure this really belongs as an "Answer" but just wanted to share my temp solution as an option.

Related

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.

Generic parameter 'Success' could not be inferred; Key path value type '_' cannot be converted to contextual type '_'; Cannot find 'Response' in scope

I think this might be the last push for my project of mobile GitHub query repository search, but I get 3 errors I cannot find out how to cope with.
The code:
import SwiftUI
import Combine
struct Root: Codable {
let items: [Item]
enum CodingKeys: String, CodingKey {
case items
}
}
struct Item: Identifiable, Codable {
let id: Int
let urlCode: String
let fullName: String
enum CodingKeys: String, CodingKey {
case id
case urlCode = "url"
case fullName = "full_name"
}
}
private final class ContentViewState: ObservableObject {
#Published var isLoading = false
#Published var query = ""
#Published var stuff = [String]()
private var subscription: AnyCancellable?
func fetchRepos(query: String) {
isLoading = true
subscription = Just("test")
.delay(for: 2, scheduler: RunLoop.main)
.sink(receiveValue: {[weak self] (title: String) in
self?.isLoading = false
self?.stuff.append(title)
})
}
}
struct ContentView: View {
#StateObject private var state = ContentViewState()
#State private var items = [Item]()
var body: some View {
VStack {
if state.isLoading {
ProgressView()
} else {
HStack {
TextField("Enter search", text: $state.query)
Button("Search") {
state.fetchRepos(query: state.query)
}
}
List(items, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.fullName).font(.headline)
Text(item.urlCode)
}
}.task {
await loadData()
}
}
}
}
func loadData() async {
guard let url = URL(string: "https://api.github.com/search/repositories?q=" + state.query + "&per_page=20") else
{
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Root.self, from: data) {
items = decodedResponse.items
}
} catch {
print("Invalid data ")
}
}
}
The errors:
"Generic parameter 'Success' could not be inferred" on line:
TextField("Enter search", text: $state.query)
"Key path value type '' cannot be converted to contextual type ''" on line:
await loadData()
}
}
"Cannot find 'Response' in scope" on line:
} catch {
print("Invalid data ")
}
}
}
Please help :)
The answer was to move the code to another file, change its structure a bit and everything works fine now!

Abrupt transition between views within a Navigation View

I'm having trouble figuring out how to smoothly navigate from my SignInView() to my FirstView(). I have my FirstView() inside of a Navigation Stack, but the transition between the views is very abrupt and devoid of the transition that you normally get with the use of a NavigationLink. How can I get the transition to work?
Much appreciated!
Here is the relevant code...
struct ContentView: View {
#EnvironmentObject var viewModel: AppViewModel
var body: some View {
VStack{
NavigationView {
if viewModel.signedIn {
FirstView()
.transition(.slide)
} else {
//.onAppear method is used for keyboard management (See Misc Functions...)
SignInView()
.onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
.navigationBarHidden(true)
}
}
.onAppear {
viewModel.listen()
}
}
}
}
class AppViewModel: ObservableObject {
private var db = Firestore.firestore()
#Published var userInfo: User?
#Published var signedIn: Bool = false
var handle: AuthStateDidChangeListenerHandle?
let authRef = Auth.auth()
var authHandle : AuthStateDidChangeListenerHandle?
var rootInfoCollection : CollectionReference!
var userIdRef = ""
func fetchUserData(){
db.collection("Users").document("\(userIdRef)").getDocument { document, error in
// Check for error
if error == nil {
// Check that this document exists
if document != nil && document!.exists {
self.userInfo = document.map { (documentSnapshot) -> User in
let data = documentSnapshot.data()
let uid = data?["uid"] as? UUID ?? UUID()
let company = data?["company"] as? String ?? ""
let name = data?["name"] as? String ?? ""
let admin = data?["admin"] as? Bool ?? false
let photo = data?["photo"] as? String ?? ""
return User(uid: uid, company: company, name: name, admin: admin, photo: photo)
}
withAnimation {
self.signedIn = true
}
}
}
}
}
func listen(){
handle = authRef.addStateDidChangeListener({ auth, user in
print(user?.email ?? "No User Found")
if let user = auth.currentUser {
self.userIdRef = user.uid
self.rootInfoCollection = Firestore.firestore().collection("/Users/")
DispatchQueue.main.async {
self.fetchUserData()
}
} else {
self.signedIn = false
}
})
}
func signIn(email: String, password: String){
authRef.signIn(withEmail: email, password: password) { result, error in
guard result != nil, error == nil else {
return
}
}
}
}
struct SignInView: View {
#EnvironmentObject var viewModel: AppViewModel
#State private var username : String = ""
#State private var password : String = ""
#State private var shouldShowLoginAlert: Bool = false
#State var selectedImageArray : [Image] = []
var disableLoginButton : Bool {
return self.username.isEmpty || self.password.isEmpty
}
var body: some View {
VStack{
Image(uiImage: #imageLiteral(resourceName: "awText"))
.resizable()
.frame(width: 180, height: 100)
.padding(.bottom, 50)
TextField("Email", text: $username)
.padding(.leading)
.disableAutocorrection(true)
.autocapitalization(.none)
Rectangle().fill(Color.gray.opacity(0.25)).frame(height: 1, alignment: .center).padding(.bottom)
.padding(.bottom)
.onChange(of: self.username, perform: { value in
if value.count > 10 {
self.username = String(value.prefix(20)) //Max 10 Characters for Username.
}
})
SecureField("Password", text: $password)
.padding(.leading)
.disableAutocorrection(true)
.autocapitalization(.none)
Rectangle().fill(Color.gray.opacity(0.25)).frame(height: 1, alignment: .center)
.onChange(of: self.username, perform: { value in
if value.count > 10 {
self.username = String(value.prefix(10)) //Max 10 Characters for Password.
}
})
//SignIn Button
Button(action: {
viewModel.signIn(email: username, password: password)
}, label: {
Text("Sign In")
.disabled(disableLoginButton)
.frame(width: 300, height: 50)
.background(Color.green)
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
.padding()
})
}
Replacing the default NavigationView behavior with your own animations isn't necessarily totally straightforward. I'll lay out one possibility, but another would be to use a real NavigationView transition, but just hide the back button once you're on FirstView.
To do the transition yourself, you'll need one root element to NavigationView, an if clause, a transition(.slide) and withAnimation. Here's a simplified version of your code showing just these elements:
class AppViewModel: ObservableObject {
#Published var signedIn = false
}
struct FirstView : View {
var body: some View {
Text("Signed in")
}
}
struct ContentView: View {
#StateObject var viewModel = AppViewModel()
var body: some View {
NavigationView {
VStack {
if viewModel.signedIn {
FirstView()
.transition(.slide)
} else {
Button("Sign me in") {
withAnimation {
viewModel.signedIn = true
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.navigationBarHidden(true)
}
}
}

finding a nil value in session when accessing value in view

So i have a sessionStore:
class SessionStore: ObservableObject {
var handle: AuthStateDidChangeListenerHandle?
#Published var isLoggedIn = false
#Published var userInSession: User?
func listenAuthenticationState() {
handle = Auth.auth().addStateDidChangeListener({(auth, user) in
if let user = user {
let firestoreGetUser = Firestore.firestore().collection("users").document(user.uid)
firestoreGetUser.getDocument{(document, error) in
if let dict = document?.data() {
guard let decodedUser = try? User.init(fromDictionary: dict) else { return }
self.userInSession = decodedUser
print("decoded user = \(decodedUser)")
}
}
self.isLoggedIn = true
print("user logged in")
} else {
self.isLoggedIn = false
self.userInSession = nil
print("no one logged in")
}
})
}
func logout() {
do {
try Auth.auth().signOut()
} catch {
}
}
func unbind() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
deinit {
unbind()
}
}
Its working as expected, I am able to sign in etc.
I have the following to pull the current user data:
import Foundation
import Firebase
import FirebaseAuth
import FirebaseFirestore
class ProfileViewModel: ObservableObject {
var uid: String = ""
var email: String = ""
var username: String = ""
var profileURL: String = ""
var bio: String = ""
var occupation: String = ""
var city: String = ""
func LoadAUser(userId: String) {
Firestore.firestore().collection("users").document(userId).getDocument{(snapshot, error) in
guard let snap = snapshot else {
print("error fetching data")
return
}
let dict = snap.data()
guard let decodedUser = try? User.init(fromDictionary: dict!) else { return }
print("decoded user - load user - \(decodedUser)")
}
}
}
In my view im trying to call it like:
import SwiftUI
struct ProfileView: View {
#EnvironmentObject var session: SessionStore
#ObservedObject var profileViewModel = ProfileViewModel()
func loadUserData() {
profileViewModel.LoadAUser(userId: session.userInSession!.uid)
}
var body: some View {
VStack {
Text("Edit Profile")
.fontWeight(.semibold)
.font(.system(.title, design: .rounded))
.foregroundColor(Color("startColor"))
Spacer()
VStack(alignment: .leading) {
Text("view")
}.padding()
.onAppear(perform: loadUserData)
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
ProfileView()
}
}
Im using .onAppear(perform: loadUserData) which is causing an issue - Thread1: Fatal error: Unexpectedly found nil while unwrapping
I also tried:
init() {
profileViewModel.LoadAUser(userId: session.userInSession!.uid)
}
But this also causes the same error.
The thing is I should only be able to get to this view if I'm logged in as this already works:
struct InitialView: View {
#EnvironmentObject var session: SessionStore
func listen() {
session.listenAuthenticationState()
}
var body: some View {
Group {
if session.isLoggedIn {
MainView()
} else {
NavigationView {
SignUpView()
}
}
}.onAppear(perform: listen)
}
}
I have an initialView()
struct InitialView: View {
#EnvironmentObject var session: SessionStore
func listen() {
session.listenAuthenticationState()
}
var body: some View {
Group {
if session.isLoggedIn {
MainView()
} else {
NavigationView {
SignUpView()
}
}
}.onAppear(perform: listen)
}
}
which takes you to the MainView() which has tabs to control which screen you can navigate to, then from here i can go to ProfileView()
Anyway by the logic of provided code it is more correct to activate isLoggedIn in
let firestoreGetUser = Firestore.firestore().collection("users").document(user.uid)
firestoreGetUser.getDocument{(document, error) in
if let dict = document?.data() {
guard let decodedUser = try? User.init(fromDictionary: dict) else { return }
self.userInSession = decodedUser
print("decoded user = \(decodedUser)")
self.isLoggedIn = true // << here !!
print("user logged in")
}
}
So whats worked for me is passing in Auth instead of session data:
func loadUserData() {
profileViewModel.LoadAUser(userId: Auth.auth().currentUser!.uid)
}

Data not showing in Swiftui using 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()

Resources