The code below is able to get the geolocation and prints it out. I got this based on some tutorials online and looking at Swift Documents. I want to pass geolocations in the form of Strings from Swift 2 to Javascript. I am able to get the GeoLocations, I do not know how to pass these Strings to my Javascript Code in the Webview.
Below is the Code I have:
#IBOutlet weak var Webview: UIWebView!
let locMgr = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
loadAddressURL()
locMgr.desiredAccuracy = kCLLocationAccuracyBest
locMgr.requestWhenInUseAuthorization()
locMgr.startUpdatingLocation()
locMgr.delegate = self //necessary
}
func locationManager(manager: CLLocationManager , didUpdateLocations locations: [CLLocation]){
let myCurrentLoc = locations[locations.count-1]
var myCurrentLocLat:String = "\(myCurrentLoc.coordinate.latitude)"
var myCurrentLocLon:String = "\(myCurrentLoc.coordinate.longitude)"
print(myCurrentLocLat)
print(myCurrentLocLon)
//pass to javascript here by calling setIOSNativeAppLocation
}
I have javascript on my website with this method:
function setIOSNativeAppLocation(lat , lon){
nativeAppLat = lat;
nativeAppLon = lon;
alert(nativeAppLat);
alert(nativeAppLon);
}
I have looked at another question labelled Pass variable from Swift to Javascript with the following solution:
func sendSomething(stringToSend : String) {
appController?.evaluateInJavaScriptContext({ (context) -> Void in
//Get a reference to the "myJSFunction" method that you've implemented in JavaScript
let myJSFunction = evaluation.objectForKeyedSubscript("myJSFunction")
//Call your JavaScript method with an array of arguments
myJSFunction.callWithArguments([stringToSend])
}, completion: { (evaluated) -> Void in
print("we have completed: \(evaluated)")
})
}
However I have no appDelegate, and I want to directly from this view make these changes. So I get a "use of unresolved identifier appDelegate".
You have to implement UIWebview delegate.Javascript function should call after webviewDidFinishLoad.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = NSURL (string: URLSTRING);
let requestObj = NSURLRequest(URL: url!);
webView.delegate = self
webView.loadRequest(requestObj);
}
func webViewDidFinishLoad(webView: UIWebView) {
let javaScriptStr = "setIOSNativeAppLocation(\(myCurrentLoc.coordinate.latitude), \(myCurrentLoc.coordinate.longitude))"
webView.stringByEvaluatingJavaScriptFromString(javaScriptStr)
}
The answer you link to is for Apple TV, which is very different to iOS & UIWebView.
Before you try to run any javascript you need to be sure that the web view has finished loading (see webViewDidFinishLoad).
When the web view is ready and you have your details you can create a String which contains the javascript you want to execute and then pass that to the web view:
let javaScript = "setIOSNativeAppLocation(\(myCurrentLoc.coordinate.latitude), \(myCurrentLoc.coordinate.longitude))"
webView.stringByEvaluatingJavaScriptFromString(javaScript)
Related
I am working with a Unity game developer, and some part of his game requires us to pass the session data to the game in order to get the right user in the game. Unfortunately, every time I tried to pass the data using the docs he provided, I keep encounter the error "Failed to call function LoginSuccessful of class NetworkManager, Calling function LoginSuccessful with 1 parameter but the function requires 3.".
He gave me the function parameters and the data type I should follow, but somehow it still returns the same error. I would need the community guidance on this matter.
Unity Game Function
public void FunctionA ( int A, int B, string token ) {
}
My JS Script
var a = 1;
var b = 1;
var c = '8d4a7d1a-69cc-40f9-a74e-0fa7b388fbc6';
var script = document.createElement("script");
script.src = loaderUrl;
var MyGameInstance = null;
script.onload = () => {
createUnityInstance(canvas, config, (progress) => {
progressBarFull.style.width = 100 * progress + "%";
}).then((unityInstance) => {
MyGameInstance = unityInstance;
loadingBar.style.display = "none";
MyGameInstance.SendMessage('NetworkManager', 'LoginSuccessful', a, b, c);
fullscreenButton.onclick = () => {
unityInstance.SetFullscreen(1);
};
}).catch((message) => {
alert(message);
});
};
Error message when the game loads:
Failed to call function LoginSuccessful of class NetworkManager
Calling function LoginSuccessful with 1 parameter but the function requires 3.
You cannot call that function via the MyGameInstance.SendMessage method.
From Unity docs:
Sometimes you need to send some data or notification to the Unity
script from the browser’s JavaScript. The recommended way of doing
it’s to call methods on GameObjects in your content. If you are
making the call from a JavaScript plugin, embedded in your project,
you can use the following code:
MyGameInstance.SendMessage(objectName, methodName, value);
Where objectName is the name of an object in your scene; methodName is
the name of a method in the script, currently attached to that object;
value can be a string, a number, or can be empty. For example:
MyGameInstance.SendMessage('MyGameObject', 'MyFunction');
MyGameInstance.SendMessage('MyGameObject', 'MyFunction', 5);
MyGameInstance.SendMessage('MyGameObject', 'MyFunction', 'MyString');
Reference: https://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html
To pass the data you want, FunctionA would have to be modified to only accept one parameter (for example a JSON string) that it would then parse to obtain the required parameters.
I am having some difficulty trying to execute a javascript function from native code. I want a script writer to be able to define a “recipe” in Javascript. It is created by calling a recipe-creating function I expose from native code. The recipe function takes a config dict which expects a recipe name and an action. The action should be a no-parameters, no-return-value function.
Back in native code, as I am processing the config dict, I cannot seem to get a reference to the action function defined. What I actually get back is a NSDictionary with no keys in it.
Thanks for any help.
Javascript
// topLevel is a valid object I expose. It has a module
// function that returns a new module
var module = topLevel.module("Demo - Recipe");
// module has a recipe method that takes a config dict
// and returns a new recipe
module.recipe({
name: "Recipe 1",
action: function() {
topLevel.debug("Hello from Recipe 1!");
}
});
Native code:
#objc public protocol ModuleScriptPluginExports: JSExport {
func recipe(unsafeParameters: AnyObject) -> ModuleScriptPlugin
}
...
public func recipe(unsafeParameters: AnyObject) -> ModuleScriptPlugin {
guard let parameters : [String:AnyObject] = unsafeParameters as? [String: AnyObject] else {
// error, parameters was not the config dict we expected...
return self;
}
guard let name = parameters["name"] as? String else {
// error, there was no name string in the config dict
return self;
}
guard let action = parameters["action"] as? () -> Void else {
// error, action in the config dict was not a () -> Void callback like I expected
// what was it...?
if let unknownAction = parameters["action"] {
// weird, its actually a NSDictionary with no keys!
print("recipe action type unknown. action:\(unknownAction) --> \(unknownAction.dynamicType) ");
}
...
}
Ok I sorted this out. Posting this in case anyone else comes across this.
The problem is in the Swift code where the JSValue is coerced to a dictionary:
parameters : [String:AnyObject] = unsafeParameters as? [String: AnyObject]
which uses toDictionary(), and doing so seems to throw away the function property.
Instead the JSValue should be kept in tact and then valueForProperty is use to get the function which is itself a JSValue.
let actionValue = parameters.valueForProperty("action");
actionValue.callWithArguments(nil);
I am getting an error when calling a JS function from Swift in a tvOS app that has me stumped.
I have set up functions to call Swift from JS that work fine, but when trying to call a JS function from Swift I get the below error in the Safari debugger:
TypeError: undefined is not an object
(anonymous function)
JSValueToObject
-[JSValue callWithArguments:]
_TFFC13tvOSShortGame11AppDelegate17callPlayVideoInJSFS0_FSST_U_FCSo9JSContextT_
_TTRXFo_oCSo9JSContext_dT__XFdCb_dS__dT__
-[IKAppContext _doEvaluate:]
-[IKAppContext _evaluate:]
__41-[IKAppContext evaluate:completionBlock:]_block_invoke
-[IKAppContext _sourcePerform]
IKRunLoopSourcePerformCallBack
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
__CFRunLoopDoSources0
__CFRunLoopRun
CFRunLoopRunSpecific
CFRunLoopRun
-[IKAppContext _jsThreadMain]
__NSThread__start__
_pthread_body
_pthread_body
thread_start
The relevant swift code is below:
//From JS to Swift
//call this method once after setting up your appController.
func createGetVimeoURL(){
//allows us to access the javascript context
appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//this is the block that will be called when javascript calls getVimeoURL(str)
let getVimeoURL : #convention(block) (String) -> Void = {
(videoName : String) -> Void in
//5. return the vimeo url to the JS code for the passed in videoName
self.getTempVimeoURL(videoName)
}
//this creates a function in the javascript context called "getVimeoURL".
//calling getVimeoURL(str) in javascript will call the block we created above.
evaluation.setObject(unsafeBitCast(getVimeoURL, AnyObject.self), forKeyedSubscript: "getVimeoURL")
}, completion: {(Bool) -> Void in
//evaluation block finished running
})
}
//From Swift to JS
//when callPlayVideoInJS() is called, playVideo(url) will be called in JavaScript.
func callPlayVideoInJS(url : String){
//allows us to access the javascript context
appController!.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//get a handle on the "playVideo" method that you've implemented in JavaScript
let playVideo = evaluation.objectForKeyedSubscript("playVideo")
//Call your JavaScript method with an array of arguments
playVideo.callWithArguments([url])
}, completion: {(Bool) -> Void in
//evaluation block finished running
})
}
//4. getTempVimeoURL from videoName
func getTempVimeoURL (videoName : String) -> String {
return "http://techslides.com/demos/sample-videos/small.mp4"
}
And the javascript function that should get called by swift (which I create manually):
playVideo: function (url) {
if(url) {
//2
var player = new Player();
var playlist = new Playlist();
var mediaItem = new MediaItem("video", url);
player.playlist = playlist;
player.playlist.push(mediaItem);
player.present();
}
}
The getVimeoURL javascript function is created when I call createGetVimeoURL in appController didFinishLaunchingWithOptions.
But I can't work out why I get the javascript error when I call callPlayVideoInJS, but it is probably something simple!
Any help appreciated.
It looks like you aren't accessing the appropriate JS method using objectForKeyedSubscript
Change
let playVideo = evaluation.objectForKeyedSubscript("vimeoVideoURL")
to
let playVideo = evaluation.objectForKeyedSubscript("playVideo")
[UPDATE]
You also need to make sure your playVideo method is accessible from the appropriate context and scope. Try putting your playVideo method in application.js:
var playVideo = function (url) {
...
There is an existing solution for CefGlue: Call .Net from javascript in CefSharp 1 - wpf
I want exactly this, but for CefGlue: I want to communicate with the App using JavaScript. So when I click a button in my HTML site, I want the application to handle this (for example: start a tcp server).
I tried to register an own CefV8Handler but without success, the Execute function on the handler is never called. Here is what I do right now
protected override void OnWebKitInitialized()
{
Console.WriteLine("Registering testy extension");
Xilium.CefGlue.CefRuntime.RegisterExtension("testy", "var testy;if (!testy)testy = {};(function() {testy.hello = function() {};})();", new V8Handler());
base.OnWebKitInitialized();
}
My V8Handler code looks as follows:
public class V8Handler : Xilium.CefGlue.CefV8Handler
{
protected override bool Execute(string name, CefV8Value obj, CefV8Value[] arguments, out CefV8Value returnValue, out string exception)
{
if (name == "testy")
Console.WriteLine("CALLED TESTY");
else
Console.WriteLine("CALLED SOMETHING WEIRED ({0})", name);
returnValue = CefV8Value.CreateNull();
exception = null;
return true;
}
}
I'm in multiprocess mode, no console window shows "CALLED TESTY" nor "CALLED SOMETHING WEIRED".
Found a solution for that. The trick is to create a CefV8Value (CreateFunction) and assign it to a V8Handler. Then assign this value to the global context. This is what it looks like:
internal class RenderProcessHandler : CefRenderProcessHandler
{
protected override void OnContextCreated(CefBrowser browser, CefFrame frame, CefV8Context context)
{
CefV8Value global = context.GetGlobal();
CefV8Value func = CefV8Value.CreateFunction("magic", new V8Handler());
global.SetValue("magic", func, CefV8PropertyAttribute.None);
base.OnContextCreated(browser, frame, context);
}
}
Another problem came up: it was called in the renderer process, but I required the callback in the browser process. In the CefV8Handlers execute function i did this:
var browser = CefV8Context.GetCurrentContext().GetBrowser();
browser.SendProcessMessage(CefProcessId.Browser, CefProcessMessage.Create("ipc-js." + name));
This way I can retrive the message in the OnProcessMessageReceived function in the CefClient implementation.
I'm using Google Apps Script and I'm trying to re-use code that calls render so that I don't have to re-type everything. I'm running into an issue that seems to relate to "this" getting transformed.
Code:
function render(displayMedium, template_name) {
var js = HtmlService.createTemplateFromFile('javascript.html');
var css = HtmlService.createTemplateFromFile('stylesheet.html');
var t = HtmlService.createTemplateFromFile(template_name);
js.config = config;
css.config = config;
t.config = config;
t.appProperties = appProperties;
js.appProperties = appProperties;
t.jsBlock = js.evaluate().getContent();
t.cssBlock = css.evaluate().getContent();
displayMedium(
HtmlService
.createHtmlOutput(t.evaluate())
.setSandboxMode(HtmlService.SandboxMode.NATIVE)
};
function renderSidebar(){
var displayMedium = DocumentApp.getUi().showSidebar;
var template_name = "app.html";
render(displayMedium, template_name);
};
And when I call renderSidebar() the error I get is as follows:
[ERROR: InternalError: Method "showSidebar" was invoked with [object Object] as "this" value that can not be converted to type Ui.
Any ideas how to fix this?
It should work if you bind the function to the UI object:
var displayMedium = DocumentApp.getUi().showSidebar.bind(DocumentApp.getUi());
When you grab that function reference, then unless you do something like the above the runtime will (or might; it clearly does here) invoke the function with some other this value (depends on the API).