如何将Firebase Storage图片URL绑定到SwiftUI的AsyncImage?
从Firebase Storage获取图片并在SwiftUI AsyncImage中显示的问题
我无法通过上传图片时存储下载URL的常规方式实现需求,需要从Firestore读取数据后,再从Firebase Storage获取图片并显示在SwiftUI的AsyncImage中。现有代码能从Firestore拿到数据,但在获取Storage图片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)) } } } } struct dataType : Identifiable { var id = UUID().uuidString var title : String var description : String var pic : String }
尝试获取图片URL时的错误
我尝试在getData中添加获取Storage下载URL的代码:
let storage = Storage.storage() let storageRef = storage.reference() let spaceRef = storageRef.child("itemImages/\(pic)") spaceRef.downloadURL { url, error in if let error = error { print("Failed to download url:", error) return } else { print(url!) } }
得到的错误信息显示路径重复,找不到对象:
Failed to download url: Error Domain=FIRStorageErrorDomain Code=-13010 "Object itemImages/itemImages/FD80E18F-C2C0-4E9F-BE8A-7A6AADAC31A3.jpg does not exist." UserInfo={object=itemImages/itemImages/FD80E18F-C2C0-4E9F-BE8A-7A6AADAC31A3.jpg, ResponseBody={ "error": { "code": 404, "message": "Not Found." } }, bucket=somee-letters-and-numbers.com, data={length = 65, bytes = 0x7b0a2020 22657272 6f72223a 207b0a20 ... 2e220a20 207d0a7d }, data_content_type=application/json; charset=UTF-8, NSLocalizedDescription=Object itemImages/itemImages/FD80E18F-C2C0-4E9F-BE8A-7A6AADAC31A3.jpg does not exist., ResponseErrorDomain=com.google.HTTPStatus, ResponseErrorCode=404}
解决方案
1. 修复路径重复问题
错误里的路径itemImages/itemImages/xxx说明:Firestore中存储的pic字段本身已经包含了itemImages/前缀。因此代码中不需要再拼接这个前缀,直接使用pic字段的值作为Storage路径即可。
2. 优化数据模型与异步处理
由于获取下载URL是异步操作,需要调整代码逻辑确保UI线程安全更新数据,同时避免强制解包导致崩溃:
修改数据模型
struct dataType : Identifiable { var id: String // 直接使用Firestore的documentID,避免UUID重复 var title : String var description : String var picURL: URL? // 改为可选URL类型,适配加载失败场景 }
更新数据获取类
class getData : ObservableObject { @Published var datas = [dataType]() init() { let db = Firestore.firestore() db.collection("items").getDocuments { [weak self] (snap, err) in guard let self = self, let documents = snap?.documents, err == nil else { print(err?.localizedDescription ?? "获取Firestore文档失败") return } for doc in documents { let id = doc.documentID guard let title = doc.get("title") as? String, let description = doc.get("description") as? String, let picPath = doc.get("pic") as? String else { print("文档字段解析失败,跳过该条目") continue } // 直接使用Firestore中存储的路径获取Storage引用 let storageRef = Storage.storage().reference().child(picPath) storageRef.downloadURL { url, error in DispatchQueue.main.async { // 必须在主队列更新UI相关数据 if let error = error { print("获取图片下载URL失败: \(error.localizedDescription)") // 添加加载失败的条目(可设置默认图片URL) self.datas.append(dataType(id: id, title: title, description: description, picURL: nil)) return } self.datas.append(dataType(id: id, title: title, description: description, picURL: url)) } } } } } }
3. 在SwiftUI视图中使用AsyncImage
struct ItemListView: View { @StateObject private var dataFetcher = getData() var body: some View { List(dataFetcher.datas) { item in VStack(alignment: .leading, spacing: 8) { Text(item.title) .font(.headline) Text(item.description) .font(.subheadline) .foregroundColor(.secondary) if let picURL = item.picURL { AsyncImage(url: picURL) { phase in switch phase { case .empty: ProgressView() // 加载中占位 case .success(let image): image.resizable() .scaledToFit() .cornerRadius(8) case .failure: Image(systemName: "photo.fill") // 加载失败占位 .foregroundColor(.gray) .font(.largeTitle) @unknown default: EmptyView() } } .frame(height: 200) } else { Image(systemName: "photo.fill") .foregroundColor(.gray) .font(.largeTitle) .frame(height: 200) } } .padding(.vertical, 8) } } }
关键说明
- 路径问题:确认Firestore中
pic字段存储的是Storage中图片的完整相对路径(如itemImages/xxx.jpg),不要在代码中重复拼接前缀。 - 异步安全:
downloadURL是异步操作,必须切换到主队列更新@Published数组,否则会导致UI更新异常。 - 容错处理:使用可选类型和
guard let避免强制解包崩溃,同时为加载失败场景提供占位图。
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

