I'm making a chat-like application with a main view called ChatView
and a list of Conversation View
s with different contacts. The idea behind the app is that messages are split into 3 parts and sent in a form of hyperlinks. The person receiving the message clicks the link and is presented with a sheet with a list of contacts in the app. When choosing a contact, the message is added to Core Data.
I registered an onOpenURL
handler on ChatView
:
ChatView()
.environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
.onOpenURL { url in
acceptedURL = .init(id: .init(), url: url)
}
.sheet(item: $acceptedURL) { link in
MessageDecryptView(url: link.url)
}
}
Inside MessageDecryptView
I do some processing and add the message to Core Data.
The issue I have is that if the app is displaying a conversation with specific user and a hyperlink is opened adding the message to that same conversation, the conversation is not updated when the sheet dismisses. Instead the user has to go back to ChatView
and enter the ConversationView
again which fetches the messages from Core Data onAppear
.
So for the scenario of MessageDecryptView
sheet being opened on top of ConversationView
I am looking for one of two possible solutions:
- how to go back to
ChatView
- (prererable) How to update the displayed
ConversationView
with a message that was just added
I would appreciate any help!
EDIT
My app is written in SwiftUI. I have ViewModel classes that are used by my views for interacting with Core Data. Inside ConversationView
there is
@ObservedObject private var messageVM = MessageViewModel()
and then the following is how I fetch the data from the database:
.onAppear(perform: {
userVM.getCurrentUser()
messageVM.getConversation(with: user.id!)
})
CodePudding user response:
You have to notify your view(s) that something changed in the messages. So just before - or inside of
MessageDecryptView(url: link.url)
insert
MessagesClass.objectWillChange.send()
and replace MessagesClass
with the name of your NSManagedObject
class in CoreData that contains your messages – the one that is used to display the messages in the view.
CodePudding user response:
We don't use view model objects in SwiftUI, we use property wrappers that make the View data struct behave like an object. And the one for core data object is @ObservedObject
so body is called when the object changes, so replace:
.onAppear(perform: {
userVM.getCurrentUser()
messageVM.getConversation(with: user.id!)
})
with
@ObservedObject var user: User
and
ChatView(user: user)
Then for fetching we use @FetchRequest
and body is called when any of the objects change or an object is inserted, moved or deleted. So in your case it would look something like this:
@FetchRequest var conversations: FetchedResults<Conversation>
init(user: User) {
let sortDescriptors = [SortDescriptor(\Conversation.timestamp, order: sortAscending ? .forward : .reverse)]
_conversations = FetchRequest(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "user = %@", user), animation: .default)
Alternatively you can pass in a FetchRequest
to your View init if that is simpler:
ConversationsView(conversations: FetchRequest(...
You can also make an extension on your entity types to return pre-configured NSFetchRequest
s that can be used to easily create FetchRequest
structs, e.g.
extension Item {
static func myFetchRequest() -> NSFetchRequest<Item> {
let fr = Self.fetchRequest()
fr.propertiesToFetch = ["title"]
fr.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]
return fr
}
}
@FetchRequest(
fetchRequest: Item.myFetchRequest(),
animation: .default)
private var items: FetchedResults<Item>