4
votes

I have been using UIWebView to display Microsoft Office documents (Word, PowerPoint, Excel) in my application for a while but Apple has recently deprecated the UIWebView class. I am trying to switch to WKWebView but Word, Excel, and Powerpoint documents are not rendering properly in WKWebView.

Using UIWebView to display an Excel document (worked great):

let data: Data
//data is assigned bytes of Excel file
let webView = UIWebView()
webView.load(data, mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", textEncodingName: "UTF-8", baseURL: Bundle.main.bundleURL)

Attempting to use WKWebView to do the same thing (displays a bunch of nonsense characters instead of the Excel file):

let data: Data
//data is assigned bytes of Excel file
let webView = WKWebView.init()
webView.load(data, mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", characterEncodingName: "UTF-8", baseURL: Bundle.main.bundleURL)

Due to the nature of my use case, I cannot save the data to disk for security reasons so I cannot use methods like this:

webView.loadFileURL(<#T##URL: URL##URL#>, allowingReadAccessTo: <#T##URL#>)

I also cannot use QuickLook (QLPreviewController) because it again requires a URL.

---------------------------------------------------------------EDIT---------------------------------------------------------

I am also aware of this method of passing the data in via a string URL but unless someone can prove that the data is never written to disk, I cannot accept it as an answer:

let data: Data
//data is assigned bytes of Excel file
let webView = WKWebView.init()
let urlStr = "data:\(fileTypeInfo.mimeType);base64," + data.base64EncodedString()
let url = URL(string: urlStr)!
let request = URLRequest(url: url)
webView.load(request)
1
Are you able to copy past in office online editor?Parag Bafna

1 Answers

3
votes

This feels like a bug in WKWebView.load(_ data: Data, mimeType MIMEType: String, characterEncodingName: String, baseURL: URL) -> WKNavigation?. It should work in the way we were trying to use it but here is how we got around the issue:

Declare your WKWebView and a custom scheme name

let webView: WKWebView
let customSchemeName = "custom-scheme-name"

Create a subclass of WKURLSchemeHandler. We are using the webView to display a single document (PDF, Word, PowerPoint, or Excel) for the life of the webView so we pass in that document as Data and the FileTypeInfo which is a custom class we made that has the file's MIME type among other things, in the init of the WKURLSchemeHandler.

private class ExampleWKURLSchemeHandler: NSObject, WKURLSchemeHandler {

private let data: Data
private let fileTypeInfo: FileTypeInfo

init(data: Data, fileTypeInfo: FileTypeInfo) {
    self.data = data
    self.fileTypeInfo = fileTypeInfo
    super.init()
}

func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
    if let url = urlSchemeTask.request.url, let scheme = url.scheme, scheme == customSchemeName {
        let response = URLResponse.init(url: url, mimeType: fileTypeInfo.mimeType, expectedContentLength: data.count, textEncodingName: nil)

        urlSchemeTask.didReceive(response)
        urlSchemeTask.didReceive(data)
        urlSchemeTask.didFinish()
    }
}

func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
    //any teardown code you may need
}

}

Instantiate your webView with the custom scheme handler class you just made:

let webViewConfiguration = WKWebViewConfiguration()
let webViewSchemeHandler = ExampleWKURLSchemeHandler.init(data: data, fileTypeInfo: fileTypeInfo)
webViewConfiguration.setURLSchemeHandler(webViewSchemeHandler, forURLScheme: customSchemeName)
self.webView = WKWebView.init(frame: .zero, configuration: webViewConfiguration)

Tell the webView to load the document using a URL that matches your custom scheme. You can pass whatever you want in the url after the customSchemeName prefix but for our use case we didn't need to because we already passed the document that we wanted to display in the initializer of the WKSchemeHandler:

guard let url = URL.init(string: "\(customSchemeName):/123") else {
            fatalError()
        }
webView.load(URLRequest.init(url: url))