webViewDelegate and function - ios

I'm writing a small webBrowser in swift and when someone clicks a link inside a webView, this needs to be detected and the link is open in another view. I wanted to implement this by putting the code for the webView function inside a separate file, so that I will be able to use the function multiple times without copy pasting it. Thing is that the function does not get's called when someone clicks a link. Should I add something to the ViewController class or to the class for the webView function?
import Foundation
import UIKit
class myWebViewController: UIWebView {
let VC1 = ViewController(nibName: nil, bundle: nil)
let VC2 = secondViewController(nibName: nil, bundle: nil)
func webView(webView: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
if(navigationType == UIWebViewNavigationType.LinkClicked){
var url = (request.URL)
currentURL=url
webView.stopLoading()
if(webView.accessibilityIdentifier == "second"){
VC2.performSegueWithIdentifier("first", sender: VC2)
}
else {
VC1.performSegueWithIdentifier("second", sender: VC1)
}
// self.delegate?.didReceiveAPIResults(jsonResult)
//
// with
//
// dispatch_sync(dispatch_get_main_queue())
// {
// self.delegate?.didReceiveAPIResults(jsonResult)
// }
}
return true
}
}

Here is a version using WKWebView and swift. It opens the clicked link in safari while the navigation works normally if you use the web address.
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var window: UIWindow = {
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.backgroundColor = UIColor.whiteColor()
window.rootViewController = UINavigationController(rootViewController: ViewController())
return window
}()
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
window.makeKeyAndVisible()
return true
}
}
class ViewController: UIViewController, WKNavigationDelegate, UITextFieldDelegate {
lazy var webView:WKWebView = {
let webView = WKWebView(frame: CGRectZero)
webView.navigationDelegate = self
return webView
}()
let textField: UITextField = {
let textField = UITextField(frame: CGRectMake(0, 6, 100, 30))
textField.clearButtonMode = .WhileEditing
textField.placeholder = "Enter your url here:"
return textField
}()
override func loadView() {
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
navigationItem.titleView = textField
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
if textField.isFirstResponder(){
textField.resignFirstResponder()
loadCurrentUrl()
}
return true
}
func loadCurrentUrl(){
let url = NSURL(string: textField.text)
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
}
func webView(webView: WKWebView!, decidePolicyForNavigationAction navigationAction: WKNavigationAction!, decisionHandler: ((WKNavigationActionPolicy) -> Void)!) {
if navigationAction.navigationType == .LinkActivated{
UIApplication.sharedApplication().openURL(navigationAction.request.URL)
decisionHandler(.Cancel)
}else{
decisionHandler(.Allow)
}
}
}

First of all, you should set the delegate for the UIWebView.
Then, implement this method:
webView:shouldStartLoadWithRequest:navigationType
See the description of this method (Objective-C)
So, maybe you forgot to set the delegate. Check this ;)

Related

When load WKWebview, its nil

I use Xcode 11.0 and Swift 5.1.
and I use WKWebview for load website, and There's no problem when I first loaded it.
but the second time to load a problem.
filename: TabBarController.swift
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
var isSelected = true
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
let tabBarIndex = tabBar.items?.firstIndex(of: item)
if isSelected == true && tabBarIndex == 0{
let vc = ViewController()
print("Go to amazon page")
vc.handleReload()
}
else{
if tabBarIndex == 0{
isSelected = true
print("home!")
}
else{
isSelected = false
print("setting!")
}
}
}
}
filename: ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url: URL = URL(string: "https://google.com")!
self.webView.load(URLRequest(url: url))
}
}
//MARK: - function
extension ViewController: WKUIDelegate, WKNavigationDelegate{
func handleReload() {
let url: URL = URL(string: "https://www.amazon.com")!
self.webView.load(URLRequest(url: url))
}
func cacheReset() {
let DataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])
let date = NSDate(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(ofTypes: DataTypes as! Set<String>, modifiedSince: date as Date){
print("Cache Reset End")
}
}
}
The error occurs at self.webView.load(URLRequest(url: url)) in func handleReload() and Here's the error log:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
I don't understand why WKWebview become nil :( Did I do something wrong?
You don't need to create ViewController again as you have already added as a child to TabBarController. You can grab that instance as below and call the require method on that same instance instead of creating a new.
if isSelected == true && tabBarIndex == 0 {
if let viewController = self.children.first(where: { $0 is ViewController }) as? ViewController {
viewController.handleReload()
}
}

