文件管理器有时在文档目录中找不到子文件夹

File Manager sometimes doesn't find subfolder in documents directory

我以前问过这个问题,但不得不重新表述所有内容以使其更容易理解。

在我的项目中,我使用以下代码在 documents directory 中创建了一个名为 HTML 的子文件夹:

fileprivate func createFolderOnDocumentsDirectoryIfNotExists() {
    let folderName = "HTML"
    let fileManager = FileManager.default
    if let tDocumentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
        let filePath =  tDocumentDirectory.appendingPathComponent("\(folderName)")
        if !fileManager.fileExists(atPath: filePath.path) {
            do {
                try fileManager.createDirectory(atPath: filePath.path, withIntermediateDirectories: true, attributes: nil)
            } catch {
                print("Couldn't create document directory")
            }
        }
        print("Document directory is \(filePath)")
    }
}

打印语句打印如下:

Document directory is file:///var/mobile/Containers/Data/Application/103869C9-9D46-4595-A370-A93BCD75D495/Documents/HTML

在我的应用程序 Bundle 中,我有一个 .css 文件供我在 HTML 字符串中使用,以 WKWebView.

显示

由于 .css 文件需要与 HTML baseURL 位于同一文件夹中,我将 .css 文件从 Bundle 复制到使用以上函数,代码如下:

fileprivate func copyCSSFileToHTMLFolder() {
    guard let cssURL = Bundle.main.url(forResource: "swiss", withExtension: "css") else { return }
    let fileManager = FileManager.default
    if let tDocumentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
        let fp = tDocumentDirectory.appendingPathComponent("HTML")
        if fileManager.fileExists(atPath: fp.path) {
            let fp2 =  fp.appendingPathComponent(cssURL.lastPathComponent)
            do {
                try fileManager.copyItem(atPath: cssURL.path, toPath: fp2.path)
            } catch {
                print("\nFailed to copy css to HTML folder with error:", error)
            }
        }
    }
}

我知道我可以通过将 HTML baseURL 设置为 main.Bundle 在应用程序的 Bundle 中同时使用两者,但是因为我需要在 HTML 中显示本地图像而不是网络链接的图像,我不得不将它们移动到一个子文件夹中,以便我以后将所需的图像复制到该子文件夹中,因为 main.Bundle 是只读的且不可写的(我们不能在那里保存图像) .

现在我有一个 ViewController 被推过 UINavigationController;在那个推 ViewController 我有一个 WKWebView 将呈现 HTML.

我还有一个 var markdownString: String? {} 属性 实例化后,我使用以下函数将其转换为 HTML 字符串:

func getHTML(str: String) -> String {
    /*
      the cssImport here points to the previously copied css file as they are on the same folder
    */

    let cssImport = "<head><link rel=\"stylesheet\" type=\"text/css\" href=\"swiss.css\"></head>"
    let htmlString = try? Down(markdownString: str).toHTML()    
    return cssImport + htmlString!
}

我的 WKWebView 使用以下代码声明:

private lazy var webView: WKWebView = {
    // script to fit the content with the screen
    var scriptContent = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"

    let wkuscript = WKUserScript(source: scriptContent, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)
    let wkucontroller = WKUserContentController()
    wkucontroller.addUserScript(wkuscript)

    let wkwebconfig = WKWebViewConfiguration()
    wkwebconfig.userContentController = wkucontroller

    let wv = WKWebView(frame: .zero, configuration: wkwebconfig)
    wv.frame.size.height = 1
    return wv
}()

每当设置 markdown String 时,我将其转换为 HTML 字符串,然后实例化 HTML baseURL 并将其加载到我的 webView 中以下代码:

public var markdownString: String? {
    didSet {
        guard let kPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("HTML", isDirectory: true) else { return }

        guard let str = markdownString else { return }
        let html = getHTML(str: str)

        print("base url:", kPath)

        let fm = FileManager.default

        do {
            let items = try fm.contentsOfDirectory(atPath: kPath.path)
            print("items:", items)
        } catch {
            print("Error:", error)
        }
        webView.loadHTMLString(html, baseURL: kPath)
    }
}

这是我的问题开始的地方。

swiss.css 文件位于以下目录中:

