swift 如何在AsyncImage中正确使用下载的URL?

ubby3x7f  于 2022-10-31  发布在  Swift
关注(0)|答案(1)|浏览(218)

如何在AsyncImage中使用从getData类下载的URL

struct RecentItemsView: View {
    var item: dataType             // var's from getData class
    var body: some View {
        HStack(spacing: 15) {
            AsyncImage(url: URL(string: item.pic), content: { image in   // item.pic here
                image.resizable()
                }, placeholder: {
                    ProgressView()
                    })

我有来自downloadURL的完整URL,但是当我在AsyncImage中使用item.pic参数时,我得到错误:***(参见图片)***
我知道这个错误包含了图像的路径,这不适合AsyncImage,这就是为什么我下载了完整的URL,问题是如何在AsyncImage中使用收到的URL

class getData : ObservableObject {

    @Published var datas = [dataType]()

    init() {

        let db = Firestore.firestore()

        db.collection("items").getDocuments { (snap, err) in

            if err != nil {

                print((err?.localizedDescription)!)
                return
            }

            for i in snap!.documents {

                let id = i.documentID
                let title = i.get("title") as! String
                let description = i.get("description") as! String
                let pic = i.get("pic") as! String

                self.datas.append(dataType(id: id, title: title, description: description, pic: pic))

                let storage = Storage.storage()
                let storageRef = storage.reference().child("\(pic)")

                storageRef.downloadURL { url, error in
                    if let error = error {
                        print("Failed to download url:", error)
                        return
                    } else {

                        print(url!)             // Full Url- https://firebasestorage.googleapis.com:...

                    }
                }
            }
        }
    }
}

struct dataType : Identifiable {

    var id = UUID().uuidString
    var title : String
    var description : String
    var pic : String
}

错误:

储存空间:

火灾恢复:

mm9b1k5b

mm9b1k5b1#

这看起来和你当前的方法有很大的不同,但是给予一下,它会从整体上简化你的代码。
主要区别是使用async awaitFirebaseFirestoreSwift
我选择使用async await/Concurrency是因为它提供了一种更线性的代码方法,并且我认为它解决了与所有对象共享变量的问题。
这是ObservableObject的外观

//Keeps UI Updates on the main thread
@MainActor
//Classes and structs should always be uppercased
class GetData : ObservableObject {

    @Published var datas = [DataType]()
    private var task: Task<Void, Never>? = nil
    init() {
        task = Task{
            do{
                try await getData()
            }catch{
                //Ideally you should present this error
                //to the users so they know that something has gone wrong
                print(error)
            }
        }
    }
    deinit{
        task?.cancel()
    }
    func getData() async throws {
        let documentPath = "items"
        let svc = FirebaseService()
        //async await allows a more linear approach. You can get the images individually
        var items : [DataType] = try await svc.retrieve(path: documentPath)

        for (idx, item) in items.enumerated() {
            //Check if your url is a full url
            if !item.pic.localizedCaseInsensitiveContains("https"){
                //If it isnt a full url get it from storage and replace the url
                items[idx].pic = try await svc.getImageURL(imagePath: item.pic).absoluteString
                //Optional update the object so you dont have to retrieve the
                //The url each time.
                try svc.update(path: documentPath, object: items[idx])
            }
        }
        datas = items
    }
}

并且您的struct应该更改为使用@DocumentID

//This is a much simpler solution to decoding
struct DataType : Identifiable, FirestoreProtocol {
    @DocumentID var id : String?
    //If you get decoding errors make these variables optional by adding a ?
    var title : String
    var description : String
    var pic : String
}

现在可以修改您的视图以使用更新的变量。

@available(iOS 15.0, *)
public struct DataTypeListView: View{
    @StateObject var vm: GetData = .init()
    public init(){}
    public var body: some View{
        List(vm.datas){ data in
            DataTypeView(data: data)
        }
    }
}
@available(iOS 15.0, *)
struct DataTypeView: View{
    let data: DataType
    var body: some View{
        HStack{
            Text(data.title)
            AsyncImage(url: URL(string: data.pic), content: { phase in
                switch phase{
                case .success(let image):
                    image
                        .resizable()
                        .scaledToFit()
                        .frame(width: 50, height: 50)
                case .failure(let error):
                    Image(systemName: "rectangle.fill")
                        .onAppear(){
                            print(error)
                        }
                case .empty:
                    Image(systemName: "rectangle.fill")
                @unknown default:
                    Image(systemName: "rectangle.fill")

                }
            })
        }

    }

}

class GetData是一个非常简单的框架,它使用下面的代码来实际进行调用,我喜欢使用泛型来简化代码,这样它就可以被各种地方重用。
你不必完全理解这是怎么回事,现在,但你应该,我已经把一吨的意见,所以它应该很容易。

import FirebaseStorage
import FirebaseFirestore
import FirebaseFirestoreSwift
import SwiftUI
import FirebaseAuth
struct FirebaseService{
    private let storage: Storage = .storage()
    private let db: Firestore = .firestore()
    ///Retrieves the storage URL for an image path
    func getImageURL(imagePath: String?) async throws -> URL{
        guard let imagePath = imagePath else {
            throw AppError.unknown("Invalid Image Path")
        }
        typealias PostContinuation = CheckedContinuation<URL, Error>
        //Converts an completion handler approach to async await/concurrency
        return try await withCheckedThrowingContinuation { (continuation: PostContinuation) in
            storage.reference().child(imagePath).downloadURL { url, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else if let url = url {
                    continuation.resume(returning: url)
                } else {
                    continuation.resume(throwing: AppError.unknown("Error getting image url"))
                }
            }
        }
    }
    ///Retireves the documetns from the Firestore and returns an array of objects
    func retrieve<FC>(path: String) async throws -> [FC] where FC : FirestoreProtocol{
        let snapshot = try await db.collection(path).getDocuments()
        return snapshot.documents.compactMap { doc in
            do{
                return try doc.data(as: FC.self)
            }catch{
                //If you get any decoding errors adjust your struct, you will
                //likely need optionals
                print(error)
                return nil
            }
        }
    }
    ///Updates the provided document into the provided path
    public func update<FC : FirestoreProtocol>(path: String, object: FC) throws{
        guard let id = object.id else{
            throw AppError.needValidId
        }
        try db.collection(path).document(id).setData(from: object)
    }
}

enum AppError: LocalizedError{
    case unknown(String)
    case needValidId
}

protocol FirestoreProtocol: Identifiable, Codable{
    ///Use @DocumentID from FirestoreSwift
    var id: String? {get set}
}

所有这些代码都可以工作,如果你把所有这些代码放在一个.swift文件中,它将编译,并应该与你的数据库一起工作。

相关问题