I am writing a Swift Hybrid application and I need to be able to know when my WKWebView has finished loading a request. I am attempting to use WKNavigationDelegate to achieve this. However, the only way I can get the events to fire is if I set it this way:
webView.navigationDelegate = self
The problem is I have some data that I want associated with my request, so I created a custom class that implements WKNavigationDelegate that looks like this:
class MyNavigationDelegate : NSObject, WKNavigationDelegate {
init(...) {
//set local variables with passed args
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
println("finished loading")
}
}
then I assign it like this:
webView.navigationDelegate = MyNavigationDelegate(my, arguments, here)
webView.loadRequest(...)
The page is loading so there is no problem with how I'm loading it. What am I missing?
The delegate is a weak property, so make sure that you're holding on to the delegate class instance elsewhere -- it's likely getting deallocated before it even gets used. Meanwhile, self is not getting deallocated, so it is working.
Related
I am having difficulty getting a WKWebview to call its delegate methods, namely the did finish one.
I am attempting something similar to this post generating a pdf report using html loaded into a WKWebView;
WKWebView not calling navigation delegate methods
However trying the suggestion there, that doesn't work for me. I have a WKWebView made in a separate class to the View controller that will be calling its methods.
If I strip things right down and just try to simply load a website in the WebView to see if the delegate functions get called I get nothing.
Here is my test class;
import UIKit
import WebKit
class TestWebview: NSObject {
let webView = WKWebView ()
func loadWebsite () {
print ("LOAD TEST CALLED")
webView.navigationDelegate = self
let link = URL(string: "https://www.apple.com")!
let request = URLRequest(url: link)
webView.load(request)
}
}
extension TestWebview: WKNavigationDelegate {
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print ("DID COMMIT")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print ("FINISHED LOADING")
}
}
And here is how I am calling it in a separate view controller;
override func viewDidLoad() {
super.viewDidLoad()
let test = TestWebview ()
test.loadWebsite()
}
Although if I put a WKWebView as an outlet into a view controller using Storyboards then the delegate methods do get called. I am trying to avoid this however as I don't want to see a web view in the view controller and want to keep all the code for that separate in another class.
Am I missing something? Checking various other posts on Stack Overflow about this hasn't given me any solutions other than making sure the navigation delegate is being set, which I am. Any help would be greatly appreciated.
You need to move let test = TestWebview () to class level, otherwise this object is going to be evicted when viewDidLoad completes: the navigationDelegate is defined as weak, so it's not going to prevent it either.
I have had a couple of instances of this recently, so I thought I'd reach out to better understand if I am doing something wrong or iOS/Xcode is just being weird.
I have a utility class, which just inherits from NSObject. My utility class holds reference to a WKWebView, and is the webview's navigationDelegate.
For the life of me, I cannot get the utility class to respond to the navigationDelegate methods, UNLESS I breakpoint at the point of setting the delegate, print out something (anything, from my testing) to the console (yep, this is important. Just breakpointing isn't enough - I have to PRINT something). Then re-run, and it responds as normal.
I am very familiar with iOS development and delegation patterns, but this is behaviour I've seen before and never understood, so I wanted to better my understanding if it's something I am getting wrong.
Here is some example code of what I am doing. It's worth noting, that if I take the utility out of the equation and just use UIViewControllers as the navigationDelegate, everything works fine.
class ExampleUtility: NSObject, WKNavigationDelegate {
private weak var containerViewController: UIViewController?
private let webView: WKWebView
init(containerViewController: UIViewController) {
self.containerViewController = containerViewController
webView = WKWebView(frame: containerViewController.view.frame)
super.init()
setupWebView()
}
private func setupWebView() {
webView.navigationDelegate = self
containerViewController?.view.addSubview(webView)
}
func start() {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
webview.load(request)
}
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// This is never called, unless I print ANYTHING to the console
}
}
The fact that it responds after breakpointing and printing makes me thing its a bug of some sort? Also worth nothing that this isn't only on debug runs. It happens when not running from Xcode also.
Any help is greatly appreciated.
Please just keep ExampleUtility object as property in containerViewController.
Then it will work.
The reason you have this issue is, the webview instance is created in the ExampleUtility but the instance of Example Utility was already released since it is not property of containerViewController.
Please try and it will work surely.
I been struggling to update my tableview through another class I made.
I then found this stackoverflow solution:
How to access and refresh a UITableView from another class in Swift
But when I follow it step by step and implement all the codes, I get the following errors:
My line:
weak var delegate: UpdateDelegate?
Gets the warning
'weak' may only be applied to class and class-bound protocol types, not 'UpdateDelegate'
And my line:
self.delegate.didUpdate(self)
Gets warning:
Instance member 'delegate' cannot be used on type 'APIgetter'
Could this be because the code is old and I'm using swift 4? else I cannot see why this should be failing. I hope you can help me :)
Update:
My Protocol:
protocol UpdateDelegate: AnyObject {
func didUpdate(sender: APIgetter)
}
Snippet from my ViewController containing the tableview:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UpdateDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
APIgetter.addDataFromSQL()
let updates = APIgetter()
updates.delegate = self
}
//update func
func didUpdate(sender: APIgetter) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
My APIgetter class in APIgetter.swift:
class APIgetter {
weak var delegate: UpdateDelegate?
class func addDataFromSQL (){
//Code to fetch data from API
//Code that comes after DispatchQueue.global & DispatchQueue.main and my result being executed
//result
self.delegate.didUpdate(self)
just update your protocol definition.
protocol UpdateDelegate: class {
// protocol body
}
or
protocol UpdateDelegate: AnyObject {
// protocol body
}
This is needed (as of Swift 4 I think) because classes are reference types and you can only use a weak reference on reference types. Not value types like structs.
UPDATE: You cannot access a property/instance member from a static function the way that you currently are. Remove the class keyword from the function and it should work.
If you want/need to use a single instance of this class throughout your application you can use a static property to make it a Singleton
class APIgetter {
static let shared: APIgetter = APIgetter()
}
Then you would be able to access it like this:
APIgetter.shared.addDataFromSQL()
You could also update the delegate in the same way before calling your function.
APIgetter.shared.delegate = self
I think in this case though I would use a Singleton without the delegate. Just use a completion handler in your function. Setting and changing the delegate on a shared instance could have some side effects if not managed carefully.
As some of you may have noticed, Apple recently implemented the WKWebView (from WebKit) as an object in the Interface Builder; however, I am having difficulty with properly implementing it. I am still able to implement it by code, but implementing it via the Interface Builder has proven to be a bit of a pain.
The WKWebView appears blank on launch, and although the NSTextField appears to work when configured, the WKWebView continues to remain blank and doesn't even call upon didStartProvisionalNavigation, didCommit or didFinish when implemented (whereas, when done programatically, this continues to work).
Interface Builder Screenshot
Interface Builder Menu Options
Note: I did attempt to implement it using the WebConfiguration as well, but no luck either. Now I'm just trying to keep the code as simplistic as possible to get a better understanding as to why this is not working.
ViewController.swift
import Cocoa
import WebKit
class WindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
// Configure Window Appearance
window!.titlebarAppearsTransparent = true
window!.isMovableByWindowBackground = true
window!.title = ""
window!.backgroundColor = NSColor.white
}
}
class ViewController: NSViewController, WKUIDelegate, WKNavigationDelegate {
let myURL = "https://google.com"
#IBOutlet var webView: WKWebView!
#IBOutlet var pageTitle: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Load Homepage URL in WebView
webView.load(URLRequest(url: URL(string: myURL)!))
}
}
So as it turns out, the WKWebView refuses to become active while there is an entitlements file in your project. In my case, I had the App Sandbox enabled with the following settings:
com.apple.security.network.server – YES
com.apple.security.files.user-selected.read-write – YES
Once deleting the entitlements file and removing it entirely from the project, the WKWebView began to work again.
I’m trying to get a WKWebView to scroll down automatically when its content changes. I added this function to my WKNavigationDelegate, which is a subclass of NSViewController:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("Finished loading web view.")
webView.scrollToEndOfDocument(self)
}
It crashes:
-[WKWebView scrollToEndOfDocument:]: unrecognized selector sent to instance 0x6080001a07e0
I guess self is not what I should be passing to scrollToEndOfDocument. I also tried to pass webView or nil instead, with the same result. What value should I pass?
N.B. I tried self because Apple’s docs say:
Declaration
func scrollToEndOfDocument(_ sender: Any?)
Parameters
sender
Typically the object that invoked this method.
You may have to do it via JavaScript:
let script = "window.scrollTo(0, document.body.scrollHeight)"
webView.evaluateJavaScript(script)
This is for old-school WebView; leaving for posterity:
Try calling scrollToEndOfDocument: on the WebView's main frame's document view:
webView.mainFrame.frameView.documentView.scrollToEndOfDocument(nil)
(What you pass in as sender shouldn't matter and has no bearing on the error you're seeing.)