Display PDF file in Swift 4 - ios

import UIKit
import WebKit
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {
var docController: UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
docController = UIDocumentInteractionController.init(url: URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(urlVal!))
docController.delegate = self
docController.presentPreview(animated: true)
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}
Above code I'm not able to display the pdf file. Can anyone help?

By seeing your code it seems that you missed to add UIDocumentInteractionControllerDelegate delegate method.
class ViewController: UIViewController,UIDocumentInteractionControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
var docController = UIDocumentInteractionController.init(url: URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(urlVal!))
docController.delegate = self
docController.presentPreview(animated: true)
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}
OR
You can also view PDF by loading it into WKWebView.
override func viewDidLoad() {
super.viewDidLoad()
let pdfFilePath = Bundle.main.url(forResource: "iostutorial", withExtension: "pdf")
let urlRequest = URLRequest.init(url: pdfFilePath!)
webView = WKWebView(frame: self.view.frame)
webView.load(request)
self.view.addSubview(webView)
}

Basically you are missing the UIDocumentInteractionControllerDelegate implementation. For preview, you should implement this method
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController
Return which ViewController need to display the Preview. If you pass the self View Controller it will display the PDF preview in the existing view controller modally. Just do this one in your View controller if you want to display the preview in the same controller.
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
If you are already doing this in your code, there is high chance of PDF URL path might be wrong.

I would use the Quicklook framework instead, it supports a wide range of document types:
iWork documents
Microsoft Office documents
PDF files
Images
Text files
Rich-Text Format documents
Comma-Separated Value files (csv)
Supports sharing of the relevant documents as well and is easy to implement.
Follow this tutorial on how to do it in Swift: https://www.appcoda.com/quick-look-framework/

Swift 5
Import this framework
import SafariServices
Then call this sentences whenever you need
if let url = URL(string: "YOUR_PDF_URL") {
let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = true
let vc = SFSafariViewController(url: url, configuration: config)
present(vc, animated: true)
}
The code will present a Safari view with the pdf on it.

Related

How can I link an app page to a website on Xcode 12?

I'm a beginner in programming and am trying to make a simple app for displaying a webpage after choosing from menu options. However, I'm having trouble linking the code window to the storyboard, especially for linking a new page for the app to a new class. I think it's partly b/c I can't view the code and storyboard side by side.
I've also written code for displaying the website in on an app page, but I'm getting an error message saying "Argument passed to call that takes no arguments" (code below). Any tips on how I can address these?
import WebKit
import UIKit
//class ViewController: UIViewController {
class WebViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad() {
let url = URL(string: "https://google.com")!
webView.load(URLRequest(url: url))
}
}
override func viewDidLoad() {
super.viewDidLoad() {
let url = URL(string: "https://google.com")!
webView.load(URLRequest(url: url))
}
}
Above should be:
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://google.com")!
webView.load(URLRequest(url: url))
}
without the inner brackets.

Whats the best way to preserve the state of the WebView in iOS?

For example, when I load a url inside the WebView in an iOS app, and when user clicked back button and navigates in the other sections of the app and when user returns back to the WebView section of the app, the WebView section should not be reloaded. Not only that, even if the user goes to other applications in the smartphone and returns back to the WebView section of the app, the WebView section should not be reloaded. How is it possible to preserve the state of the WebView in iOS?
Any possible solutions are welcomed!
You could possibly use UserDefaults to help with this. Something like the following untested code:
let defaultURLString = "https://yoursite.com/home"
override func viewDidLoad() {
super.viewDidLoad()
if let savedURL = UserDefaults.standard.url(forKey: "webViewUrl") {
// Load webview with url here
webView.load(URLRequest(url: savedURL))
} else {
// Load default url here
if let defaultURL = URL(string: self.defaultURLString) {
webView.load(URLRequest(url: defaultURL))
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let currentUrl = webView.url {
UserDefaults.standard.set(currentUrl, forKey: "webViewUrl")
}
}
You may need to clear the defaults for the webViewUrl key upon app startup.
If you would like the user to go to the same url every time they return to the webView try:
let defaultURLString = "https://yoursite.com/home"
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let defaultURL = URL(string: self.defaultURLString) {
webView.load(URLRequest(url: defaultURL))
}
}
If you would like the user to not be able to navigate to other links within the webView, set the delegate to self and add the delegate method shouldStartLoadWith. Here you can check if the user clicked a link and prevent them from going anywhere, like so:
class TestViewController: UIViewController, UIWebViewDelegate {
#IBOutlet private(set) var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.webView.delegate = self
let url = URL(string: "https://yoursite.com/home")
self.webView.loadRequest(URLRequest(url: url!))
}
// MARK: UIWebViewDelegate
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
if navigationType == .linkClicked {
return false
}
return true
}
}

