I am using this tutorial as a reference.
https://www.raywenderlich.com/11609977-getting-started-with-cloud-firestore-and-swiftui
I am basically trying to add an image to a Card type in this tutorial
In my app AddStoryView gets 2 strings title & storyText and a UIImage from user input
Trying figure how to update the views in ForEach when image gets edited
In my model( I can’t figure how to use UIImage with Codable) so I tried a different approach of uploading image separately. And calling onAppear with a method that gets the image for the story in the StoryCardView. It does work but when updating images it’s not updating until app restarts.
struct Story: Identifiable, Codable {
@DocumentID var id: String?
var author: String?
var headline :String?
var bodyText: String?
var userId: String?
var storyId: String?
// var storyImage: UIImage = UIImage(named: "logo")!
var comment: String?
}
Here is ViewModel that StoriesListView observes storyViewModels to render stories in view
class StoryListViewModel: ObservableObject {
@Published var storyRepository = StoryRepository()
@Published var storyViewModels: [StoryViewModel] = []
private var cancellables: Set<AnyCancellable> = []
init() {
storyRepository.$stories.map { stories in
stories.map(StoryViewModel.init)
}
.assign(to: \.storyViewModels, on: self)
.store(in: &cancellables)
}
func addStory(story: Story, image: UIImage) {
storyRepository.uploadStory(story: story, image: image)
}
}
Here is StoryViewModel
class StoryViewModel: ObservableObject, Identifiable {
private let storyRepository = StoryRepository()
@Published var story: Story
private var cancellables: Set<AnyCancellable> = []
var id = ""
init(story: Story) {
self.story = story
// set up binding for story between the stories id and the viewmodels id then store object in cancellables so it can be canceled later
$story
.compactMap { $0.id }
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
func editStory(story: Story, image: UIImage) {
storyRepository.updateStory(story, image)
}
}
Here is Story Repository where communication is handled with firebase
class StoryRepository: ObservableObject {
////
private let path: String = "stories"
private let store = Firestore.firestore()
@Published var stories: [Story] = []
var userId = ""
private let authenticationService = AuthService()
private var cancellables: Set<AnyCancellable> = []
init() {
authenticationService.$user
.compactMap { user in
user?.uid
}
.assign(to: \.userId, on: self)
.store(in: &cancellables)
authenticationService.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.get()
}
.store(in: &cancellables)
}
func get(){
store.collection(path)
.addSnapshotListener{ querySnapshot, error in
if let error = error {
print("Error getting stories: \(error.localizedDescription)")
return
}
let stories = querySnapshot?.documents.compactMap {document in
try? document.data(as: Story.self)
} ?? []
DispatchQueue.main.async {
self.stories = stories
}
}
}
}
Here is StoriesListVIew
struct StoriesListView: View {
@EnvironmentObject var storyListVM: StoryListViewModel
var body: some View {
ScrollView {
VStack (spacing: 20){
ForEach(storyListVM.storyViewModels) { storyViewModel in
StoryCardView(storyViewModel: storyViewModel)
}
}
.padding()
}
}
}
Here is where the whole story gets updated.
func updateStory(_ story: Story, _ image: UIImage) {
guard let storyDocId = story.id else { return }
ImageManager.instance.uploadStoryImage(storyID: story.storyId ?? "", image: image) { (_ success: Bool) in
if success {
do {
try self.store.collection(self.path).document(storyDocId).setData(from: story)
} catch {
fatalError("Unable to add card: \(error.localizedDescription).")
}
return
} else {
print("Error uploading post image to firebase")
return
}
}
}
This is how I am getting the image to show in StoryCardView with onAppear
private func getStoryImage() {
ImageManager.instance.downloadStoryImage(storyID: storyViewModel.story.storyId ?? "") { (returnedImage) in
if let image = returnedImage {
DispatchQueue.main.async {
self.storyImg = image
}
}
}
}