OAuthSwift WKWebView opens and closes in a constant loop

I am trying to authenticate an iOS app with the Spotify API using OAuth2.
For this I am using OAuthSwift.
When my application loads, I am redirected to Spotify, I can log in and allow my app access to my account.
When I am redirected back to my app however, the WebView is dismissed, however immediately re opens on the previous page, dismissed itself and re opens.
This continues in a loop indefinitely.
I wondered if this is something todo with having my initAuthFlow function called in viewDidAppear, however moving this to viewDidLoad complains about
Warning: Attempt to present <OAuthKeyChainApp.WKWebViewController: 0x7fb42b505160> on <OAuthKeyChainApp.HomeController: 0x7fb42b50cf30> whose view is not in the window hierarchy!
and the controller is never presented.
HomeController.swift
class HomeController: OAuthViewController {
let oauthSwift = OAuth2Swift(
consumerKey: "xxxxxx",
consumerSecret: "xxxxxx",
authorizeUrl: "https://accounts.spotify.com/en/authorize",
accessTokenUrl: "https://accounts.spotify.com/api/token",
responseType: "code"
)
lazy var internalWebViewController: WKWebViewController = {
let controller = WKWebViewController()
controller.view = UIView(frame: UIScreen.main.bounds)
controller.loadView()
controller.viewDidLoad()
return controller
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .purple
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
initAuthFlow()
}
fileprivate func initAuthFlow() -> Void {
oauthSwift.authorizeURLHandler = internalWebViewController
guard let callbackURL = URL(string: "oauthkeychainapp://oauthkeychain-callback") else { return }
oauthSwift.authorize(
withCallbackURL: callbackURL,
scope: "user-library-modify",
state: generateState(withLength: 20),
success: { (credential, response, params) in
print(credential)
}) { (error) in
print(error.localizedDescription)
}
}
}
extension HomeController: OAuthWebViewControllerDelegate {
func oauthWebViewControllerDidPresent() { }
func oauthWebViewControllerDidDismiss() { }
func oauthWebViewControllerWillAppear() { }
func oauthWebViewControllerDidAppear() { }
func oauthWebViewControllerWillDisappear() { }
func oauthWebViewControllerDidDisappear() { oauthSwift.cancel() }
}
WKWebViewController.swift
import UIKit
import WebKit
import OAuthSwift
class WKWebViewController: OAuthWebViewController {
var webView: WKWebView!
var targetURL: URL?
override func viewDidLoad() {
super.viewDidLoad()
}
override func handle(_ url: URL) {
targetURL = url
super.handle(url)
loadAddressURL()
}
func loadAddressURL() {
guard let url = targetURL else { return }
let req = URLRequest(url: url)
self.webView?.load(req)
}
}
extension WKWebViewController: WKUIDelegate, WKNavigationDelegate {
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.allowsBackForwardNavigationGestures = true
webView.uiDelegate = self
webView.navigationDelegate = self
view = webView
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("loaded")
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// Check for OAuth Callback
if let url = navigationAction.request.url, url.scheme == "oauthkeychainapp" {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
self.dismiss(animated: true, completion: nil)
decisionHandler(.cancel)
return
}
// Restrict URL's a user can access
if let host = navigationAction.request.url?.host {
if host.contains("spotify") {
decisionHandler(.allow)
return
} else {
// open link outside of our app
UIApplication.shared.open(navigationAction.request.url!)
decisionHandler(.cancel)
return
}
}
decisionHandler(.cancel)
}
}
You aren't doing anything to change the state of your application. Because of this initAuthFlow is being called again, Spotify I assume has a valid session for you, so the controller is dismissed and the cycle repeats.
In the success closure of your oauthSwift.authorize call you should put the tokens into the KeyChain or somewhere secure and ensure initAuthFlow is only called when that state is invalid.

How to preload a UIWebView in another ViewController when launching the application in swift 3?

