I have asked this before, but had to reformulate everything so it becomes more understandable.
In my project, I have created a subfolder inside the documents directory
called HTML
with the following code:
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)")
}
}
The print statement prints the following:
Document directory is file:///var/mobile/Containers/Data/Application/103869C9-9D46-4595-A370-A93BCD75D495/Documents/HTML
Inside my app's Bundle
I have a .css
file for me to use in a HTML
string to be presented in a WKWebView
.
As the .css
file needs to be in the same folder as the HTML baseURL
, I copy that .css
file from the Bundle
to the directory created with the above function, with the following code:
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)
}
}
}
}
I know I could use both in the app's Bundle
by setting the HTML baseURL
to the main.Bundle
, but since I need to present local images inside the HTML
and not web-linked images, I had to move those into a subfolder in order for me to later copy the desired images into that subfolder, since the main.Bundle
is read-only and not writable (We cannot save there images).
Now I have a ViewController
that gets pushed through a UINavigationController
; inside that pushed ViewController
I have a WKWebView
that will present the HTML
.
I also have a var markdownString: String? {}
property that once instantiated I convert it to a HTML
string using the following function:
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!
}
My WKWebView
is declared with the following code:
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
}()
Whenever my markdown String
is set, I convert it to an HTML
string, then I instantiate the HTML baseURL
and the I load it into my webView
with the following code:
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)
}
}
Here's where my problem starts.
The swiss.css
file is in the following directory:
file:///var/mobile/Containers/Data/Application/103869C9-9D46-4595-A370-A93BCD75D495/Documents/HTML/
The HTML baseURL
points to the following path:
file:///var/mobile/Containers/Data/Application/103869C9-9D46-4595-A370-A93BCD75D495/Documents/HTML/
As you can see, they're both pointing to the same path.
Sometimes my HTML
finds the swiss.css
file, as it prints the items
in the do{}catch{}
block, but other times it doesn't find the folder, the try fm.contentsOfDirectory(atPath: kPath.path)
fires an error catched in the catch{}
block printing the following:
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"}}
This both happens in the simulator and on the real device.
What's causing this issue? Why does it finds the folder sometimes and other times it doesn't, even without rebooting the app, which may reset the documents directory
path, even though I'm not forcing the path, but instead I'm fetching it as you can see above.
I don't understand this behavior.
UPDATE
So I was thinking that reading the html
string directly might be causing some problems so I changed my markdown: String?
declaration.
In it, I write the converted markdown string (not an html string) into a file in the same path, called index.html
.
Now one think different happened -- when I push this view in the first time, it now finds my swiss.css
file and the html
file detects it and uses it as expected; so far so good.
but when I dismiss this view controller, poping to the parent view controller (pressing the back button) and then try to push it again, the same error (described previously) prompts:
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"}}
Note that some of the paths here are different than the previous, because it is a different launch and it changes, but that's not the problem here because I never store the path and force write it, I always fetch the URLs with the default File Manager fetch,
The updated markdown
declaration goes by the following:
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)
}
}
}
Somehow this confused me even more.
Why does it works on the first presentation and then it doesn't?
The error is telling you that it can't write to that file path, which means it doesn't exist. You should confirm this in your markdownString method by adding a check for FileManager.default.fileExists(atPath: kPath.path)
before you try to write index.html to the writePath.
Once you confirm that, you need to track the life-cycle of the HTML folder's creation (just keep checking FileManager.default.fileExists(atPath: thePath)
until you track down when it's disappearing/not getting created.
From the code you posted, there's no obvious indication why this is happening. It has to be somewhere else in your app.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With