I am making PDF using TPPDF library. This makes the PDF fine and returns a URL which is like this:
pdfURL = file:///Users/taimoorarif/Library/Developer/CoreSimulator/Devices/8A2723A7-DD69-4551-A297-D30033734181/data/Containers/Data/Application/EE60FB55-13AE-4658-A829-8A85B2B6ED95/tmp/SwiftUI.pdf
If I open this URL in Google Chrome, this shows my PDF. But when I try to use
UIApplication.shared.open(pdfURL)
It did nothing.
I also made a UIViewRepresentable PDFKitView:
import SwiftUI
import PDFKit
struct PDFKitView: View {
var url: URL
var body: some View {
PDFKitRepresentedView(url)
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
}
}
And use it as:
PDFKitView(url: URL(string: pdfURL.absoluteString))
It also not worked.
I know this url is the path where this file is saved. So, after searching on this, I tried:
let fileURL = Bundle.main.url(forResource: pdfURL, withExtension: "pdf")!
And application is crashing here with error:
Unexpectedly found nil while unwrapping an Optional value
The question here is I want to open this PDF in my APP, whether it opens on Safari or Google Drive but don't know how to do that. So how can I open my PDF?
Bundle.main is not FileManager. Bundle.main is for accessing your app's resources like a video, image that you added to your project using Xcode.
URL(string:) is meant for online urls. To initialize an URL for a file use URL(fileURLWithPath: anyURL.path). So what you really should do is:
PDFKitView(url: URL(fileURLWithPath: pdfURL.path))
or
PDFKitView(url: pdfURL)
Related
I am trying to load PDF from URL using WKWebiew using following code and I have also added necessary delegate methods of WKWebiew.
func loadPDFDocument()
{
if let url = URL(string: self.contentURL)
{
print("URL: \(url)")
if UIApplication.shared.canOpenURL(url) {
self.webView.navigationDelegate = self
self.webView.load(URLRequest(url: url))
} else {
self.showInvalidURLError()
}
} else {
self.showInvalidURLError()
}
}
It is loading but actual content is not showing up, instead it shows like following image:
Now, I have tried it with PDFKit using following code and it is loading the actual content.
func loadPDFDocument()
{
let pdfView = self.createPdfView(withFrame: self.view.bounds)
if let pdfDocument = self.createPdfDocument() {
self.view.addSubview(pdfView)
pdfView.document = pdfDocument
}
}
func createPdfDocument() -> PDFDocument?
{
if let resourceUrl = URL(string: "https://d1shcqlf263trc.cloudfront.net/Engage/Contents/LearningStore/16335111672611633500529010123TestPDFfile06Oct2021.pdf") {
return PDFDocument(url: resourceUrl)
}
return nil
}
func createPdfView(withFrame frame: CGRect) -> PDFView
{
let pdfView = PDFView(frame: frame)
pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
pdfView.autoScales = true
return pdfView
}
The reasons why I want to load this PDF using WKWebiew are following:
I can get callback once URL did finish loading (with or without error)
I also need to load other types of content e.g. PPT, so I can reuse WKWebiew code.
What may be the issue due to which that PDF is not being able to load using WKWebiew. Is that issue with PDF, or with URL or the way I load it with WKWebiew?
The code you have provided directly does not work, and leads to the following error
WebPageProxy::didFailProvisionalLoadForFrame: frameID=3, domain=WebKitErrorDomain, code=102
I did not find any official apple documentation for this specific error but a simple search points to some kind of interruption.
As an alternative, you can try to load data from the URL:
if let data = try? Data(contentsOf: url) {
self.webView.load(data, mimeType: "", characterEncodingName: "", baseURL: url)
}
But this leads to the weird page you have shown in your question.
Here, the web view does not know that you are trying to show a PDF. Simply providing the content type fixes the issue.
if let data = try? Data(contentsOf: url) {
self.webView.load(data, mimeType: "application/pdf", characterEncodingName: "UTF8", baseURL: url)
}
So, you need to make sure the web view knows what type of content you're going to load.
How you would do that for various file types you want to support is a different question you need to figure out.
this is my first time trying to add an AR preview into an app (also my first time using the file system). I have been trying to implement a solution similar to that explained here https://developer.apple.com/forums/thread/126377 however one key difference is that my usdz model is not in my main bundle as it is generated and downloaded from an external source at run time. I was wondering if it is possible to display a file stored in the apps documents or cache directory and how it is done.
The file is downloaded and stored in the caches directory as follows:
class ModelFetcher: NSObject{
var modelUrl: URL?
func generateModel() {
guard let url = URL(string: "http://127.0.0.1:5000/model.usdz") else {return}
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
var request = URLRequest(url: url)
request.httpMethod = "POST"
let downloadTask = urlSession.downloadTask(with: request)
downloadTask.resume()
}
}
extension ModelFetcher: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("File Downloaded Location- ", location)
guard let url = downloadTask.originalRequest?.url else {
return
}
let docsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let destinationPath = docsPath.appendingPathComponent(url.lastPathComponent)
try? FileManager.default.removeItem(at: destinationPath)
do {
try FileManager.default.copyItem(at: location, to: destinationPath)
self.modelUrl = destinationPath
print("File moved to: \(modelUrl!.absoluteURL)")
} catch let error {
print("Copy Error: \(error.localizedDescription)")
}
}
}
I then try to display the model using ARQuicklookPreview as follows:
import SwiftUI
import QuickLook
import ARKit
struct ARQuickLookView: UIViewControllerRepresentable {
var allowScaling: Bool = true
func makeCoordinator() -> ARQuickLookView.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> QLPreviewController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
return controller
}
func updateUIViewController(_ controller: QLPreviewController,
context: Context) {
// nothing to do here
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
let parent: ARQuickLookView
let destinationPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("model.usdz")
private lazy var fileURL: URL = destinationPath
init(_ parent: ARQuickLookView) {
self.parent = parent
super.init()
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(
_ controller: QLPreviewController,
previewItemAt index: Int
) -> QLPreviewItem {
let fileURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("model.usdz")
print(fileURL)
let item = ARQuickLookPreviewItem(fileAt: fileURL)
print(item)
item.allowsContentScaling = parent.allowScaling
return item
}
}
}
struct ARQuickLookView_Previews: PreviewProvider {
static var previews: some View {
ARQuickLookView()
}
}
However, I receive the error "Unhandled item type 13: contentType is: (null) #PreviewItem" in the console and in the UI it reads unsupported file type, I have performed tests to make sure the file is in the location of the URL and I can even open it in preview on my mac so it's not like the file format or location is wrong.
Any help on displaying a model from a location other than the main bundle would be helpful, or perhaps a workaround to first move the file then display it.
Edit: After changing the preview controller function to
let fileURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("model.usdz")
return fileUrl as QLPreviewItem
Now in the UI it just says "model universal scene description Package"
And in the console I still get an error however now it reads: Unhandled item type 13: contentType is: com.pixar.universal-scene-description-mobile #PreviewItem
Which doesn't make sense as that it one of the only types it supports
Thanks,
Louis
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)
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))
I'm trying to open a .pdf file after download which is downloaded with Alamofire. But I've seen only using a "webview". Thus the application consumes lots of memory and is not viable.
What I want is to open it with the native device application. Any suggestions? Thank you.
Edit: This is my code for download file:
var localPath: NSURL?
Alamofire.download(.GET, url, destination: { (temporaryURL, response) in
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let pathComponent = response.suggestedFilename
localPath = directoryURL.URLByAppendingPathComponent(pathComponent!)
return localPath!
})
.response { (request, response, _, error) in
if error != nil
{
// got an error in getting the data, need to handle it
print("Error: \(error!)")
}
//print(response)
print("Download file en:\(localPath!)")
self.view.hideToastActivity()
//self.actioncall()
}
}
I need open file from localpath...
You should use UIDocumentInteractionController. You can read about it on this Apple documentation page.
By doing some Googling you should see even some example implementations. For example here you can see some code about this done by "mattneub".
I let you one more code that you can use:
var documentInteractionController: UIDocumentInteractionController!
#IBAction func openDocument(sender: UIButton) {
let URL: NSURL = NSBundle.mainBundle().URLForResource("yourPDF", withExtension: "pdf")!
if (URL != "") {
// Initialize Document Interaction Controller
self.documentInteractionController = UIDocumentInteractionController(URL: URL)
// Configure Document Interaction Controller
self.documentInteractionController.delegate = self
// Present Open In Menu
self.documentInteractionController.presentOptionsMenuFromRect(sender.frame, inView: self.view, animated: true)
//presentOpenInMenuFromRect
}
}
// UIDocumentInteractionControllerDelegate
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) -> UIViewController {
return self
}
In my Swift iOS app, I want to download some dynamic HTML pages from a remote server, save them in the document directory, and display those pages from document directory.
I was using this to load the page:
var appWebView:WKWebView?
...
appWebView!.loadRequest(NSURLRequest(URL: NSURL(fileURLWithPath: htmlPath)))
Everything works on the simulator, but when I moved to real phones, it just showed a blank page. I then connected to the app using Safari, and found it complained with "Failed to load resource".
I then tried to first read the content of the page at htmlPath, then use
appWebView!.loadHTMLString()
to load the page. It works when the HTML page is simple. But if the HTML references something else, i.e. a JavaScript file also in the document directory (with an absolute path like <script src="file:////var/mobile/Containers/Data/Application/762035C9-2BF2-4CDD-B5B1-574A0E2B0728/Documents/xxxxx.js">), it will fail to load.
Does anyone know why this happens, and how to resolve the issue?
More info:
XCode version: 7.3.1
Deployment Target: 8.1 (I tried to use 9.3 too, but that didn't help.)
This is a simplified version of what I have used to load local files in a project of mine (iOS 10, Swift 3). I have just updated my code (7.5.2017) after testing it out again on iOS 10.3.1 and iPhone 7+ as requested by Raghuram and Fox5150 in the comments.
I just created a completely new project and this is the folder structure:
Update 19.04.2018: Added a new feature to download a .zip with HTML, CSS, JS files, unzip it in /Documents/ (Alamofire + Zip) and then load those files into the webView. You can find it in the GitHub sample project as well. Again, feel free to fork & star! :)
Update 08.02.2018: finally added a GitHub sample project, which also includes a local JavaScript file. Feel free to fork & star! :)
Version 1 with webView.loadFileURL()
ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView()
let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
let htmlUrl = URL(fileURLWithPath: htmlPath!, isDirectory: false)
webView.loadFileURL(htmlUrl, allowingReadAccessTo: htmlUrl)
webView.navigationDelegate = self
view = webView
}
}
Version 2 with webView.loadHTMLString()
ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView()
let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
let folderPath = Bundle.main.bundlePath
let baseUrl = URL(fileURLWithPath: folderPath, isDirectory: true)
do {
let htmlString = try NSString(contentsOfFile: htmlPath!, encoding: String.Encoding.utf8.rawValue)
webView.loadHTMLString(htmlString as String, baseURL: baseUrl)
} catch {
// catch error
}
webView.navigationDelegate = self
view = webView
}
}
Gotchas to look out for:
Make sure that your local html/js/css files are in Project -> Target -> Build Phases -> Copy Bundle Resources
Make sure that your html files don't reference relative paths e.g. css/styles.css because iOS will flatten your file structure and styles.css will be on the same level as index.html so write <link rel="stylesheet" type="text/css" href="styles.css"> instead
Given the 2 versions and the gotchas here are my html/css files from the project:
web/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Offline WebKit</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<h1 id="webkit-h1">Offline WebKit!</h1>
</body>
</html>
web/css/styles.css
#webkit-h1 {
font-size: 80px;
color: lightblue;
}
If somebody wants a GitHub sample project, tell me in the comments section and I'll upload it.
Swift 4 Method
This method allows WKWebView to properly read your hierarchy of directories and sub-directories for linked CSS/JS files. You do NOT need to change your HTML, CSS or JS code.
Updated for Xcode 9.3
Step 1
Import the folder of local web files anywhere into your project. Make sure that you:
☑️ Copy items if needed
☑️ Create folder references (not "Create groups")
☑️ Add to targets
Step 2
Go to the View Controller with the WKWebView and add the following code to the viewDidLoad method:
let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "website")!
webView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
webView.load(request)
index – the name of the file to load (without the .html extension)
website – the name of your web folder (index.html should be at the root of this directory)
Conclusion
The overall code should look something like this:
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.uiDelegate = self
webView.navigationDelegate = self
let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "Website")!
webView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
webView.load(request)
}
}
If any of you have further questions about this method or the code, I'll do my best to answer. :)
This solution helped me:
[configuration.preferences setValue:#YES forKey:#"allowFileAccessFromFileURLs"];
This works well (Swift 3, Xcode 8):
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
if let url = Bundle.main.url(forResource: "file", withExtension: "txt")
{
do
{
let contents = try String(contentsOfFile: url.path)
webView.loadHTMLString(contents, baseURL: url.deletingLastPathComponent())
}
catch
{
print("Could not load the HTML string.")
}
}
}
}
This works nicely with file URL or remote URL, and whether file is in the bundle or in documents:
if url.isFileURL {
webView.loadFileURL(url, allowingReadAccessTo: url)
} else {
let request = URLRequest(url: url)
webView.load(request)
}
Constructing the URLs this way allowed me to load resources from the document directory with WKWebView:
guard let docDir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) else {
return
}
let resourceURL = docDir.appendingPathComponent("/Path/To/Your/Resource")
self.wkWebView.loadFileURL(resourceURL, allowingReadAccessTo: docDir)
The files must be in the document directory.
I implemented the following to retrieve a document:
let documentDirUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let fileNameWithExtension = "IMG_0002.PNG"
let indexFileUrl = documentDirUrl.appendingPathComponent(fileNameWithExtension)
if FileManager.default.fileExists(atPath: indexFileUrl.path) {
webView.loadFileURL(indexFileUrl, allowingReadAccessTo: documentDirUrl)
}
Check that ticket: iOS: How to load local files (not in the bundle) in a WKWebView?
var nsurl = URL(fileURLWithPath: URL(fileURLWithPath: URL(fileURLWithPath: documentsDirectory()).appendingPathComponent(user_appli).absoluteString).appendingPathComponent("index.html").absoluteString) //locally
var readAccessToURL: URL? = nsurl.deletingLastPathComponent?.deletingLastPathComponent
if let anURL = readAccessToURL {
webView?.loadFileURL(nsurl, allowingReadAccessTo: anURL)
}