WebView does not load

I just started developing with swift, so I am sorry if the question is basic/stupid.
I have the following setup, just a test
import WebKit
import UIKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://hackingswift.com")!
webView.load(URLRequest(url:url))
webView.allowsBackForwardNavigationGestures = true
}
}
Unfortunately the browser doesn't load. The simulator only shows an empty navigation bar.
Suggestions? I am following a tutorial on hackingswift, so it's supposed to work.
You have to add webView as a subview or make an IBOutlet using Interface builder.
Try this:
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView?
func loadView() {
webView = WKWebView()
webView?.navigationDelegate = self
self.view.addSubview(webView!)
}
override func viewDidLoad() {
super.viewDidLoad()
self.loadView()
let url = URL(string: "https://hackingswift.com")!
webView?.load(URLRequest(url:url))
webView?.allowsBackForwardNavigationGestures = true
}
}
If you want it a bit more simple (without nullable variable), for example:
class ViewController: UIViewController, WKNavigationDelegate {
var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
self.view.addSubview(webView)
webView?.allowsBackForwardNavigationGestures = true
self.loadUrl("https://hackingswift.com")
}
func loadUrl(_ url: String) {
if let url = URL(string: url) {
webView.load(URLRequest(url:url))
}
}
}
EDIT: it looks like some websites to load, while others do not, even if they are secure. If I put apple.com in the example, it loads, but a few others do not
Your url should be started with http or https for the webView to load.
Another possible reason is that your url containing an invalid certificate. Add the delegate function below into your code. You have to let WKWebView to bypass the certificate checking. However, this code is never recommended to go into production. You should be careful about what website your webView should and will load.
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let cred = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, cred)
}
The problem is this line:
let url = URL(string: "https://hackingswift.com")!
There is no such URL on the Internet, so you're not actually going to see anything. (You won't see anything if you paste that URL into any browser.)
So change that line to this:
let url = URL(string: "https://www.hackingwithswift.com")!
Now run the app, and presto, you'll see the web site:

How to use UIDocumentInteractionController?