file:///var/mobile/Containers/Data/Application/103869C9-9D46-4595-A370-A93BCD75D495/Documents/HTML/

HTML baseURL指向以下路径:

file:///var/mobile/Containers/Data/Application/103869C9-9D46-4595-A370-A93BCD75D495/Documents/HTML/

如您所见,它们都指向同一条路径。

有时我的 HTML 会找到 swiss.css 文件,因为它会在 do{}catch{} 块中打印 items,但有时找不到文件夹, try fm.contentsOfDirectory(atPath: kPath.path) 引发在 catch{} 块打印以下内容时捕获的错误:

Error: Error Domain=NSCocoaErrorDomain Code=260 "The folder “HTML” doesn’t exist." UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/103869C9-9D46-4595-A370-A93BCD75D495/Documents/HTML, NSUserStringVariant=( Folder ), NSUnderlyingError=0x1c044e5e0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

这在模拟器和真实设备上都会发生。

导致此问题的原因是什么?为什么它有时会找到文件夹而有时却找不到,即使没有重新启动应用程序,这可能会重置 documents directory 路径,即使我没有强制路径,而是我将其获取为你可以看到上面。

我不明白这种行为。

更新

所以我在想直接读取 html 字符串可能会导致一些问题,所以我更改了我的 markdown: String? 声明。

在其中,我将转换后的 markdown 字符串(不是 html 字符串)写入同一路径下的文件中,名为 index.html.

现在人们认为发生了不同的事情——当我第一次推送这个视图时,它现在找到了我的 swiss.css 文件并且 html 文件检测到它并按预期使用它;到目前为止一切顺利。

但是 当我关闭这个视图控制器时,弹出到父视图控制器(按下后退按钮)然后尝试再次按下它,同样的错误(如前所述)提示:

Failed to write html to file with error: Error Domain=NSCocoaErrorDomain Code=4 "The folder “index.html” doesn’t exist." UserInfo={NSURL=file:///var/mobile/Containers/Data/Application/C0639153-6BA7-40E7-82CF-25BA4CB4A943/Documents/HTML/index.html, NSUserStringVariant=Folder, NSUnderlyingError=0x1c045d850 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

请注意,这里的一些路径与之前的不同,因为它是不同的启动并且会发生变化,但这不是这里的问题,因为我从不存储路径并强制写入它,我总是获取 URL使用默认的文件管理器提取,

更新后的 markdown 声明如下:

public var markdownString: String? {
    didSet {
        guard let kPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("HTML", isDirectory: true) else { return }

        guard let str = markdownString else { return }
        let html = getHTML(str: str)

        do {
            let writePath = kPath.appendingPathComponent("index.html")
            try html.write(to: writePath, atomically: true, encoding: .utf8)
            webView.loadFileURL(writePath, allowingReadAccessTo: kPath)
        } catch {
            print("Failed to write html to file with error:", error)
            return
        }

        let fm = FileManager.default
        do {
            let items = try fm.contentsOfDirectory(atPath: kPath.path)
            print("items:", items)
        } catch {
            print("Error:", error)
        }   
    }
}

不知何故,这让我更加困惑。

为什么它在第一个演示文稿中起作用,然后就不起作用了?

您在更新部分提到了以下错误:
Failed to write html to file with error: Error Domain=NSCocoaErrorDomain Code=4 "The folder “index.html” doesn’t exist."
据我了解你的问题,index.html 不应该是一个文件夹,而是一个文件。

我不知道为什么它将 index.html 视为文件夹,但也许会替换
let writePath = kPath.appendingPathComponent("index.html")

let writePath = kPath.appendingPathComponent("index.html", isDirectory: false)
帮助或至少导致更具体的错误消息。

错误告诉您它无法写入该文件路径,这意味着它不存在。在尝试将 index.html 写入 writePath 之前,您应该在 markdownString 方法中通过添加对 FileManager.default.fileExists(atPath: kPath.path) 的检查来确认这一点。

确认后,您需要跟踪 HTML 文件夹创建的生命周期(只需不断检查 FileManager.default.fileExists(atPath: thePath) 直到找到 disappearing/not 的创建时间。

从您发布的代码来看,没有明显的迹象表明为什么会发生这种情况。它必须在您应用的其他地方。