I am actually using two WebViews in my iOS application. I want to preload the second WebView in my SecondViewController when launching the application. I've tried to preload the WebView using NSNotification Center but it does not work. How can I preload the WebView in the SecondViewController?
I solved a similar problem in the following way:
Preload a view controller which contains a webview and store a reference in Appdelegate.
Call self.preloadvc?.view.layoutSubviews() in AppDelegate
At any time needed you can present it.
It seems necessary to create the web view programmatically in the view controller
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//
var preloadvc : PreloadVC? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Preloading
DispatchQueue.main.async(execute: {
self.preloadvc = PreloadVC()
//Thats the trick for preloading the view
self.preloadvc?.view.layoutSubviews()
})
return true
}
}
class PreloadVC : UIViewController, UIWebViewDelegate {
var mypreloadedView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
mypreloadedView = UIWebView(frame: view.frame)
view.addSubview(mypreloadedView)
mypreloadedView.delegate = self
if let myurl = URL(string: "https://www.google.de") {
let request = URLRequest(url: myurl)
self.mypreloadedView.loadRequest(request)
}
}
func webViewDidFinishLoad(_ webView: UIWebView)
{
print("Loaded")
}
}
Presentation anywhere :
#IBAction func ShowPreloaded(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let preloadedvc = appDelegate.preloadvc {
self.present(preloadedvc, animated: true, completion: nil)
}
}

WKWebView evaluateJavaScript not returning html

I am trying to parse the html returned from a WKWebView load() with evaluateJavaScript but it never prints anything. Am I doing this right? Any other ways? didFinish does print.
import UIKit
import WebKit
class MyWebViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: self.view.frame)
webView.navigationDelegate = self
let url = NSURL (string: "https://google.com");
let request = NSURLRequest(url: url! as URL)
webView.load(request as URLRequest)
self.view.addSubview(webView)
self.view.sendSubview(toBack: webView)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { (html: AnyObject?, error: NSError?) in
print(html!)
} as? (Any?, Error?) -> Void)
print("didFinish")
}
}
Using evaluateJavaScript with a WKWebView is a bit tricky.
Since I think this answer would be useful to many people, rather than address your specific question with a short code snippet and a comment that you need to implement WKScriptMessageHandler, I'm going to post a full, complete example that you can use to see how everything works together.
To use this, create a "Single View Application" iOS project in Xcode and paste this over the default ViewController.swift file.
//
// WebViewController.swift
// WKWebViewExample
//
// Created by par on 4/2/17.
// Copyright © 2017 par. All rights reserved. MIT License.
//
import UIKit
import WebKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let webViewController = WebViewController()
// install the WebViewController as a child view controller
addChildViewController(webViewController)
let webViewControllerView = webViewController.view!
view.addSubview(webViewControllerView)
webViewControllerView.translatesAutoresizingMaskIntoConstraints = false
webViewControllerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webViewControllerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webViewControllerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webViewControllerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
webViewController.didMove(toParentViewController: self)
}
}
class WebViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
private var webView: WKWebView!
private var webViewContentIsLoaded = false
init() {
super.init(nibName: nil, bundle: nil)
self.webView = {
let contentController = WKUserContentController()
contentController.add(self, name: "WebViewControllerMessageHandler")
let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.scrollView.bounces = false
webView.navigationDelegate = self
return webView
}()
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !webViewContentIsLoaded {
let url = URL(string: "https://stackoverflow.com")!
let request = URLRequest(url: url)
webView.load(request)
webViewContentIsLoaded = true
}
}
private func evaluateJavascript(_ javascript: String, sourceURL: String? = nil, completion: ((_ error: String?) -> Void)? = nil) {
var javascript = javascript
// Adding a sourceURL comment makes the javascript source visible when debugging the simulator via Safari in Mac OS
if let sourceURL = sourceURL {
javascript = "//# sourceURL=\(sourceURL).js\n" + javascript
}
webView.evaluateJavaScript(javascript) { _, error in
completion?(error?.localizedDescription)
}
}
// MARK: - WKNavigationDelegate
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// This must be valid javascript! Critically don't forget to terminate statements with either a newline or semicolon!
let javascript =
"var outerHTML = document.documentElement.outerHTML.toString()\n" +
"var message = {\"type\": \"outerHTML\", \"outerHTML\": outerHTML }\n" +
"window.webkit.messageHandlers.WebViewControllerMessageHandler.postMessage(message)\n"
evaluateJavascript(javascript, sourceURL: "getOuterHMTL")
}
// MARK: - WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any] else {
print("could not convert message body to dictionary: \(message.body)")
return
}
guard let type = body["type"] as? String else {
print("could not convert body[\"type\"] to string: \(body)")
return
}
switch type {
case "outerHTML":
guard let outerHTML = body["outerHTML"] as? String else {
print("could not convert body[\"outerHTML\"] to string: \(body)")
return
}
print("outerHTML is \(outerHTML)")
default:
print("unknown message type \(type)")
return
}
}
}
I know it has been a while since this was answered and I found that this didn't really work for me. I used the below instead.
I could not get the evaluateJavaScript to work until i added the setTimeout() function. For what it is worth here is my code below.
webView.evaluateJavaScript("setTimeout(function(){
// Do something here
},10);") { (result1, error) in
if error == nil {
print(result1 ?? "")
}
}

