You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 09:46:06