I want to execute javascript in an external screen's webview. In my main view I'm trying to call the pop() function in the External View like this:
let ex = ExternalDisplayViewController()
ex.pop(str: "Hello!")
When I run it I get "Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value" at self.webView.evaluateJavaScript("go('\(str)')", completionHandler: nil) in my External View:
import UIKit
import WebKit
class ExternalDisplayViewController: UIViewController, WKUIDelegate {
private var webView: WKWebView!
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let config = WKWebViewConfiguration()
config.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
config.setValue(true, forKey: "allowUniversalAccessFromFileURLs")
webView = WKWebView(frame: view.frame, configuration: config)
webView.scrollView.contentInsetAdjustmentBehavior = .never
webView.uiDelegate = self
// load local html file
let bundleURL = Bundle.main.resourceURL!.absoluteURL
let html = bundleURL.appendingPathComponent("external.html")
webView.loadFileURL(html, allowingReadAccessTo:bundleURL)
view.addSubview(webView)
}
func pop(str: String) {
self.webView.evaluateJavaScript("go('\(str)')", completionHandler: nil)
}
}
Thanks!
EDIT (2023-03-01):
Sorry, I'm new to Swift and working with multiple views. If I use viewDidLoad() or loadView() I'm getting the same result:
import UIKit
import WebKit
class ExternalDisplayViewController: UIViewController, WKUIDelegate {
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
config.setValue(true, forKey: "allowUniversalAccessFromFileURLs")
webView = WKWebView(frame: view.frame, configuration: config)
webView.scrollView.contentInsetAdjustmentBehavior = .never
webView.uiDelegate = self
// load local html file
let bundleURL = Bundle.main.resourceURL!.absoluteURL
let html = bundleURL.appendingPathComponent("external.html")
webView.loadFileURL(html, allowingReadAccessTo:bundleURL)
view.addSubview(webView)
}
func pop(str: String) {
if (self.isViewLoaded) {
// viewController is visible
print("view controller should be visible")
self.webView.evaluateJavaScript("go('\(str)')", completionHandler: nil)
} else {
print("view controller is not loaded")
/*_ = self.view
self.webView.evaluateJavaScript("go('\(str)')", completionHandler: nil)*/
}
}
}
This produces the following output:
View Loaded?
view controller is not loaded
How can I initialize this view so it's accessible?
I'm not completely sure of what you meant by external screen so I am assuming that you want to somehow init an
UIViewControllerwith aWKWebView, loadsexternal.htmlfrom your bundle and executes a javascript function without presenting the view controller. Please correct me if I'm wrong.(1) You should keep a strong reference to your
ExternalDisplayViewControlleras Swift's ARC might deallocate it. You should do something like this:(2)
UIViewControllerlifecycle functions likeviewDidLoadandviewWillLayoutSubviewswon't be called until the its view is loaded into the view hierarchy. You can understand more about the lifecycle from here.The only function that will be called in
ExternalDisplayViewControllerin your codes now is theinitfunction, so you will have to move theWKWebViewinitialisation codes into it.(3) Depending on where and when you call your
popfunction, you might not be able to call the javascript function you desire from the webview because there will be a race condition between the webview loading of your html and calling of the javascript function. You can prevent this by usingWKNavigationDelegate'sdidFinishdelegate function which will let you know when your webview has finish loading the html.(4) You can also consider moving your
WKWebViewcodes into your main view controller since you are not planning to present yourExternalDisplayViewController(assuming my assumption is correct).UPDATE 2023-03-07
The reason why it wasn't working is because you are actually initialising two different
ExternalDisplayViewController.SceneDelegate
ViewController
This will not work because the
ExternalDisplayViewControllerinstance presented in your external display is initialised fromSceneDelegate. The instance initialised inViewControlleris not presented and basically does nothing and will be dealloc by ARC.There are a few ways to resolve this. One of it is to use
NotificationCenteras mentioned by the other answer, but do remember to remove the observer when you dismiss yourExternalDisplayViewControllerelse it may become a zombie object.Another way which is slightly more straightforward is to reference the
ExternalDisplayViewControllerinstance that has been presented in your external display directly from yourViewController."External Display Configuration" is the name you provided for your scene configuration in
AppDelegate.Hope this helps.