Webview and navigation controller

I have a webview in my swift app with a bunch of links on the website it is connected to. When a link is pressed, how do I make it so instead of opening the link in the same view, make it open in a new view with a back button (navigation controller). My viewcontroller.swift code is below. Any help?
import UIKit
import Parse
import PKHUD
class Home: UIViewController {
#IBOutlet var homeweb: UIWebView!
#IBOutlet var HomeActivity: UIActivityIndicatorView!
var request: NSURLRequest? = NSURLRequest(URL: NSURL(string: "https://google.com")!)
override func viewDidLoad() {
super.viewDidLoad()
if navigationController?.viewControllers.count > 1 { navigationItem.leftBarButtonItem = nil }
PKHUD.sharedHUD.contentView = PKHUDSuccessView()
PKHUD.sharedHUD.show()
PKHUD.sharedHUD.dimsBackground.boolValue
PKHUD.sharedHUD.userInteractionOnUnderlyingViewsEnabled.boolValue
PKHUD.sharedHUD.hide(afterDelay: 3.0);
if let request = request {
homeweb.loadRequest(request)
}
}
}
extension Home: UIWebViewDelegate {
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request != self.request && navigationType != .Other {
if let Home = storyboard?.instantiateViewControllerWithIdentifier("homeview") as? Home {
Home.request = request
navigationController?.pushViewController(Home, animated: true)
return false
}
}
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func webViewDidStartLoad(homeweb: UIWebView)
{
HomeActivity.startAnimating()
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
func webViewDidFinishLoad(homeweb: UIWebView)
{
HomeActivity.stopAnimating()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
title = homeweb.stringByEvaluatingJavaScriptFromString("document.title")
}
#IBAction func about(sender: UIBarButtonItem) {
let alertController = UIAlertController(title: "About Ball00n", message: "Copyright ©2014-2015 GreenBalloon, LLC James Cozzi (jc0z) jamescozzi00#gmail.com balloonhack.webs.com", preferredStyle: .Alert)
let Action1 = UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil)
alertController.addAction(Action1)
self.presentViewController(alertController, animated: true, completion: nil)
}
}
set the viewcontroller to be the webview's delegate and implement the following method:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
// check for your specific condition
if request.URL?.absoluteString == "some link" {
// push viewcontroller here or perform a segue
return false // to not follow the link in the webview
}
return true
}
now - after understanding what you really try to achieve :) - i did the following:
setup a viewcontroller in storyboard with just a webview all over the screen. ctrl drag from the webview to the webviewcontroller and set it to be its delegate. set the viewcontroller to a custom class "WebViewController". also set the storyboard ID to "WebViewController". drag from the webview to the WebViewController class and connect an IBOutlet called "webView". finally your WebViewController class should look like this:
import UIKit
class WebViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
var request: NSURLRequest? = NSURLRequest(URL: NSURL(string: "http://YOUR_INITIAL_WEBSITE")!)
override func viewDidLoad() {
super.viewDidLoad()
if navigationController?.viewControllers.count > 1 {
navigationItem.leftBarButtonItem = nil
}
if let request = request {
webView.loadRequest(request)
}
}
}
extension WebViewController: UIWebViewDelegate {
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request != self.request && navigationType != .Other {
if let webViewController = storyboard?.instantiateViewControllerWithIdentifier("WebViewController") as? WebViewController {
webViewController.request = request
navigationController?.pushViewController(webViewController, animated: true)
return false
}
}
return true
}
func webViewDidFinishLoad(webView: UIWebView) {
title = webView.stringByEvaluatingJavaScriptFromString("document.title")
}
}
at least this is a start :)

Resources