import SwiftUI
import Firebase
import FirebaseFirestore
struct ContentView: View {
var body: some View {
customView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct customView : View{
#State var msg = ""
#ObservedObject var datas = observer()
var body : some View{
VStack{
List{
ForEach(datas.data){i in
Text(i.msg)
}
.onDelete { (index) in
let id = self.datas.data[index.first!].id
let db = Firestore.firestore().collection("msgs")
db.document(id).delete{(err) in
if err != nil{
print((err!.localizedDescription))
return
}
print("deleted Successfully !!!")
self.datas.data.remove(atOffsets: index)
}
}
}
HStack{
TextField("msg", text: $msg).textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
print(self.msg)
self.addData(msg1: self.msg)
}) {
Text("Add")
}.padding()
}.padding()
}
}
func addData(msg1:String){ 'Here is the code for additon'
let db = Firestore.firestore()
let msg = db.collection("msgs").document()
msg.setData(["id":msg.documentID,"msg": msg1]) { (err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
print("Success")
self.msg = ""
}
}
}
class observer : ObservableObject{
#Published var data = [datatype]()
init() {
let db = Firestore.firestore().collection("msg")
db.addSnapshotListener{(snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
for i in snap!.documentChanges{
if i.type == .added{
let msgData = datatype(id: i.document.documentID, msg: i.document.get("msg")
as! String)
self.data.append(msgData)
}
}
}
}
}
struct datatype : Identifiable {
var id : String
var msg : String
}
Here is the code for the CRUD(creation, reading, update, delete) using the firebase cloud service. When i enter the text in the text field and then hit button, it adds to firebase database, but doesn't shows up in the interface in the list of the app. Can anybody tell me where i am going wrong?
In this i am trying to add data to firebase, delete it ,read it and modify/update it. But the data entered doesn't shows up in interface.
Try to update data container explicitly on main thread, like below
if i.type == .added{
let msgData = datatype(id: i.document.documentID, msg: i.document.get("msg")
as! String)
DispatchQueue.main.async {
self.data.append(msgData)
}
}
as alternate try assignment instead of modification (however this should not be the case)
DispatchQueue.main.async {
self.data = self.data + [msgData]
}
Related
I trying to integreat firebase with my program to make true or false but I keep get the error
Cannot convert value of type 'Notifications.Type' to expected argument type 'Notifications'
Here is my code
import SwiftUI
struct ProfileHost: View {
#Environment(\.editMode) var editMode
#EnvironmentObject var modelData: ModelData
#State private var draftProfile = Profile.default
#State private var notifications = Notifications(id: "", prefersNotifications: true)
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
if editMode?.wrappedValue == .active {
Button("Cancel", role: .cancel) {
draftProfile = modelData.profile
notifications = modelData.notifications
editMode?.animation().wrappedValue = .inactive
}
}
Spacer()
EditButton()
}
if editMode?.wrappedValue == .inactive {
ProfileSummary(profile: modelData.profile, notifications: modelData.notifications)
} else {
ProfileEditor(profile: $draftProfile, notifications: $notifications)
.onAppear {
draftProfile = modelData.profile
}
.onDisappear {
modelData.profile = draftProfile
}
}
}
.padding()
}
}
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
.environmentObject(ModelData())
}
}
Modeldata is the file I made the notifications var
This the code of modeldata
import Foundation
import Combine
final class ModelData: ObservableObject {
#Published var landmarks: [Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
#Published var profile = Profile.default
#Published var notifications = Notifications.self
var features: [Landmark] {
landmarks.filter { $0.isFeatured }
}
var categories: [String: [Landmark]] {
Dictionary(
grouping: landmarks,
by: { $0.category.rawValue }
)
}
}
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
at
if editMode?.wrappedValue == .inactive {
ProfileSummary(profile: modelData.profile, notifications: modelData.notifications)
I get an error that says.
Cannot convert value of type 'Notifications.Type' to expected argument
type 'Notifications'
I tried to look at other wedsites like github and reddit
I have another question.
I have a demo app where I add a ToDo in a Firestore database. From the base View I open a .sheet with a TextEditor where I enter data and save it into Firestore database. But on dismiss the List of ToDos in the base View is gone and is not refreshed until I go to another tab in the app and return back.
I have a ViewModel where I use a Firebase snapshot listener.
Code of the base View:
import Firebase
import Foundation
import SwiftUI
import FirebaseStorage
struct HomeMenuView: View {
#ObservedObject var toDosViewModel = ToDosViewModel()
#Binding var showAddToDoView: Bool
#State private var showModifyToDoView = false
#State private var note = ""
#State private var selectedToDoId = ""
func removeRow(at offset:IndexSet) {
for index in offset {
toDosViewModel.deleteNote(noteToDelete: toDosViewModel.todos[index].id!)
}
}
var body: some View {
ZStack{
VStack (alignment: .center){
List() {
ForEach(toDosViewModel.todos) { todo in
VStack(alignment: .leading, spacing: 10) {
Text(todo.notes)
.font(.subheadline)
.foregroundColor(Color.tabBarColor)
.lineLimit(2)
.onTapGesture {
showAddToDoView = true
selectedToDoId = todo.id!
note = todo.notes
}
}
.listRowSeparatorTint(Color.tabBarColor)
}
.onDelete(perform: removeRow)
}
.listStyle(InsetGroupedListStyle())
.onAppear() {
toDosViewModel.subscribe()
}
}
}
.sheet(isPresented: $showAddToDoView) {
VStack() {
HStack () {
Button("Save") {
guard !note.isEmpty else
{ showAddToDoView = false; return }
toDosViewModel.addNote(notes: note)
note = ""
showAddToDoView = false
}
.offset(x: 20)
Spacer()
Button("Back") {
note = ""
showAddToDoView = false
}
.offset(x: -20)
}
.frame(height: 50, alignment: .center)
TextEditor(
text: $note
)
}
}
}
}
The ViewModel:
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
import UIKit
class ToDosViewModel: ObservableObject {
#Published var todos = [ToDo]()
#Published var errorMessage: String?
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
func subscribe() {
if listenerRegistration == nil {
listenerRegistration = db.collection("todos")
.order(by: "timestamp", descending: true)
.addSnapshotListener { [weak self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self?.todos = documents.compactMap { queryDocumentSnapshot in
let result = Result { try queryDocumentSnapshot.data(as: ToDo.self) }
switch result {
case .success(let todo):
if let todo = todo {
self?.errorMessage = nil
return todo
}
else {
self?.errorMessage = "Document doesn't exist."
return nil
}
case .failure(let error):
switch error {
case DecodingError.typeMismatch(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.valueNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.keyNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.dataCorrupted(let key):
self?.errorMessage = "\(error.localizedDescription): \(key)"
default:
self?.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
return nil
}
}
}
}
}
func addNote(notes: String) {
db.collection("todos").document().setData(["notes" : notes, "timestamp" : FieldValue.serverTimestamp()])
}
func modifyNote(noteToModify: String, notes: String) {
db.collection("todos").document(noteToModify).setData(["notes" : notes, "timestamp" : FieldValue.serverTimestamp()])
}
func deleteNote(noteToDelete: String) {
db.collection("todos").document(noteToDelete).delete()
}
}
Any idea what the issue could be?
Thanks a lot for your support.
Change
#ObservedObject var toDosViewModel = ToDosViewModel()
To
#StateObject var toDosViewModel = ToDosViewModel()
// I am trying to pass in the value of the #AppStorage onto the class using #Binding.
// Declaration of #AppStorge, this the the single source of truth.
// I get the following error on the SigninViewModel = Class 'SigninViewModel' has no initializers
import SwiftUI
struct MainView: View {
#AppStorage("userSignIn") var userSignIn = false
var body: some View {
ZStack {
TabView {
AppointmentsView()
.tabItem {
Image(systemName: "calendar")
Text("Appointments")
}
AccountView(userSignIn: .constant(true))
.tabItem {
Image(systemName: "person")
Text("Profile")
}
StatsView()
.tabItem {
Image(systemName: "chart.bar")
Text("Stats")
}
}
}
}
}
// This the class with the #Binding is being declared and where I am trying to have access to the #AppStorae value.
class SigninViewModel: ObservableObject {
#Published var nonce = ""
#Binding var userSignIn: Bool
func authenticate(credential: ASAuthorizationAppleIDCredential) {
// getting Token...
guard let token = credential.identityToken else {
print("Error with Firebase")
return
}
//Token String...
guard let tokenString = String(data: token, encoding: .utf8) else {
print("Error with Token")
return
}
let firebaseCredential = OAuthProvider.credential(withProviderID: "apple.com",
idToken: tokenString,
rawNonce: nonce)
Auth.auth().signIn(with: firebaseCredential) { (result, err) in
if let error = err {
print(error.localizedDescription)
return
}
// User succesfully logged into Firebase...
print("Logged in Success")
// Directing user to Main page...
withAnimation(.easeInOut) {
self.userSignIn = true
}
}
}
}
You can put the #AppStorage("userSignIn") inside the ObservableObject, but your code quickly becomes a mess.
I think a more simple structure would be to put all of your authentication state inside a single AuthenticationModel: ObservableObject and use it as a #StateObject in the MainView. Additionally, I would recommend pushing it up the view tree.
class AuthenticationModel: ObservableObject {
#Published var nonce = ""
#Published var userSignIn: Bool
init() {
let defaults = UserDefaults.standard
userSignIn = defaults.bool(forKey: "userSignIn")
}
func authenticate(credential: ASAuthorizationAppleIDCredential)
...
// Successful Login
let defaults = UserDefaults.standard
defaults.set(true, forKey: "userSignIn")
// Update the views
userSignIn = true
}
}
struct MainView: View {
#StateObject var authModel: AuthenticationModel = AuthenticationModel()
var body: some View {
if authModel.userSignIn {
Home(authModel: authModel)
} else {
SignIn(authModel: authModel)
}
}
}
struct SignIn: View {
#ObservedObject var authModel: AuthenticationModel
var body: some View {
Button("Sign In") {
authModel.authenticate(...)
}
}
}
I have SwiftUI view which I want to change after checking user log-pass. I'm trying to change isAuth var like you can see below.
import SwiftUI
struct Auth : View, AuthProtocol {
#State private var isAuth = false
init() {
userManager.notifier = self
}
var body : some View {
if isAuth {
WelcomeView()
} else {
VStack {
Divider()
Text("Please, wait a minute...")
Divider()
}
.frame(width: 450, height: 350)
}
}
func passAuth() {
if userManager.validateUser() {
self.isAuth.toggle()
print("isAuth: \(isAuth)")
}
}
}
And I got output isAuth: false.
I call passAuth func from this code
class <classname> {
var notifier : AuthProtocol!
func fetchUsers() {
db.collection("users").getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
var newUser = UserData()
newUser.login = document["login"] as! String
newUser.name = document["name"] as! String
newUser.password = document["password"] as! String
newUser.accessLevel = document["access_level"] as! Int
self.usersList.append(newUser)
}
self.notifier.passAuth()
}
}
}
}
I have no idea why value of isAuth isn't changing...
We use MVVM in swiftui. so we need a viewModel. now when the network request resutl will come we can update our view model and the change will be reflected by the View, simple right? i advice you to check out swiftui tutorials.
struct Auth : View {
#StateObject var viewModel = ViewModel()
// #Binding var isAuth: Bool // should use binding is isAuth will be passed by a parent view
var body : some View {
if viewModel.isAuth {
Text("WelcomeView()")
} else {
VStack {
Divider()
Text("Please, wait a minute...")
Divider()
}
.frame(width: 450, height: 350)
}
}
}
class ViewModel : ObservableObject {
#Published var isAuth = false
func fetchUsers() {
db.collection("users").getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
var newUser = UserData()
newUser.login = document["login"] as! String
newUser.name = document["name"] as! String
newUser.password = document["password"] as! String
newUser.accessLevel = document["access_level"] as! Int
self.usersList.append(newUser)
}
//
if validateUser() {
self.isAuth = true
}
//
}
}
}
}
There is a ListView. I make a transaction in Cloud Firestore by changing the field of an element when I click on it in the list. Data in the database changes as it should, but after this action all the elements in the list disappear (although there is .onAppear {fetchData}). An important point: this is a child view, there is no such problem in the parent view.
I also added a button at the bottom of the list to execute fetchData (), when I click on it, the data returns to the list
What could be the problem? Thanks
import SwiftUI
struct SecondView: View {
#ObservedObject var viewModel = BooksViewModel()
var body: some View {
VStack {
List(viewModel.books) { book in
VStack(alignment: .leading) {
Button("Update data"){
let updBook = book
self.viewModel.myTransaction(book: updBook)
}
Text(book.title)
.font(.headline)
Text(book.author)
.font(.subheadline)
Text("\(book.numberOfPages) pages")
.font(.subheadline)
}
}
.navigationBarTitle("Books")
.onAppear() {
self.viewModel.fetchData()
}
Button("update list"){
self.viewModel.fetchData()
}
}
}
}
ViewModel:
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
class BooksViewModel: ObservableObject {
#Published var books = [Book]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("books").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.books = documents.compactMap { queryDocumentSnapshot -> Book? in
return try? queryDocumentSnapshot.data(as: Book.self)
}
}
}
func deleteBook(book: Book){
if let bookID = book.id{
db.collection("books").document(bookID).delete()
}
}
func updateBook(book: Book) {
if let bookID = book.id{
do {
try db.collection("books").document(bookID).setData(from: book) }
catch {
print(error)
}
}
}
func addBook(book: Book) {
do {
let _ = try db.collection("books").addDocument(from: book)
}
catch {
print(error)
}
}
func myTransaction(book: Book){
let bookID = book.id
let targetReference = db.collection("books").document(bookID!)
db.runTransaction({ (transaction, errorPointer) -> Any? in
let targetDocument: DocumentSnapshot
do {
try targetDocument = transaction.getDocument(targetReference)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
guard let oldValue = targetDocument.data()?["pages"] as? Int else {
let error = NSError(
domain: "AppErrorDomain",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(targetDocument)"
]
)
errorPointer?.pointee = error
return nil
}
// Note: this could be done without a transaction
// by updating the population using FieldValue.increment()
transaction.updateData(["pages": oldValue + 1], forDocument: targetReference)
return nil
}) { (object, error) in
if let error = error {
print("Transaction failed: \(error)")
} else {
print("Transaction successfully committed!")
}
}
}
}
Parent view:
import SwiftUI
struct ContentView: View {
#ObservedObject var viewModel = BooksViewModel()
var body: some View {
NavigationView {
VStack {
List(viewModel.books) { book in
VStack(alignment: .leading) {
Button("Update"){
let delBook = book
self.viewModel.myTransaction(book: delBook)
}
Text(book.title)
.font(.headline)
Text(book.author)
.font(.subheadline)
Text("\(book.numberOfPages) pages")
.font(.subheadline)
}
}
.navigationBarTitle("Books")
.onAppear() {
self.viewModel.fetchData()
}
NavigationLink(destination: SecondView()){
Text("Second View")
}
}
}
}
}
A possible solution might be that your Views and its ViewModels interfere with each other. It looks like you create two instances of the same BookViewModel:
struct ContentView: View {
#ObservedObject var viewModel = BooksViewModel()
struct SecondView: View {
#ObservedObject var viewModel = BooksViewModel()
Try creating one BooksViewModel and pass it between views (you can use an #EnvironmentObject).