webview turns nil after calling function from another controller - ios

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.

Related

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:

Display PDF file in Swift 4

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.

ScriptMessageHandler not always called on actual device, works fine on simulator

I use WKWebView and I want to be notified when website is fully loaded. The webView:didFinishNavigation method of WKNavigationDelegate is fired when document.readyState is either interactive or complete and I want to be sure that site was completely loaded. I came up with the solution which uses JavaScript injection. Here is my MWE:
import UIKit
import WebKit
class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate {
var webView: WKWebView!
#IBOutlet weak var loadLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let contentController = WKUserContentController()
let scriptPath = NSBundle.mainBundle().pathForResource("script", ofType: "js")!
let scriptString = try! String(contentsOfFile: scriptPath)
let script = WKUserScript(source: scriptString, injectionTime: .AtDocumentStart, forMainFrameOnly: true)
contentController.addUserScript(script)
contentController.addScriptMessageHandler(self, name: "readyHandler")
let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController
webView = WKWebView(frame: CGRect.zero, configuration: configuration)
webView.navigationDelegate = self
loadLabel.text = nil
}
#IBAction func loadWebsite() {
webView.loadRequest(NSURLRequest(URL: NSURL(string: "http://stackoverflow.com")!))
loadLabel.text = "Loading..."
}
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
print("message received")
loadLabel.text = "Complete"
}
}
And this is the content of script.js file:
document.onreadystatechange = function () {
if(document.readyState === "complete"){
webkit.messageHandlers.readyHandler.postMessage("");
}
}
userContentController:didReceiveScriptMessage method is always called on iOS Simulator, but on the actual device (iPhone 6 in my case) it isn't called most of the times. Any idea what can be wrong about it or what's the other way of checking if website is completely loaded?
For some reason you need to add the webView to a visible view for this to work on the device. If you don't want the webView to be visible, add it and then set the hidden property to true.
For the code example above:
func viewDidLoad(){
...
webView.hidden = true
view.addSubview(webView)
}

Error when passing data from one view controller to the other

I'm currently making an rss feed reader in swift 2 and I'm having a hard time trying to pass the url from my tableview to my web view. When I print my url in the tableviewcontroller it adds (optional) in front of the url. However when it has passed into my webviewcontroller it has no (optional) inf front of it. This makes me worry over the fact that something has gone wrong on the way.
tableviewcontroller passing the url to the webviewcontroller:
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath){
let webVC = WebViewController()
webVC.entryUrl = entriesArray[indexPath.row]["link"]
self.navigationController?.pushViewController(webVC, animated: true)
print(entriesArray[indexPath.row]["link"])
}
webviewcontroller receiving the data and trying to use it:
var entryUrl:String!
override func viewDidLoad() {
super.viewDidLoad()
print(entryUrl)
if entryUrl != nil {
print(entryUrl)
let requesturl: NSURL? = NSURL(string: entryUrl)
let request = NSURLRequest(URL: requesturl!)
web.loadRequest(request)
}
loadAddressURL()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
func loadAddressURL() {
if let requestURL = NSURL(string: entryUrl) {
let request = NSURLRequest(URL: requestURL)
web?.loadRequest(request)
}
}
I get an error on the line:
loadAddressURL()
saying:
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) on dispatch_semaphore_dispose
From the tableviewcontroller the log prints:
Optional("http://www.google.com/")
But from the webviewcontroller the log just prints:
http://www.google.com/
and after that:
fatal error: unexpectedly found nil while unwrapping an Optional value
Why won't this work and why is the the url not an optional anymore in the webviewcontroller?
Change the line of code to declare object of view Controller as below.
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath){
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let webVC = mainStoryboard.instantiateViewControllerWithIdentifier("WebViewController") as! WebViewController
webVC.entryUrl = entriesArray[indexPath.row]["link"]
self.navigationController?.pushViewController(webVC, animated: true)
print(entriesArray[indexPath.row]["link"])
}
Dont forgot to add indetifier of viewController in storyBoard.

Resources