Firebase图片存FileManager遇目录不存在错误,无法读取显示
问题描述
从Firebase Storage获取图片并保存至iOS FileManager本地目录时,出现No such file or directory exists错误;同时在SwiftUI的FinalEventView视图中,无法从FileManager读取图片显示到列表,调试发现遍历目录内容的代码被跳过。
图片获取与保存代码
func retrieveAllPictures(helloid: String) async { let db = Firestore.firestore() var contains = false let result = try? await db.collection("events").document(helloid).getDocument() if(result?.data() != nil){ allPictureIDs = (result?.data()!["pictures"] as? [String])! } for i in allPictureIDs { if(i == helloid) { contains = true } } if(allPictureIDs != nil){ for pathOfIds in allPictureIDs { let storage = Storage.storage().reference() let fileRef = storage.child(pathOfIds) //Split path to get actual name of the image let components = pathOfIds.components(separatedBy: "/") let pathForFileManager = components[1] //print(pathForFileManager) do { let data : Data = try await withCheckedThrowingContinuation { continuation in fileRef.getData(maxSize: 5000 * 1024) { data, error in if let error = error { continuation.resume(throwing: error) } else { continuation.resume(returning: data!) } } } let imageFinal = UIImage(data: data)! self.retrievedEventImages.append(imageFinal) let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let myCustomDirectory = documentsDirectory.appendingPathComponent("directory_\(helloid)") print(myCustomDirectory) //checking if directory already exists if !FileManager.default.fileExists(atPath: myCustomDirectory.path) { do { try FileManager.default.createDirectory(at: myCustomDirectory, withIntermediateDirectories: true, attributes: nil) } catch { print(error) } let imageURL = myCustomDirectory.appendingPathComponent("\(pathForFileManager)") if let imgData = imageFinal.jpegData(compressionQuality: 1.0){ do { try imgData.write(to: imageURL) } catch { print(error) } } } else { let imageURL = myCustomDirectory.appendingPathComponent("\(pathForFileManager)") //checking if file already exists in directory, if not add it to the directory if let imgData = imageFinal.jpegData(compressionQuality: 1.0){ do { try imgData.write(to: imageURL) } catch { print(error) } } }} catch { print(error) } }} else { self.retrievedEventImages.append(UIImage(systemName: "x.circle")) } }
SwiftUI视图代码
/// This view is the eventual view which shows the content of an event struct FinalEventView: View { @EnvironmentObject var viewModel: AppViewModel var id: String @State var selectedImage: UIImage? @State var isPickerShowing = false @State private var isShowingQRView = false @State var images: [UIImage] = [] var body: some View { VStack{ NavigationLink(destination: ShowQRCodeView(eventid: self.id), isActive: $isShowingQRView) { EmptyView() } VStack { Image(uiImage: viewModel.finalPP) .resizable() .frame(width: 90, height: 90, alignment: .center) .cornerRadius(50) Text("\(viewModel.finalName)") }//Profile picture and Text Divider() //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// ScrollView{ ForEach(self.images, id: \.self){ image in Image(uiImage: image) .resizable() .frame(width: 360, height: 370) .cornerRadius(50) } }.sheet(isPresented: $isPickerShowing, onDismiss: nil) { ImagePicker(selectedImage: $selectedImage, isPickerShowing: $isPickerShowing)}//Images //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// .toolbar(content: { //Plus to add image ToolbarItem() { Button(action: { isPickerShowing = true self.selectedImage = nil }, label: { Image(systemName: "plus") }) .frame(alignment: .trailing) }//end toolbarItem1 ToolbarItem(placement: .navigationBarTrailing) { Button(action: { Task { try? await viewModel.uploadOtherPicture(plusoneid: self.id, image: self.selectedImage!) try? await viewModel.retrieveAllPictures(helloid: self.id) selectedImage = nil } }, label: { Text("upload chosen image") }) } ToolbarItem(){ Menu(content: { Button(action: { isShowingQRView = true }, label: { Text("Show QR-Code") Image(systemName: "list.bullet")}) Button(action: { var TODO = true }, label: { Text("Delete event") Image(systemName: "trash") }) }, label: { Label("PDF", systemImage: "list.bullet") }) }//end toolbarItem2 })//end toolbar }//end VStack .edgesIgnoringSafeArea(.bottom) .onAppear{ Task { try? await viewModel.retrievePP(helloid: self.id) try? await viewModel.retrieveAllPictures(helloid: self.id) try? await viewModel.getName(id: self.id) try? await viewModel.getEvents()} let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let myCustomDirectory = documentsDirectory.appendingPathComponent("\(id)") do { let contents = try FileManager.default.contentsOfDirectory(at: myCustomDirectory, includingPropertiesForKeys: nil, options: []) for item in contents { if let image = UIImage(contentsOfFile: item.path) { self.images.append(image) } } } catch { print(error) } } }//end body }//end struct
调试发现的问题
遍历目录内容的代码段被跳过:
for item in contents { if let image = UIImage(contentsOfFile: item.path) { self.images.append(image) } }
推测图片保存到目录时失败,但不确定具体原因。
问题分析与修复方案
1. 目录名称不匹配(核心问题)
保存图片时创建的目录是directory_\(helloid),但读取时查找的目录是\(id),两者名称不一致,导致读取时找不到目录,直接进入错误分支,遍历代码自然不会执行。
修复:统一目录名称,将读取时的目录路径改为:
let myCustomDirectory = documentsDirectory.appendingPathComponent("directory_\(id)")
2. 异步操作顺序错误
onAppear中,读取本地目录的代码是同步执行的,而retrieveAllPictures是异步任务,可能还没完成图片保存就开始读取,此时目录为空,遍历代码无内容可执行。
修复:将读取本地目录的代码移到异步任务内部,确保保存完成后再读取:
.onAppear{ Task { try? await viewModel.retrievePP(helloid: self.id) try? await viewModel.retrieveAllPictures(helloid: self.id) try? await viewModel.getName(id: self.id) try? await viewModel.getEvents() // 读取本地目录的代码移到异步任务最后 let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let myCustomDirectory = documentsDirectory.appendingPathComponent("directory_\(id)") do { let contents = try FileManager.default.contentsOfDirectory(at: myCustomDirectory, includingPropertiesForKeys: nil, options: []) self.images.removeAll() // 清空旧数据,避免重复添加 for item in contents { if let image = UIImage(contentsOfFile: item.path) { self.images.append(image) } } } catch { print(error) } } }
3. 路径拆分越界风险
代码中直接使用components[1]获取文件名,若pathOfIds格式不符合预期(比如不是两段式路径),会导致数组越界崩溃。
修复:增加越界检查:
let components = pathOfIds.components(separatedBy: "/") guard components.count >= 2 else { print("Invalid image path format: \(pathOfIds)") continue } let pathForFileManager = components[1]
4. 强制解包隐患
代码中多处使用!强制解包(如data!、UIImage(data: data)!),一旦数据为空会直接崩溃。
修复:替换为可选绑定:
let data : Data = try await withCheckedThrowingContinuation { continuation in fileRef.getData(maxSize: 5000 * 1024) { data, error in if let error = error { continuation.resume(throwing: error) } else if let validData = data { continuation.resume(returning: validData) } else { continuation.resume(throwing: NSError(domain: "StorageError", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data returned from storage"])) } } } guard let imageFinal = UIImage(data: data) else { print("Failed to convert data to UIImage") continue }
内容的提问来源于stack exchange,提问作者anni_m
相关产品推荐
相关产品推荐

