UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: SwifUI Xcode 12.5 iOS 14 Add UIImage to observed view model

Forums > SwiftUI

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
                }
            }

        }
    }

3      

I think you need to convert the image to Data

let imageData = image.jpegData(compressionQuality: 1)

and to retrive image

let image = UIImage(data: imageData) // note imageData is optional do you need to unwrap first

display in SwiftUI

Image(uiImage: image)

3      

@NigelGee Thanks again for helping. I see you help others a lot!

What I learned from this is: 1 write better questions. 2 it was just a type thing, if I would have focused on the part in my question where I said I just want to add an image to the card type in the RW tutorial I may have found a solution sooner

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.