(Note: I originally posted this in General but it was suggested iOS might be a more appropriate home)
I have an inherited app that I need to migrate from Xojo 2017 r2.1/iOS9.3/Xcode 7 to Xojo 2018 r4/iOS SDK 12.1/Xcode 10/. Code assembles and runs, but blows up on the first reference to an ExecuteJavascript emulation method (ExecuteJavascript) using UIKit
[code] EvaluateJavascript(extends web as iOSHTMLViewer, JS as Text) as Text
Declare Function stringByEvaluatingJavaScriptFromString Lib "UIKit" Selector "stringByEvaluatingJavaScriptFromString:" (id As Ptr, js As CFStringRef) As CFStringRef
Dim jsResponse As Text
jsResponse = stringByEvaluatingJavascriptFromString(web.Handle, JS)
Return jsResponse[/code]
Greg O’Lone responded:
Right. That method is from UIWebView and doesnt work with WKWebView. See the following link for more info:
ExecuteJavascriptXC in place of ExecuteJavascript?
Or EvaluateJavascript?
I have this currently:
// Get current browser title
Dim currentTitle As Text
// currentTitle = webViewer.EvaluateJavascript("getBrowserTitle();")
currentTitle = webViewer.ExecuteJavascriptXC("getBrowserTitle();")
// which should return "fred"; calls from iOSDesignExtensions
Function ExecuteJavascriptXC(Extends viewer As iOSHTMLViewer, script As Text, callback As ExecuteJavascriptDelegateXC = nil) As Text
Declare Sub execJavaScript Lib "WebKit.framework" selector "evaluateJavaScript:completionHandler:" (obj_id As ptr, script As CFStringRef, block As ptr)
// callback is Nil
If callback <> Nil Then
ExecuteJavascriptCallback = callback
Dim block As New iOSBlock(AddressOf ExecuteJavascript_Result)
execJavaScript(viewer.Handle, script, block.Handle)
Else
execJavaScript(viewer.Handle, script, Nil)
// which returns nothing, unlike the original EvaluateJavascript in 2017 r2.1
End If
// so the parent Sub receives no result and continues looping waiting for a result to be returned... which never occurs
So what I see is that the original (2017 r2.1) EvaluateJavascript was supposed to return a response, and (2018 r4) ExecuteJavascriptXC does not.
ExecuteJavascriptXC will return a value via the callback parameter that you pass when you call it. So create a method that accepts the same parameters as the delegate and that where your result will be.
Do you have a code example using Execute JavascriptXC to interact with a web viewer on iOS?
I tried
dim fred as ExecuteJavascriptDelegateXC
webViewer.ExecuteJavascriptXC("getBrowserTitle();", fred)
even an alert I planted in getBrowserTitle() in the JS no longer triggers.
No you don’t want to create a variable of type delegate. You want to create a method that matches the delegate parameters. Take a look at Delegates in the Xojo docs. Then have a look at the vHTMLViewer view in iOSDesignExtensions. It has a “Pi” button that uses JS to calculate pi and that demonstrates how to use a callback to receive the result.
I’d like to revive this thread - I have just installed 2019r2b81, as Apple updated my Xcode toolkit to 11.0. After a few tweaks, at least the code compiles. As Jason suggested above, I installed the iOSDesignExtensions, and am emulating some of the code of the vHTMLViewer view example.
My problem is I am unable to execute a simple function call to javascript to retrieve the document.title; whether I embed my code in a text string or pull up my library file, which reads successfully. Here’s what I’m doing: (I tried to simplify it as much as possible)
var getBrowserTitle as text = var newTitle=document.title; newTitle;
dim js as text = getBrowserTitle
Call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)
// (jn another method)
cmdResult(value As auto, error As RuntimeException)
name = value // name is a global text variable
name always comes back empty, and triggers a Runtime Exception of “A JavaScript exception occurred”
Aargh - typos above; first pass code too late to edit. the code s/b
Dim html As Text = "<html><body></body></html><script>function getBrowserTitle(){var newTitle = document.title}newTitle;</script></body></html>"
Declare Sub loadHTML Lib "WebKit.Framework" Selector "loadHTMLString:baseURL:" (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)
dim js as text = getBrowserTitle
Call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)
// (jn another method)
cmdResult(value As auto, error As RuntimeException)
name = value // name is a global text variable
Either way in Greg’s reply works for the javascript; if I define js as text = “getBrowserTitle()” I no longer get “A JavaScript exception occurred” and instead get “TypeMismatchException (Expected Text but got Ptr)” since value is returned as &h0000000… instead of text.
Clearly the document.title is not being read correctly.
Anyone see a way to get this property to read correctly?
Also, is there any property under Variables->self (ViewWeb.ViewWeb) that should reveal the document.title?
I followed your (Jeremie’s) suggestions. Still no result value.
The HTML view (webViewer) is activated via loadHTML; my html initialization text string displays a message to confirm this.
I have a timer-triggered method that performs the ExecuteJavascriptXC, and tests to see if cmdResult has been run
if it has, it should have a result. My value (title) comes back empty now.
This gets me a fresh window, displaying “hi!”
// load test html to window referenced by webViewer
Dim html As Text = "<html><body></body></html>hi!</body></html>"
Declare Sub loadHTML Lib "WebKit.Framework" Selector "loadHTMLString:baseURL:" (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)
This is the timed routine, firing every second until satisfied
// Get current browser title
dim currentTitle As text
dim js as text = "document.title"
// try to get the document.title property via js, once
// reqDocTitle is a switch initialized to false
if (not(reqDocTitle)) then
call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)
reqDocTitle = true
end if
// if cmdResult has not yet executed, drop out
// cmdResultDone indicated cmdResult has fired; initialized as false
if (cmdResultDone) then
break // I stop here to check the title
// Determine if currentTitle is "title" which means the page loaded
if (currentTitle = "title") then
// do stuff...
end if
end if
cmdResultDone = false
and here’s cmdResult:
If error <> nil Then
dim reason as text
reason = error.Reason
break
return
end if
dim res as text = value
cmdResultDone = true
[quote=455494:@Michael Gehl]This gets me a fresh window, displaying “hi!”
// load test html to window referenced by webViewer
Dim html As Text = “hi!”
Declare Sub loadHTML Lib “WebKit.Framework” Selector “loadHTMLString:baseURL:” (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)[/quote]
I don’t know if this is causing the error but your html is wrong.
You don’t need to declare loadHTML function as it is already included in iOSDesignExtensions
You should pause the debugger when you get nil and move up the function stack to see what error the iOS framework is returning.
Should have mentioned I had fixed the html; I’m a dyslexic typist
loadHTML in HTMLExtensionsXC uses UIKit - is that right? I’m redeclaring it locally with WebKit.
Next up the stack is ExecuteJavascript_Result with a result pointer and a zeroed error pointer, which tries to invokes the callback (cmdResult) with a blank value and error of Nil, and returns Nil.
dim html As text = "<html><body>hi!</body></html>"
// from vHTMLViewer example
declare sub loadHTML Lib "WebKit.Framework" Selector "loadHTMLString:baseURL:" (obj_id As Ptr, html As CFStringRef, url As Ptr)
loadHTML(webViewer.Handle, html, Nil)
this succeeds, as the “hi!” is displayed.
in another method, triggered by a timer, and checked to ensure the code above had been executed:
// Get current browser title
dim js as text = "document.title"
// try to get the document.title property via js, once
call webViewer.ExecuteJavascriptXC(js, weakAddressOf cmdResult)
ExecuteJavascriptXC runs ExecuteJavascript_Result, which executes cmdResult as a callback
// cmdResult(value as auto, error as RuntimeException)
if error <> Nil Then
dim reason As text
reason = error.Reason
break
return
end If
// break here, step ahead one, but value is blank
dim res as text = value