In an Xcode 26.4 + iOS 26.x environment, I called evaluateJavaScript inside webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) and got the following error:
Printing description of error:
▿ Optional<Error>
- some : Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=TypeError: undefined is not a function, WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A JavaScript exception occurred})
However, this worked fine in earlier versions before Xcode 26.4 (> 26.0 and < 26.4, though I do not remember the exact version), still with iOS 26.x.
It also works fine in an Xcode 26.4 + iOS 18.x environment.
And if I add a slight delay using DispatchQueue.main.asyncAfter in the Xcode 26.4 + iOS 26.x environment, then it works without any issue.
So what exactly is going on here? I would really appreciate an explanation.
Thanks for the precise reproducer — isolating the failure to the Xcode 26.4 SDK + iOS 26.x runtime combination, and confirming that a delay makes it work, narrows the cause.
The pattern you're describing — evaluateJavaScript from webView(_:didCommit:) failing with "TypeError: undefined is not a function," and working when delayed — points to a delegate-timing issue. didCommit is the wrong lifecycle stage for evaluating JavaScript that depends on the page's own scripts being defined.
The ordering from WKNavigationDelegate (https://developer.apple.com/documentation/webkit/wknavigationdelegate):
didStartProvisionalNavigation— request starteddidReceiveServerRedirect…— any redirectsdecidePolicyFor navigationResponse— response headers receiveddidCommit— response committed; document creation startingdidFinish— page fully loaded; page scripts have executed
At didCommit time, the response has been committed and the document is being constructed, but the <script> elements in the page haven't necessarily run yet. If your evaluateJavaScript call is invoking a function defined by one of the page's own scripts, that function may not exist in the JS context at the moment you call it — which is what undefined is not a function is reporting.
webView(_:didFinish:) is the documented "page has finished loading" callback, including initial script execution. That's the natural place to call evaluateJavaScript against page-defined symbols. The DispatchQueue.main.asyncAfter workaround approximates the same thing on a fixed timer rather than an event signal. It works because by the time the delay elapses, the page's scripts have usually finished running. The timer length is a guess, though; an event-driven signal is more reliable.
Moving the evaluateJavaScript call to didFinish should resolve it deterministically rather than depending on the delay being long enough on a given device or network.
If the JS you're evaluating only depends on standard browser globals (no page-defined functions), please share the string — there'd be a different cause to investigate.