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?
2
Answers
When you write this line
the web view will not load you have to push the view to the navigationController like this
after that you can call ex.pop(str: "str") but you have to gave it a little time until the view loads before calling this function so what you can do is at the end of the function viewDidLoad in the ExternalDisplayViewController add this
and instead of calling ex.pop(str: "Hello!") write this code instead
But make sure your viewController has a navigationController when you call self.navigationController
I’m not completely sure of what you meant by external screen so I am assuming that you want to somehow init an
UIViewController
with aWKWebView
, loadsexternal.html
from 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
ExternalDisplayViewController
as Swift’s ARC might deallocate it. You should do something like this:(2)
UIViewController
lifecycle functions likeviewDidLoad
andviewWillLayoutSubviews
won’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
ExternalDisplayViewController
in your codes now is theinit
function, so you will have to move theWKWebView
initialisation codes into it.(3) Depending on where and when you call your
pop
function, 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
‘sdidFinish
delegate function which will let you know when your webview has finish loading the html.(4) You can also consider moving your
WKWebView
codes 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
ExternalDisplayViewController
instance presented in your external display is initialised fromSceneDelegate
. The instance initialised inViewController
is 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
NotificationCenter
as mentioned by the other answer, but do remember to remove the observer when you dismiss yourExternalDisplayViewController
else it may become a zombie object.Another way which is slightly more straightforward is to reference the
ExternalDisplayViewController
instance 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.