I tried to call a Javascript function from swift using below code but can't access the Javascript function
This is how I create the Javascript context object:
lazy var context: JSContext? = {
let context = JSContext()
guard let
JSPath = Bundle.main.path(forResource: "IAV", ofType: "html")
else {
print("Unable to read resource files.")
return nil
}
do {
let iav = try String(contentsOfFile: JSPath, encoding: String.Encoding.utf8)
_ = context?.evaluateScript(iav)
} catch (let error) {
print("Error while processing script file: \(error)")
}
return context
}()
//Set value to js
func setToken(Token:String)
{
let jsmethod = context?.objectForKeyedSubscript("setIAVToken")
let passParameter = jsmethod?.call(withArguments: [Token])
}
The content of html file is
sample IAV form
</head>
<body > <header> </header> <main>
<h1>Registration Form</h1>
<form id="myform" method="post">
<div id="mainContainer"> <input type="button" id="start" value="Add Bank"> </div>
var value="dhjhsd";
var setIAVToken = function(token) {
value= token;
}
$('#start').click(function() {
var iavToken = value;
alert(iavToken)
dwolla.configure('uat');
dwolla.iav.start('iavContainer', iavToken, function(err, res) {
console.log('Error: ' + JSON.stringify(err) + ' -- Response: ' + JSON.stringify(res));
});
}); </script> </html>
You should probably use WKWebView (which is the new way to load web content in a webview in iOS since it uses WebKit and has lots of improvements like better memory allocation than it does WebView).
In a swift file WebviewBridging.swift in iOS you could have something like
import UIKit
import WebKit
class WebviewBridging: UIViewController, WKNavigationDelegate {
var webView: WKWebView?
override func viewDidLoad() {
super.viewDidLoad()
// Create a WKWebView instance
webView = WKWebView (frame: self.view.frame, configuration: webConfig)
view.addSubview(webView!)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let url = Bundle.main.url(forResource: "index", withExtension: "html")!
webView!.loadFileURL(url, allowingReadAccessTo: url)
}
func callJSFunctionFromSwift(){
webView!.evaluateJavaScript("changeBackgroundColor('red')", completionHandler: nil)
}
}
In an index.html file on your iOS project folder you could have something like:
<html>
<head>
</head>
<body>
<script>
function changeBackgroundColor(colorText) {
document.body.style.background = colorText;
}
</script>
</body>
</html>
Whenever you will call callJSFunctionFromSwift() in swift it will communicate with the WKWebView through XPC communication and evaluate your javascript code that will trigger the javascript function in index.html.
In case of objective c do the following:
#import <JavaScriptCore/JavaScriptCore.h> in header file.
Next use this code inside viewcontroller.
NSString *jsPath = [[NSBundle mainBundle] pathForResource:#"JSFileName" ofType:#"js"];
NSString *scriptString = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
JSContext *context = [[JSContext alloc] init];
context = [[JSContext alloc] init];
[context evaluateScript:scriptString];
JSValue *function = context[#"setMessage"];
JSValue* result = [function callWithArguments:#[#"your custom string"]]; //pass the string whatever you want.
[result toString]; // This will give the value in string format.
Edit the html, use
<input type="button" id="start" value="Add Bank" onClick="setMessage()"> instead of <input type="button" id="start" value="Add Bank">
Also add <input type="hidden" id="token" value="Token value"> inside the form to pass the token value.
In the javascript method:
<script>
function setMessage() {
var iavToken = document.getElementById("token").value;
alert(iavToken)
dwolla.configure('uat');
dwolla.iav.start('iavContainer', iavToken, function(err, res) {
console.log('Error: ' + JSON.stringify(err) + ' -- Response: ' + JSON.stringify(res));
});
}
</script>
Next invoke the javascript function using the following objective c.
NSString * jsCallBack = [NSString stringWithFormat:#"setMessage()"];
[webView stringByEvaluatingJavaScriptFromString:jsCallBack];
P.S: You need to add <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> (or if you have the js file locally add it.) in the html to work with the js.
Related
I created an easy login form in html with a submit button that when I clicked it, a Javascript's function check if the login is correct or not.
All put all files into a qrc file in QT and I wrote this code for run the program:
login.pro
QT += webenginewidgets core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = login
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
RESOURCES += \
login.qrc
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtWebEngineWidgets>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void connectToJs(bool result, QWebEngineView *view);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtWebEngineWidgets>
#include <QUrl>
#include <QtWebView/QtWebView>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QFile apiFile(":/qtwebchannel/qwebchannel.js");
if(!apiFile.open(QIODevice::ReadOnly))
qDebug()<<"Couldn't load Qt's QWebChannel API!";
QString apiScript = QString::fromLatin1(apiFile.readAll());
apiFile.close();
view->page()->runJavaScript(apiScript);
view->load(QUrl(QStringLiteral("qrc:/new/prefix1/index.html")));
connect(view->page(), SIGNAL(loadFinished(bool)), this, SLOT(connectToJs(bool, view)));
setCentralWidget(view);
}
void MainWindow::connectToJs(bool result, QWebEngineView * view) {
qDebug() << "connectToJs!" << result;
if (result) {
QWebChannel *channel = new QWebChannel(view->page());
view->page()->setWebChannel(channel);
channel->registerObject(QString("nameObject"), this);
}
}
MainWindow::~MainWindow()
{
delete ui;
}
index.html
<html>
<head>
<title>Javascript Login Form Validation</title>
<script src="login.js"></script>
</head>
<body>
<div class="container">
<div class="main">
<h2>Javascript Login Form Validation</h2>
<form id="form_id" method="post" name="myform">
<label>User Name :</label>
<input type="text" name="username" id="username"/>
<label>Password :</label>
<input type="password" name="password" id="password"/>
<input type="button" value="Login" id="submit" onclick="validate()"/>
</form>
</div>
</div>
</body>
</html>
login.js
function validate(){
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
if ( username == "Jack" && password == "login"){
alert ("Login successfully");
}
else{
alert("Error: the login is incorrect!");
}
}
var workoutObject;
if (typeof qt != 'undefined') {
alert("Qt is OK!!");
new QWebChannel(qt.webChannelTransport, function (channel) {
workoutObject = channel.objects.nameObject;
});
}
else {
alert("Qt is not defined!");
}
That I want now is pass the username and the password from html to QT when I click the button.
How can I implement the QWebChannel?
In order to pass args from HTML/JS to QT via QWebEngine you will have to use the QWebChannel. There are some prerequisites for this task:
First you will have to create Qt object to interact with HTML/JS
H file
class QJavaScriptProxy : public QObject
{
Q_OBJECT
private:
QString m_oUserName;
QString m_oPassword;
public:
// Ctor
QJavaScriptProxy(QObject * poParent = Q_NULLPTR);
// Expose function to HTML/JS
Q_INVOKABLE void setUserNameAndPassword(const QString & oUserName, const QString & oPass);
...
};
Cpp file
QJavaScriptProxy::QJavaScriptProxy(QObject * poParent) : QObject(poParent)
{
// Set object name will be used as JS variable
setObjectName("m_oQtProxy");
}
QJavaScriptProxy::setUserNameAndPassword(const QString & oUserName, const QString & oPassword)
{
//store user name and password
m_oUserName = oUserName;
m_oPassword = oPassword;
}
Then register this object in your MainWindow
QWebChannel * poChannel = new QWebChannel(view->page());
view->page()->setWebChannel(poChannel);
// Create proxy object
m_poQtProxy = new QJavaScriptProxy(this);
// Register object
channel->registerObject(m_poQtProxy->objectName(), m_poQtProxy);
Add the qwebchannel.js file and link it to your HTML resources
index.html
<script src="qwebchannel.js" type="text/javascript"></script>
In the JavaScript side
login.js
new QWebChannel(qt.webChannelTransport, function(channel){
// Set global Qt proxy object
window.qProxy = channel.m_oQtProxy;
});
// Pass user name and password to QT
// Note: you can call this function on button click.
function toQT(name, pass){
qProxy.setUserNameAndPassword(name,pass);
}
I have a simple web app that prints streaming data to the console.log. This works just fine in a web page.
I would like to print the log messages directly onto the Xcode console as they come across the stream without using a web view.
Prefer everything to be in Swift if possible but Objective-C solution is ok.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Barchart Market Data API Example</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="../dist/barchart-marketdata-api-1.0.js"></script>
<script type="text/javascript">
$(function() {
var onMarketUpdate = function(message) {
if (message.type == "TRADE") {
var q = client.getMarketState().getQuote(message.symbol);
if (q)
console.log('Symbol: ' + q.symbol +
'\nLast:' + q.lastPrice +
'\nVolume:' + q.volume +
'\nTime:' + q.time);
}
};
var client = new Barchart.RealtimeData.Connection();
client.connect('qsws-us-e-01.aws.barchart.com', 'user', 'password');
client.on('marketUpdate', onMarketUpdate, 'ESZ6');
});
</script>
<body>
</body>
</html>
I have been able to load the script above (everything in the script text/javascript tag into a file called myLib.js) as well as the required libraries
let fileLocation1 = NSBundle.mainBundle().pathForResource("jquery", ofType: "js")!
let jsSource1 : String
do {
jsSource1 = try String(contentsOfFile: fileLocation1)
} catch {
jsSource1 = "Contents of JavaScript not loaded."
}
let fileLocation2 = NSBundle.mainBundle().pathForResource("barchart-marketdata-api", ofType: "js")!
let jsSource2 : String
do {
jsSource2 = try String(contentsOfFile: fileLocation2)
} catch {
jsSource2 = "Contents of JavaScript not loaded."
}
let fileLocation3 = NSBundle.mainBundle().pathForResource("myLib", ofType: "js")!
let jsSource3 : String
do {
jsSource3 = try String(contentsOfFile: fileLocation3)
} catch {
jsSource3 = "Contents of JavaScript not loaded."
}
let context = JSContext()
context.evaluateScript(jsSource1)
context.evaluateScript(jsSource2)
context.evaluateScript(jsSource3)
I am not sure how to get the output from the console.log into the context as it is inside a Jquery anonymous function.
It really does not have to come from the console.log, I could just return the string to the mac/iOS app.
I have to use UIWebView in my project and I seem that I got a problem.
Well I have html as below.
<html>
<a onclick="myFunction(1)">element1</a>
<a onclick="myFunction(2)">element1</a>
<a onclick="myFunction(3)">element1</a>
</html>
When I click to the a href link I have to execute my javascript code
<script>
var arr = [];
function myFunction(number) {
arr.push(number);
}
</script>
now how I can pass an Array to UIViewController?
and how I know in swift if I call myFunction() from UIWebView?
You can use WKWebView, available since OS X 10.10 or iOS 8 to do the job. In Xcode 9 and later, you can add the WkWebView directly to Interface Builder and connect the IBOutlet. For earlier versions of Xcode, you must do so programmatically. For full compatibility, the code below shows how to add the WKWebView programmatically.
Make your view controller conform to the WKScriptMessageHandler protocol and add the following code:
class ViewController: UIViewController, WKScriptMessageHandler {
weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// myAction is the pipe name Javascript uses to post messages
// to your app. You can define more than 1 pipe.
let controller = WKUserContentController()
controller.addScriptMessageHandler(self, name: "myAction")
let config = WKWebViewConfiguration()
config.userContentController = controller
// Add the WKWebView to the view
let frame = CGRectMake(20, 20, 200, 200)
let webView = WKWebView(frame: frame, configuration: config)
self.view.addSubview(webView)
// Load your HTML file
let url = NSBundle.mainBundle().URLForResource("mydoc", withExtension: "html")!
webView.loadFileURL(url, allowingReadAccessToURL: url)
// Pass reference to the view controller
self.webView = webView
}
// Handle the message posted by Javascript
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
if let arr = message.body as? [Int] {
print(arr)
}
}
}
And the HTML + Javascript:
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Hello world</title>
<script type="text/javascript">
var arr = [];
function myFunction(number) {
arr.push(number);
// Send a message to your app on the myAction pipe
window.webkit.messageHandlers.myAction.postMessage(arr);
}
</script>
</head>
<body>
element1
element2
element3
</body>
</html>
I have a simple index.html file like this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
setInterval(function(){
if (typeof Cocoa !== 'undefined') {
Cocoa.log('JQuery loaded...');
}
}, 3000);
});
</script>
</head>
<body>
<h1>Testing script in objective c enviroment.</h1>
<div id="container" class="col">
<p>Paragraph 1</p>
<p>content goes here</p>
</div>
</body>
</html>
loaded from a WebView subclass in my AppDelegate:
appDelegate.h
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property NSWindow *webWindow;
#property myWebView *myWV;
#end
// ...
appDelegate.m
#import <WebKit/WebKit.h>
#import "myWebView.h"
#implementation AppDelegate
// ...
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
CGRect webRect = CGRectMake(0, 0, 1000, 1000);
self.webWindow = [[NSWindow alloc] initWithContentRect:webRect styleMask:NSTexturedBackgroundWindowMask backing:NSBackingStoreBuffered defer:NO];
self.webWindow.contentView = [[NSView alloc] initWithFrame:webRect];
self.myWV = [[myWebView alloc] initWithFrame:webRect frameName:#"myWV" groupName:#"webViews"];
self.myWV.UIDelegate = self;
self.myWV.resourceLoadDelegate = self;
self.myWV.frameLoadDelegate = self;
[self.webWindow.contentView addSubview:self.myWV];
[self.webWindow orderFront:NSApp];
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"index"
ofType:#"html"
inDirectory:#""];
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
NSURLRequest *request = [NSURLRequest requestWithURL:fileURL];
[[self.myWV mainFrame] loadRequest:request];
}
// associate js "Cocoa.log" function with -logJavaScriptString: method
+ (NSString *)webScriptNameForSelector:(SEL)sel
{
if(sel == #selector(logJavaScriptString:))
return #"log";
return nil;
}
//this allows JavaScript to call the -logJavaScriptString: method
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel
{
if(sel == #selector(logJavaScriptString:))
return NO;
return YES;
}
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
NSLog(#"webView loaded");
[[self.myWV windowScriptObject] setValue:self forKey:#"Cocoa"];
}
- (void)logJavaScriptString:(NSString *)logText
{
NSLog(#"JavaScript: %#",logText);
}
Why does nothing inside $(function() {}); get called? If I place the interval log function outside the JQuery onReady function, it does start to call the correct method after a few seconds.
Is there some cross-site policy issue going on that's preventing scripts from being loaded? If so, how can I disable it? Why isn't jQuery loading?
I changed
https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js
to
http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js
and now it works. If anyone knows why WebView doesn't like https, let me know in the comments.
I am trying to call a AS3 function from javascript but getting the following error in browser:
Object doesnot support property or method myCreateFile .
Below is the AS3 class:
package {
import flash.display.Sprite;
import flash.external.ExternalInterface;
import flash.net.FileReference;
import flash.events.IOErrorEvent;
import flash.events.Event;
import flash.system.Security;
public class CreateDoc extends Sprite {
private static const DEFAULT_FILE_NAME:String = "example.txt";
//FileReference Class well will use to save data
private var fr:FileReference;
public function CreateDoc()
{
// Register the function for external use.
ExternalInterface.addCallback("myCreateFile", myCreateFile);
Security.allowDomain("*");
}
public function myCreateFile():void
{
fr = new FileReference();
//open a native save file dialog, using the default file name
fr.save("Demo file", DEFAULT_FILE_NAME);
fr = null;
}
}
}
HTML code:
<html>
<head>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
try{
var flashvars = {};
var params = {allowscriptaccess:"always", movie:"CreateDoc.swf", wmode:"opaque", menu:"false"};
var attributes = {id:"flashcontent", name:"flashcontent"};
swfobject.embedSWF("CreateDoc.swf", "flashcontent", "800", "600", "10.0.0", "expressInstall.swf", flashvars, params, attributes);
}
catch(err){
alert(err.message);
}
</script>
<script type="text/javascript">
function doFunction(){
alert('Calling function..');
try{
var myObj = document.getElementById("flashcontent");
myObj.myCreateFile();
}
catch(err){
alert(err.message);
}
}
</script>
</head>
<body>
<div id="flashcontent">
</div>
<input id="save file" type="button" value="clickme" onclick="doFunction();" />
</body>
Any idea what is wrong when I try to call the myCreateFile() AS3 function which is present in CreateDoc class from java script?
The problem is that you have used same id in three places. Change "flashcontent" here:
swfobject.embedSWF("CreateDoc.swf", "flashcontent" , ... to something else , unique_id for example , so it will be: swfobject.embedSWF("CreateDoc.swf", "unique_id" ... . After that use this id here : document.getElementById("flashcontent"); too , like document.getElementById("unique_id");