SwiftUI #fetchrequest not updating my view - fetch

I have a view which uses the #fetchrequest wrapper to pull in from CoreData, it works fine however, but when I edit the data in a .sheet and return to the view, the data is not updated until I navigate away and come back. Shouldn't it just update automatically? Here is some sample code:
List
import CoreData
struct Stories: View {
#State var addStory = false
#Environment(\.managedObjectContext) var moc
#FetchRequest<Story>(
entity: Story.entity(),
sortDescriptors: []
) var stories: FetchedResults<Story>
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
ForEach(stories, id: \.self){ story in
GeometryReader { geometry in
StoryCard(show: story.show,
image: Image(uiImage: UIImage(data: story.image as! Data)!) ?? Image("deer3"),
title: story.title,
date:story.date,
locationTitle: story.locationTitle ?? "",
text: story.text,
timeOfDay: timeOfDayData[Int(story.tod)],
weather: weatherData[Int(story.weather)],
story: story)
.environment(\.managedObjectContext, self.moc)
.offset(y: story.show ? -geometry.frame(in: .global).minY : 0)
}.frame(height: story.show ? screen.height : 280)
.frame(maxWidth:story.show ? .infinity : screen.width-60)
.background(Color("Tan"))
.zIndex(story.show ? 2.0 : 0.5)
}
}
}
Object
struct StoryCard: View {
#State var show:Bool
#State var image:Image = Image("deer1")
#State var title = "Story title"
#State var date:Date = Date()
#State var locationTitle = "Priddis"
#State var location:CLLocationCoordinate2D = CLLocationCoordinate2D()
#State var text = "Lorem ipsum valor decor vassus sit amet."
#State var timeOfDay = timeOfDayData[0]
#State var weather = weatherData[0]
#State var edit = false
#ObservedObject var story:Story
#Environment(\.managedObjectContext) var moc
var body: some View {
....
Button(action:{
self.show.toggle()
self.moc.performAndWait {
self.story.show = self.show
try? self.moc.save()
}
self.edit.toggle()
}){
Text("Edit")
}.sheet(isPresented: self.$edit) {
ImagePickerTest(addStory: self.$edit, story:self.story).environment(\.managedObjectContext, self.moc)
}
....
}
}
Edit
struct Editor: View{
...
#State var story:Story?
#Environment(\.managedObjectContext) var moc
var body: some View {
...
Button(action:{
self.moc.performAndWait {
self.story!.title = self.storyName
self.story!.date = self.date
self.story!.locationTitle = self.locationTitle
self.story!.tod = Int16(self.tod)
self.story!.weather = Int16(self.weather)
self.story!.text = self.text
self.story!.show = false
self.story!.latitude = self.centerCoordinate.latitude
self.story!.longitude = self.centerCoordinate.longitude
self.story!.image = self.inputImage?.pngData()
try? self.moc.save()
}){
Text("Save")
}
...
}
}
Any help is greatly appreciated. Thank you!

For anyone interested, I actually figured this out. I was passing individual variables into my Story object instead of using the ones from the #observedobject story variable (Picard facepalm)

Related

Reference to member 'decor' cannot be resolved without a contextual type

I have been following the tutorial on youtube by Reality School. This part is for the firebase store model for storing usdz models with thumbnails. I have got stuck at .decor it is saying " Reference to member 'decor' cannot be resolved without a contextual type"
I do not have any reference to .decor in any other part of the code. And I can't find anything about it. Can anyone give some pointers in the correct direction?
import Foundation
import FirebaseFirestore
class ModelsViewModel: ObservableObject {
#Published var models: [Model] = []
private let db = Firestore.firestore()
func fetchData() {
db.collection("models").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("Firestore: No documents")
return
}
self.models = documents.map { (queryDocumentSnapshot) -> Model in
let data = queryDocumentSnapshot.data()
let name = data["name"] as? String ?? ""
let categoryText = data["category"] as? String ?? ""
let category = ModelCategory(rawValue: categoryText) ?? .decor
let scaleCompensation = data["scaleCompensation"] as? Double ?? 1.0
return Model(name: name, category: category, scaleCompensation: Float(scaleCompensation))
}
}
}
}
Here is also the model code.
import SwiftUI
import RealityKit
import Combine
enum ModelCategory: String, CaseIterable {
case porches
case gazebos
case pergolas
case garages
case roomabove
case loftedroomabove
var lable: String {
get {
switch self {
case .porches:
return "Porches"
case .gazebos:
return "Gazebos"
case . pergolas:
return "Pergolas"
case .garages:
return "Garages"
case .roomabove:
return "Room Above"
case .loftedroomabove:
return "Lofted Room Above"
}
}
}
}
class Model: ObservableObject, Identifiable {
var id: String = UUID().uuidString
var name: String
var category: ModelCategory
#Published var thumbnail: UIImage
var modelEntity: ModelEntity?
var scaleCompensation: Float
private var cancellable: AnyCancellable?
init(name: String, category: ModelCategory, scaleCompensation: Float = 1.0) {
self.name = name
self.category = category
self.thumbnail = UIImage(systemName: "photo")!
self.scaleCompensation = scaleCompensation
FirebaseStorageHelper.asyncDownloadToFilesystem(relativePath: "thumbnails/\(self.name).png") { localUrl in
do {
let imageData = try Data(contentsOf: localUrl)
self.thumbnail = UIImage(data: imageData) ?? self.thumbnail
} catch {
print("Error loading image: \(error.localizedDescription)")
}
}
}
//async model loading
func asyncLoadModelEntity() {
let filename = self.name + ".usdz"
self.cancellable = ModelEntity.loadModelAsync(named: filename)
.sink(receiveCompletion: { loadCompletion in
switch loadCompletion {
case .failure(let error): print("Unable to load modelEntity for \(filename) Error: \(error.localizedDescription)")
case .finished:
break
}
}, receiveValue: { modelEntity in
self.modelEntity = modelEntity
self.modelEntity?.scale *= self.scaleCompensation
print("modelEntity for \(self.name) has been loaded")
})
}
}

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 update data for parent NavigationView

