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?
1 Answers
Answers 1
In your UPDATE section you mention the following error:
Failed to write html to file with error: Error Domain=NSCocoaErrorDomain Code=4 "The folder “index.html” doesn’t exist."
As far as I understood your issue, index.html should not be a folder but a file.
I have no idea why it treats index.html as folder, but maybe replacing
let writePath = kPath.appendingPathComponent("index.html")
with
let writePath = kPath.appendingPathComponent("index.html", isDirectory: false)
helps or at least leads to a more specific error message.
0 comments:
Post a Comment