I have created a program that loads PDF files. I want when the user is editing that they can publish the file anywhere in PDF format.
Do I use UIDocumentInteractionController or use UIActivityViewController?
Here is the code:
import UIKit
import PDFKit
#available(iOS 11.0, *)
#available(iOS 11.0, *)
class PDFViewControllerEN: UIViewController {
var document: UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
// retrieve URL to file in main bundle`
}
#IBOutlet var pdfview: UIView!
#IBAction func share(_ sender: UIButton) {
}
#IBAction func doAction2(_ sender: UIBarButtonItem) {
document.presentOptionsMenu(from: view.bounds, in: view, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
//Here you are going to display your PdfController
//PDFController that is seprate class you had created to show pdf file being opened
//i.e
//check which button was being selected
switch ButtonSelected.Tag {
case 0:
var document: UIDocumentInteractionController = {
let pdfView = PDFView(frame: UIScreen.main.bounds)
let url = Bundle.main.url(forResource: "EN1", withExtension: "pdf")
let vc = UIDocumentInteractionController(url: url!)
pdfView.document = PDFDocument(url: url!)
view.addSubview(pdfView)
vc.delegate = self
return vc
}()
// document.presentPreview(animated: true)
break
case 1:
//here control when you selected button with tag 0
//here need to open pdf AR2
//set Frame here all bounds
var document: UIDocumentInteractionController = {
let pdfView = PDFView(frame: UIScreen.main.bounds)
let url = Bundle.main.url(forResource: "EN2", withExtension: "pdf")
let vc = UIDocumentInteractionController(url: url!)
pdfView.document = PDFDocument(url: url!)
view.addSubview(pdfView)
vc.delegate = self
return vc
}()
break
case 2:
//here control when you selected button with tag 0
//here need to open pdf AR2
//set Frame here all bounds
var document: UIDocumentInteractionController = {
let pdfView = PDFView(frame: UIScreen.main.bounds)
let url = Bundle.main.url(forResource: "EN3", withExtension: "pdf")
let vc = UIDocumentInteractionController(url: url!)
pdfView.document = PDFDocument(url: url!)
view.addSubview(pdfView)
vc.delegate = self
return vc
}()
break
default:
//Error Case
print("No tag Value Available")
}
}
}
#available(iOS 11.0, *)
extension PDFViewControllerEN: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}
I have tried and explained in detail about UIDocumentInteractionController. Refer this link if you want to check the details.
https://medium.com/if-let-swift-programming/managing-files-in-ios-dfcdfdc1f426
Code
extension ViewController {
/// This function will set all the required properties, and then provide a preview for the document
func share(url: URL) {
documentInteractionController.url = url
documentInteractionController.uti = url.typeIdentifier ?? "public.data, public.content"
documentInteractionController.name = url.localizedName ?? url.lastPathComponent
documentInteractionController.presentPreview(animated: true)
}
/// This function will store your document to some temporary URL and then provide sharing, copying, printing, saving options to the user
func storeAndShare(withURLString: String) {
guard let url = URL(string: withURLString) else { return }
/// START YOUR ACTIVITY INDICATOR HERE
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
let tmpURL = FileManager.default.temporaryDirectory
.appendingPathComponent(response?.suggestedFilename ?? "fileName.png")
do {
try data.write(to: tmpURL)
} catch {
print(error)
}
DispatchQueue.main.async {
/// STOP YOUR ACTIVITY INDICATOR HERE
self.share(url: tmpURL)
}
}.resume()
}
}
extension ViewController: UIDocumentInteractionControllerDelegate {
/// If presenting atop a navigation stack, provide the navigation controller in order to animate in a manner consistent with the rest of the platform
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
guard let navVC = self.navigationController else {
return self
}
return navVC
}
}
extension URL {
var typeIdentifier: String? {
return (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier
}
var localizedName: String? {
return (try? resourceValues(forKeys: [.localizedNameKey]))?.localizedName
}
}
Calling
#IBAction func showOptionsTapped(_ sender: UIButton) {
/// Passing the remote URL of the file, to be stored and then opted with mutliple actions for the user to perform
storeAndShare(withURLString: "https://images5.alphacoders.com/581/581655.jpg")
}
Note
https://www.bignerdranch.com/blog/working-with-the-files-app-in-ios-11/
Before your files can appear in the Files app, you must indicate that your app supports Open in Place and File Sharing Enabled. These options are configured using keys in your Info.plist file.
The first key is UIFileSharingEnabled, which enables iTunes sharing of files in your Documents folder.
The second key is LSSupportsOpeningDocumentsInPlace, which grants the local file provider access to files in your Documents folder.
Add these keys to your Info.plist and set their values to YES.
Using UIDocumentInteractionController is quite easy. You just need to know the url of your file, then you present the menu:
/// Needs to be global, otherwise the controller will be destroyed when the file is handed over to target application
var documentInteractionController: UIDocumentInteractionController!
class MyViewController: UIViewController {
var url: URL
...
#IBAction func share(_ sender: UIBarButtonItem) {
documentInteractionController = UIDocumentInteractionController()
documentInteractionController.url = url
documentInteractionController.uti = url.uti
documentInteractionController.presentOptionsMenu(from: sender, animated: true)
}
}
extension URL {
var uti: String {
return (try? self.resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier ?? "public.data"
}
}
Silly case but might help to someone.
To anyone who can't save files check if your controller is alive. My problem was that my UIDocumentInteractionController was destroyed after it was closed.
Here's how my function looked like:
private func showDocumentInteractionController(url: URL) {
let documentInteractionController = UIDocumentInteractionController(url: url)
documentInteractionController.presentOptionsMenu(from: view.frame, in: view, animated: true)
documentInteractionController.delegate = self
}
The fix is to make sure that UIDocumentInteractionController is alive after it closes:
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {
private let documentInteractionController = UIDocumentInteractionController()
private func showDocumentInteractionController(url: URL) {
documentInteractionController.url = url
documentInteractionController.presentOptionsMenu(from: view.frame, in: view, animated: true)
documentInteractionController.delegate = self
}
}
This should work which shows a PDF file:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
guard let path = Bundle.main.url(forResource: "YOUR_PDF_FILE_NAME_WITHOUT_EXTENSION", withExtension: "pdf") else { return }
let documentInteractionController = UIDocumentInteractionController.init(url: path)
documentInteractionController.delegate = self
documentInteractionController.presentPreview(animated: true)
}
}
}
extension ViewController: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}