how can I update data in TextField that lives in the detail of a NavigationView? I want the data to be updated in the List of the parent View. Here is my code:
class Address: ObservableObject, Identifiable {
let id = UUID()
#Published var name: String
#Published var age: String
init(name: String, age: String) {
self.name = name
self.age = age
}
}
class AddressBook: ObservableObject {
#Published var addresses: [Address]
init(addresses: [Address]) {
self.addresses = addresses
}
}
struct TestData {
static let addressbook = AddressBook(addresses: [
Address(name: "Person1", age: "39"),
Address(name: "Person2", age: "22")
])
}
struct ContentView: View {
#ObservedObject var addressbook = TestData.addressbook
var body: some View {
NavigationView {
List (addressbook.addresses) {address in
NavigationLink(destination: AddressDetail(address: address)) {
Text(address.name)
}
}
.navigationBarTitle("Addressbook")
}
}
}
struct AddressDetail: View {
#ObservedObject var address: Address
var body: some View {
Form {
TextField("name", text: $address.name)
TextField("age", text: $address.age)
}
}
}
This code doesn't work: If I go to the AddressDetail-View, change the TextField values and then go back, the changes don't update in the List-View.
Nico
The problem is that Address is a class, so if its published properties changed the reference in addresses is not changed, but ContentView view observes addressbook as a container of addresses.
Here is demo of possible approach (tested & works with Xcode 12b / iOS 14, also on 11.4 / iOS 13.4)
List (addressbook.addresses) {address in
NavigationLink(destination: AddressDetail(address: address)) {
Text(address.name)
.onReceive(address.objectWillChange) { _ in
self.addressbook.objectWillChange.send()
}
}
}
Alternate: based on Binding (requires much more changes, so, as for me, less preferable, but worth mention
struct Address: Identifiable, Hashable {
let id = UUID()
var name: String
var age: String
init(name: String, age: String) {
self.name = name
self.age = age
}
}
class AddressBook: ObservableObject {
#Published var addresses: [Address]
init(addresses: [Address]) {
self.addresses = addresses
}
}
struct TestData {
static let addressbook = AddressBook(addresses: [
Address(name: "Person1", age: "39"),
Address(name: "Person2", age: "22")
])
}
struct ContentView: View {
#ObservedObject var addressbook = TestData.addressbook
var body: some View {
NavigationView {
List (Array(addressbook.addresses.enumerated()), id: \.element) { i, address in
NavigationLink(destination: AddressDetail(address: self.$addressbook.addresses[i])) {
Text(address.name)
}
}
.navigationBarTitle("Addressbook")
}
}
}
struct AddressDetail: View {
#Binding var address: Address
#State private var name: String
#State private var age: String
init(address: Binding<Address>) {
_address = address
_name = State(initialValue: _address.wrappedValue.name)
_age = State(initialValue: _address.wrappedValue.age)
}
var body: some View {
Form {
TextField("name", text: $name)
TextField("age", text: $age)
}
.onDisappear {
self.address.name = self.name
self.address.age = self.age
}
}
}

Is there a way to give initial value in an instance in property initializer dynamically using SwiftUI and Firebase?

Hi I am currently making a dating app's chat page that you can have different rooms for every match using SwiftUI and Cloud Firestore.
I would like to show different chat room every time you tap different user on the top page depending on the matchId.
For now, I need to type the right one in the View file in order to make it work correctly, however, Id like to assign it dynamically.
How can I add the correct matchId to the instance in the View file? Or, should I try different ways?
First, this is the top page.
VStack{
Text("Match Users")
List(self.shareData.matchUserArray){ user in
NavigationLink(destination: MessageView(matchUserInfo: user)){
HStack{
Text(user.name)
Text(user.age)
}
}
}
}
And this is the View file. Without typing "Ll73RINefGxEcYQJoWSE" in the MessageViewModel instance and instead giving it "", I can see the messages in the debug area but don't see any in List.
struct MessageView: View {
var matchUserInfo: User
#ObservedObject var msgVM = MessageViewModel(matchId: "Ll73RINefGxEcYQJoWSE")
#EnvironmentObject var shareData : ShareData
#State var text = ""
#State var matchId = ""
var body: some View {
VStack{
List(self.msgVM.messages, id: \.id){ i in
if i.fromUser == self.shareData.currentUserData["id"] as? String ?? ""
{
MessageRow(message: i.msg, isMyMessage: true)
} else if i.toUser == self.shareData.currentUserData["id"] as? String ?? ""
{
MessageRow(message: i.msg, isMyMessage: false)
}
}
.onAppear { UITableView.appearance().separatorStyle = .none }
.onDisappear { UITableView.appearance().separatorStyle = .singleLine }
HStack{
TextField("message here", text: $text).textFieldStyle(RoundedBorderTextFieldStyle()).padding()
Button(action: {
if self.text.count > 0 {
self.msgVM.sendMsg(msg: self.text, toUser: self.matchUserInfo.id, fromUser: self.shareData.currentUserData["id"] as! String, matchId: self.msgVM.matchId)
self.text = ""
}
}) {
Image(systemName: "paperplane")
}.padding(.trailing)
}
}
.navigationBarTitle("\(self.matchUserInfo.name)", displayMode: .inline)
.onAppear{
DispatchQueue.global().async{
self.getMatchId(partner: self.matchUserInfo)
}
_ = MessageViewModel(matchId: self.matchId)
}
.onDisappear{
print(self.msgVM.messages)
}
}
func getMatchId(partner: User){
Firestore.firestore().collection("MatchTable").document(self.shareData.currentUserData["id"] as? String ?? "").collection("MatchUser").whereField("MatchUserId", isEqualTo: partner.id).getDocuments { (snap, err) in
if let snap = snap {
for id in snap.documents{
self.msgVM.matchId = id.data()["MatchRoomId"] as? String ?? ""
_ = MessageViewModel(matchId: self.msgVM.matchId)
self.matchId = self.msgVM.matchId
}
}
}
}
}
Also this is the firebase part.
import Foundation
import FirebaseFirestore
struct Message: Identifiable {
var id: String
var msg: String
var fromUser: String
var toUser: String
var date: Timestamp
var matchId : String
}
class MessageViewModel: ObservableObject {
var datas = FirebaseData()
let db = Firestore.firestore()
#Published var matchId:String
#Published var messages = [Message]()
init(matchId: String){
self.matchId = matchId
self.db.collection("Messages").whereField("matchId", isEqualTo: self.matchId).order(by: "date").addSnapshotListener { (snap, error) in
if let error = error {
print(error.localizedDescription)
return
}
if let snap = snap {
for i in snap.documentChanges {
if i.type == .added{
let toUser = i.document.get("toUser") as! String
let fromUser = i.document.get("fromUser") as! String
let message = i.document.get("message") as! String
let id = i.document.documentID
let date = i.document.get("date") as! Timestamp
let matchId = i.document.get("matchId") as! String
self.messages.append(Message(id: id, msg: message, fromUser: fromUser, toUser: toUser, date: date, matchId: matchId))
}
}
}
}
}
func sendMsg(msg: String, toUser: String, fromUser: String, matchId: String){
let data = [
"message": msg,
"toUser": toUser,
"fromUser": fromUser,
"date": Timestamp(),
"matchId": matchId
] as [String : Any]
Firestore.firestore().collection("Messages").addDocument(data: data){ error in
if let err = error {
print(err.localizedDescription)
return
}
print("Sent message")
}
}
}
Thank you
All you should really need is to construct your ObservedObject in an init function:
let matchUserInfo: User
#ObservedObject private var msgVM: MessageViewModel
init(_ user: User) {
self.matchUserInfo = user
self._msgVM = ObservedObject(initialValue: MessageViewModel(matchId: user.matchId))
}
Assuming, of course, that the matchId you care about is passed in via your User type. You know your data structures better than I do, the key here is to simply create your observed object based on your passed in User.

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