Posts

Post marked as solved
6 Replies
30k Views
Hello forum!I have a special problem with my iOS App.My setup is the following:Swift 4 app with minimum target iOS 11the app consists more or less of single view with a WKWebView which is connected to the client portal of a health insurancethe client portal provides a login (session cookies) and allows to download pdf documents when logged-inI want the downloades pdf files to be opened in QuickLookPreviewController instead of WKWebView.The customer need is that they want to be able to print or share their documents and I like to use the QuickLookPreview therefore, because it offers the best native feeling for this case.Now to my problem:I managed to download the documents to local storage (temp folder) and to open it in QuickLookPreviewController with a little trick:The response of the download is intercepted (by using webView(..., decidePolicyFor ...)) and the download url is used to trigger a separate download (using URLSession.shared.dataTask(with: downloadUrl)) to iPhone storage, because QuickLookPreviewController needs a local file and cannot deal with the download url to the pdf.In general this works - most of the time. Since I need to be in a logged-in state to download the pdf, I have to pass the authentication cookies from WKWebView (where I logged-in) to the shared cookie storage (which is used by the download task). This cookie sync between the two cookie storages is a known issue and I think I read all the related postings in the web, but couldn't get it working reliably.In my debug logs I can see that my logic works in general, but sometimes the cookies aren't synchronized at all.Here's my code. I appreciate every help 🙂 import UIKit import WebKit import QuickLook class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, QLPreviewControllerDataSource { @IBOutlet var webView: WKWebView! var documentPreviewController = QLPreviewController() var documentUrl = URL(fileURLWithPath: "") var webViewCookieStore: WKHTTPCookieStore! let webViewConfiguration = WKWebViewConfiguration() ... override func viewDidLoad() { super.viewDidLoad() // link the appDelegate to be able to receive the deviceToken let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.viewController = self // initial configuration of custom JavaScripts webViewConfiguration.userContentController = userContentController // QuickLook document preview documentPreviewController.dataSource = self webView = WKWebView(frame: CGRect.zero, configuration: webViewConfiguration) // see "The 2 delegates": https://samwize.com/2016/06/08/complete-guide-to-implementing-wkwebview/ webView.uiDelegate = self webView.navigationDelegate = self view.addSubview(webView) webViewCookieStore = webView.configuration.websiteDataStore.httpCookieStore ... load("https://clientportal.xyz"!) } private func load(_ url: URL) { load(URLRequest(url:url)) } private func load(_ req: URLRequest) { var request = req request.setValue(self.deviceToken, forHTTPHeaderField: "iosDeviceToken") request.setValue(self.myVersion as? String, forHTTPHeaderField: "iosVersion") request.setValue(self.myBuild as? String, forHTTPHeaderField: "iosBuild") request.setValue(UIDevice.current.modelName, forHTTPHeaderField: "iosModelName") debugPrintHeaderFields(of: request, withMessage: "Loading request") webView.load(request) debugPrint("Loaded request=\(request.url?.absoluteString ?? "n/a")") } /* Intercept decision handling to be able to present documents in QuickLook preview Needs to be intercepted here, because I need the suggestedFilename for download */ func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { let url = navigationResponse.response.url if (openInDocumentPreview(url!)) { let documentUrl = url?.appendingPathComponent(navigationResponse.response.suggestedFilename!) loadAndDisplayDocumentFrom(url: documentUrl!) decisionHandler(.cancel) } else { decisionHandler(.allow) } } /* Download the file from the given url and store it locally in the app's temp folder. The stored file is then opened using QuickLook preview. */ private func loadAndDisplayDocumentFrom(url downloadUrl : URL) { let localFileURL = FileManager.default.temporaryDirectory.appendingPathComponent(downloadUrl.lastPathComponent) // getAllCookies needs to be called in main thread??? (https://medium.com/appssemble/wkwebview-and-wkcookiestore-in-ios-11-5b423e0829f8) //??? needed?? DispatchQueue.main.async { self.webViewCookieStore.getAllCookies { (cookies) in for cookie in cookies { if cookie.domain.range(of: "my.domain.xyz") != nil { HTTPCookieStorage.shared.setCookie(cookie) debugPrint("Sync cookie [\(cookie.domain)] \(cookie.name)=\(cookie.value)") } else { debugPrint("Skip cookie [\(cookie.domain)] \(cookie.name)=\(cookie.value)") } } debugPrint("FINISHED COOKIE SYNC") debugPrint("Downloading document from url=\(downloadUrl.absoluteString)") URLSession.shared.dataTask(with: downloadUrl) { data, response, err in guard let data = data, err == nil else { debugPrint("Error while downloading document from url=\(downloadUrl.absoluteString): \(err.debugDescription)") return } if let httpResponse = response as? HTTPURLResponse { debugPrint("Download http status=\(httpResponse.statusCode)") } // write the downloaded data to a temporary folder do { try data.write(to: localFileURL, options: .atomic) // atomic option overwrites it if needed debugPrint("Stored document from url=\(downloadUrl.absoluteString) in folder=\(localFileURL.absoluteString)") DispatchQueue.main.async { self.documentUrl = localFileURL self.documentPreviewController.refreshCurrentPreviewItem() self.present(self.documentPreviewController, animated: true, completion: nil) } } catch { debugPrint(error) return } }.resume() } } func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { return documentUrl as QLPreviewItem } /* We always have just one preview item */ func numberOfPreviewItems(in controller: QLPreviewController) -> Int { return 1 } /* Checks if the given url points to a document provided by Vaadin FileDownloader and returns 'true' if yes */ private func openInDocumentPreview(_ url : URL) -> Bool { return url.absoluteString.contains("/APP/connector") } P.S. I tried many solutions:Reading and syncing the cookies in main thread (there are many hints in the web, that you have to acces cookies in this way) - does not work reliablyReading the WKWebViews cookies via Javascript to avoid the main thread handling - but here I get too less information about the cookies (e.g. domain and path is missing)I configured a WKProcessPool - didn't helpMost of the time the cookies are synchronized, but with the same implementation I get problems when trying it later on, e.g. when I uploaded the presumably working code to TestFlight. It even differs when using a solution in Simulator or on a real device.The error is a downloaded file (HTTP status 200), but its size is less than 8 kB, because I wasn't allowed to download it it because of the missing synchronized authentication cookie.
Posted
by RSymp.
Last updated
.