【问题标题】:SwifUI Xcode 12.5 iOS 14 Add UIImage to observed view modelSwiftUI Xcode12 5 iOS14 将 UIImage 添加到观察视图模型
【发布时间】:2021-07-28 06:31:03
【问题描述】:

我将本教程用作参考。 https://www.raywenderlich.com/11609977-getting-started-with-cloud-firestore-and-swiftui

我基本上是在本教程中尝试将图像添加到卡片类型。

在我的应用中,AddStoryView 从用户输入中获取 2 个字符串 title & storyText 和一个 UIImage。

试图弄清楚当图像被编辑时如何更新ForEach 中的视图。

在我的模型中,我不知道如何将 UIImage 与 Codable 一起使用。所以,我尝试了另一种单独上传图片的方法。并使用获取StoryCardView 中故事图像的方法调用onAppear。它确实有效,但是在更新图像时它不会更新,直到应用重新启动。

 import Foundation
 import FirebaseFirestoreSwift

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

这是StoriesListView 观察storyViewModels 以呈现故事的ViewModel

 import Foundation
 import Combine
 import PhotosUI

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

这里是StoryViewModel

    import Foundation
    import Combine

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

这是使用 Firebase 处理通信的 Story Repository:

    import FirebaseFirestore
    import FirebaseFirestoreSwift
    import FirebaseStorage
    import Combine
    import PhotosUI

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

这里是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()
        }
    }
}

这里是整个故事在StoryRepository 中更新的地方:

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

这就是我使用onAppear 在 StoryCardView 中显示图像的方式:

 private func getStoryImage() {
        ImageManager.instance.downloadStoryImage(storyID: storyViewModel.story.storyId ?? "") { (returnedImage) in
            if let image = returnedImage {
                DispatchQueue.main.async {
                    self.storyImg = image
                }
            }
        }
    }

【问题讨论】:

  • 通过阅读您的问题,我很难弄清楚问题所在。你问怎么上传图片?或者试图弄清楚如何更新视图?但是,你没有 StoryCardView 显示... 一般建议:ObservableObjects 的 @Published 数组将不起作用 -- @Published 适用于值类型,而不是引用类型。此外,在上传图像方面,您不应尝试将 UIImage 编码为 Codable 对象并存储它。存储文件的路径(例如String)并将文件与模型记录分开上传到 Firebase 存储。
  • 我有图像 只是想在图像被编辑时如何更新列表中的视图。如果您查看教程,我只想将图像添加到他们的卡片中。感谢您的关注。
  • 你的代码在哪里显示正在编辑的图像?
  • 我会将该代码添加到问题中
  • 所以您想在调用updateStory 时更新您的用户界面?但是您似乎没有在本地更新任何东西——它只是在更新 Firebase。您希望在当地看到什么变化?

标签: ios xcode firebase swiftui


【解决方案1】:

事实证明,我无法透过树木看到阿甘。我从中学到的是: 1 写出更好的问题。 2 这只是一个类型的问题,如果我专注于我的问题中我说我只想在 RW 教程中为卡片类型添加图像的部分,我可能会更快找到解决方案。

请注意,我不再通过在 onAppear 中下载图像来获取图像,这要归功于我引用的 RW 教程中的 Combine 和 FirebaseFirestoreSwift。

这是我的新模型

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: Data?
    var comment: String?
    var createdAt: Date
}

我现在只是在从用户那里获取图像后将 UIImage 转换为数据,然后将其添加到 Story 类型。

 private func add() {
    let imageData = imgSelected.jpegData(compressionQuality: 0.01)
    let storyToAdd = Story(headline: title, bodyText: storyText, storyImage: 
    imageData,createdAt: Date())
    storyListVM.addStory(story: storyToAdd)
}

当我的 storyImage 实际上传到 firebase 时,我将其转换回 UIImage

func uploadStoryImage(storyID: String, image: Data, handler: @escaping (_ success: Bool) -> ()) {

    // convert image to UIImage can force unwrap because a default image is passed
    let image = UIImage(data: image)!
    // Get the path where we will save the image
    let path = getStoryImagePath(storyID: storyID)
  
    // Save image to path
    DispatchQueue.global(qos: .userInteractive).async {
        self.uploadImage(path: path, image: image) { (success) in
            DispatchQueue.main.async {
                handler(success)
            }
        }
    }


}

当图像在从视图模型观察到的视图中呈现时,它是数据类型,因此需要转换为 UIImage 类型。注意 storyImg 是视图中的默认 UIImage

 Image(uiImage: UIImage(data:storyViewModel.story.storyImage!) ?? storyImg)

【讨论】:

  • 很高兴您找到了适合您的解决方案。通常,图像通常存储在 Firebase 存储中,然后它们的引用(即路径)存储在 Firestore 中。 Firestore 本身有您可能会遇到的上限(我看到您已经在使用图像的高度压缩版本),但同样,如果此解决方案适合您,那么现在就很好。
  • 谢谢 我如何在 firebase 中存储路径?
  • 这将是模型中的一个字符串值,用于存储您上传到 Storage 的相对路径(如 user/images/profile123.png)。
  • 感谢只是想了解如何从该路径或如何在我的视图中渲染图像?
  • 您必须从 Firebase 存储下载它。 Firebase 存储文档向您展示了如何下载数据。您可能会在 ObservableObject 中执行此操作,然后在下载后显示 Image
猜你喜欢
  • 2011-01-07
  • 1970-01-01
  • 2013-09-23
  • 1970-01-01
  • 2013-07-22
  • 1970-01-01
  • 2023-01-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多