webview turns nil after calling function from another controller

Hello if anybody can help me thanks!
I got two viewcontrollers HomeController and TableController
In HomeController i got a webview and when i go to the TableController and call a function to load an another request for the webview. The webview returns nil.
The HomeController is the main screen of the application which is a webview. The TableController is a table from a hamburger menu left of the application. When i open it and click on an item, I want to get the url of that item and use that url to send a request with the same HomeController. But at the moment when i reach connectUrl() i get an fatal error and it is because the webView is nil.
edit: answer below
Code below:
TableController
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var HomeViewController: HomeController {
return self.storyboard?.instantiateViewControllerWithIdentifier("homeController") as! HomeController
}
let url = self.item[indexPath.item].url
HomeViewController.connectUrl(url!)
}
HomeController
class HomeController: UIViewController{
static let sharedInstance = HomeController()
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
webView = WKWebView(frame: view.bounds, configuration: configuration)
self.view.addSubview(self.webView)
}
func connectUrl(url: String){
let url2 = NSURL(string: url)
let req = NSURLRequest(URL: url2!)
print(req)
self.webView!.loadRequest(req)
}}
Is there any domain erron in console?/
if yes
add it to plist.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
I found the solution after trial and error with many development options like delegates, segue, instantiateViewController etc.
In the end NSNotificationCenter was the solution for me.
I created this function in my TableController to send a notification to the HomeController to call ConnectUrl.
func sendData(url: String){
self.dismissViewControllerAnimated(true, completion: nil)
let itemDictionary = ["itemUrl": url]
NSNotificationCenter.defaultCenter().postNotificationName("connectUrl", object: nil, userInfo: itemDictionary)
}
}
}
In the HomeController I added an Observer to receive the noticatification and unpack the dictionary is received to get the url.
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: view.bounds, configuration: configuration)
self.view.addSubview(self.webView!)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "connectUrl:", name: "connectUrl", object: nil)
}
func connectWithSpecifiedItem(notification: NSNotification){
let itemUrl = notification.userInfo!["itemUrl"] as! String
let url2 = NSURL(string: itemUrl)
let req = NSURLRequest(URL: url2!)
self.webView!.loadRequest(req)
}
This is not a WKWebView related issue. The actual problem here is that you don't know how to return data from a view controller.
The most basic solution for this is a delegate. First, create a TableControllerDelegate.
protocol TableControllerDelegate {
func tableController(tableController: TableController, didSelectItemWithURL: NSURL)
}
Then you implement this in your HomeController:
class HomeController: UIViewController, TableControllerDelegate {
func tableController(tableController: TableController, didSelectItemWithURL url: NSURL) {
webView.loadRequest(NSURLRequest(URL: url))
}
}
In your TableController, things get a little simpler and less connected. You let it have a TableControllerDelegate field and simply call that to pass the selected item URL back to whatever view opened it.
Note that there is no direct relationship between TableController and HomeController anymore. This is a good practice.
class TableController: UITableViewController {
weak delegate: TableControllerDelegate?
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let url = self.item[indexPath.item].url
delegate?.tableController(self, didSelectItemWithURL: url)
}
}
Now when you present your TableController from your HomeController you simply configure its delegate:
func displayTableController() {
let tableController = storyboard?.instantiateViewControllerWithIdentifier("tableController") as! TableControllerController
tableController.delegate = self
}
More code, but this is a very common pattern in iOS.

Resources