How to call swift function from javascript web view - javascript

I am launching a web-view from my swift app. From the web-view I want to call a swift function on a button click. I found the code sample here.
When I press the button, nothing happens. The URL I am loading in the web-view is https://test-swift-js.github.io
Here is the code :
import Cocoa
import WebKit
class ViewController : NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
var webView:WebView!
#IBAction func mybutton(_ sender: Any) {
let url = URL(string: "https://test-swift-js.github.io")
let request = URLRequest(url: url!)
webView = WebView(frame: NSRect(x: 0, y: 0, width: 1000, height: 1000))
webView.mainFrame.load(request)
self.view.addSubview(webView)
}
func webView(webView: WebView!, didClearWindowObject windowObject: WebScriptObject!, forFrame frame: WebFrame!) {
windowObject.setValue(self, forKey: "interOp")
}
//The name used to represent the Swift function name in Javascript.
class func webScriptNameForSelector(aSelector: Selector) -> String!
{
if aSelector == "callSwift:"
{
return "callSwift"
}
else
{
return nil
}
}
class func isSelectorExcludedFromWebScript(aSelector: Selector) -> Bool
{
return false
}
func callSwift(message:String)
{
var alert = NSAlert()
alert.messageText = message
alert.alertStyle = NSAlertStyle.informational
alert.beginSheetModal(for: self.view.window!, completionHandler: nil)
}
}

Maybe you forget to set the delegate.

Related

App Extension (Action Extension) doesn’t open

For some reason that I don't understand, the Action Extension Button (in Share menu) doesn't respond. Action extension, at this point, catches the URL from Safari (where it was launched from) to make some things after. As a layer between Web and extension there is JS file (maybe something wrong here, i just copied it)
ViewController:
class ActionViewController: UIViewController {
var SafariURL: NSURL!
override func viewDidLoad() {
super.viewDidLoad()
let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem
let itemProvider = extensionItem!.attachments?.first as? NSItemProvider
let propertyList = String(kUTTypePropertyList)
if itemProvider!.hasItemConformingToTypeIdentifier(propertyList) {
print("I'm here2")
itemProvider!.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in
let dictionary = item as? NSDictionary
OperationQueue.main.addOperation {
let results = dictionary![NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary
let urlString = results!["currentUrl"] as? String
self.SafariURL = NSURL(string: urlString!)
}
})
} else {
print("error")
}
}
#IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)
}
JS File:
var GetURL = function() {};
GetURL.prototype = {
run: function(arguments) {
arguments.completionFunction({ "currentUrl" : document.URL });
},
finalize: function(arguments) {
var message = arguments["statusMessage"];
if (message) {
alert(message);
}
}
};
var ExtensionPreprocessingJS = new GetURL;
Finally, you should change a content of override func viewDidLoad to
super.viewDidLoad()
if let inputItem = extensionContext?.inputItems.first as? NSExtensionItem {
if let itemProvider = inputItem.attachments?.first {
itemProvider.loadItem(forTypeIdentifier: kUTTypePropertyList as String) { [self] (dict, error) in
guard let itemDictionary = dict as? NSDictionary else { return }
guard let javaScriptValues = itemDictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else { return }
self.Pageurl = javaScriptValues["URL"] as? String ?? ""
JS is ok!

Execute Javascript on external url using HybridView, Xamarin cross platform

I have tried to execute JavaScript on an external url (ie: http://facebook.com) using WebView from Visual Studio Mac 2019, and so far no results.
To do so, I have tried to follow along with the official tutorial here https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview, and also tried a simpler one here: https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/
Here is what I did with explanations:
On my shared folder, I created an HybridWebView class with the following code:
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(Func<string, Task<string>>),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get => (string)GetValue(UriProperty);
set
{
SetValue(UriProperty, value);
}
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
public Func<string, Task<string>> ExecuteJavascript
{
get { return (Func<string, Task<string>>)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
From The macOS project which I use to test my cross-platform app, I tried the following custom renderer:
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as HybridWebView;
if (webView != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.ExecuteJavascript.ToString())));
}
}
}
To note that the following part wouldn't work:
var webView = e.NewElement as HybridWebView;
if (webView != null)
webView.ExecuteJavascript = (js) =>
{
return Task.FromResult(this.ExecuteJavascript(js)); // issue at ExecuteJavascript with following error ('HybridWebViewRenderer' does not contain a definition for 'ExecuteJavascript' ), hence replaced by Control.LoadRequest ...
};
From my ViewModel, I did the following:
public Func<string, Task<string>> EvaluateJavascript { get; set; }
public async Task OnConnectTapped()
{
Console.WriteLine("on connect tapped");
// passing the url onto a connection service
var hybridWebView = new HybridWebView
{
Uri = "https://facebook.com/"
};
//hybridWebView.InvokeAction("document.getElementById('td');");
//var result = await hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
var result = await hybridWebView.ExecuteJavascript("document.cookie;");
Console.WriteLine("result is {0}", result);
}
Here is the error when trying to execute my code:
System.NullReferenceException: Object reference not set to an instance of an object
at MyApp.ViewModel.MainModel.OnConnectTapped () [0x00031] in .../../././/ViewModel/MainModel.cs:451
at .......<.ctor>g__c5|48_9 () [0x0001f] in /../../../.cs:143
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/external/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021
at Foundation.NSAsyncSynchronizationContextDispatcher.Apply () [0x00002] in /Library/Frameworks/Xamarin.Mac.framework/Versions/6.6.0.12/src/Xamarin.Mac/Foundation/NSAction.cs:178
at at (wrapper managed-to-native) AppKit.NSApplication.NSApplicationMain(int,string[])
at AppKit.NSApplication.Main (System.String[] args) [0x00040] in /Library/Frameworks/Xamarin.Mac.framework/Versions/6.6.0.12/src/Xamarin.Mac/AppKit/NSApplication.cs:100
at redacted.macOS.MainClass.Main (System.String[] args) [0x00017] in /Users/dom-bruise/Projects/redacted/redacted.macOS/Main.cs:11
For me, it could either be because I can't execute external pages, or the part where I replaced by the following messing up my attempt.
if (webView != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.ExecuteJavascript.ToString())));
}
My main goal here is to have my app execute JavaScript underneath the hood on pages using WebView, and fill in forms automatically calling back C# from my app.

How to use UIImagePickerController from webview

I'm following this tutorial online https://makeapppie.com/2016/06/28/how-to-use-uiimagepickercontroller-for-a-camera-and-photo-library-in-swift-3-0/ (with a little bit of a twist). I'm trying to call my UIImagePickerController from a webview and I'm not sure how to change the code to get it to work properly. The difference is that I'm going to be receiving a call from javascript and then invoking the picker as a result instead of with a UIButton. Then I want to send the image back as a base64 string using my javascript interface.
Here is what I have so far.
import UIKit
import WebKit
class ViewController: UIViewController,
WKScriptMessageHandler,
UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
var webView: WKWebView?
let userContentController = WKUserContentController()
let picker = UIImagePickerController();
#IBAction func photoFromLibrary(_ sender: UIBarButtonItem) {
picker.allowsEditing = false
picker.sourceType = .photoLibrary
picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
present(picker, animated: true, completion: nil)
}
override func loadView() {
super.loadView()
let config = WKWebViewConfiguration()
config.userContentController = userContentController
self.webView = WKWebView(frame: self.view.bounds, configuration: config)
userContentController.add(self, name: "iOS")
let url = URL(string:"https://relate.lavishweb.com/account")
let request = URLRequest(url: url!)
_ = webView?.load(request)
self.view = self.webView
}
override func viewDidLoad() {
super.viewDidLoad()
picker.delegate = self
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
webView?.evaluateJavaScript("window.settings.setImageBase64FromiOS()") { (result, error) in
if error != nil {
print("Success")
} else {
print("Failure")
}
}
// now use the name and token as you see fit!
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject])
{
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2
// myImageView.contentMode = .scaleAspectFit //3
// myImageView.image = chosenImage //4
//I want to do additional stuff here and send back as a base64 String
dismiss(animated:true, completion: nil) //5
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
}
Hi I have the same problem an ein solved it, but it's a little dirty.
In viewDidLoad() you override the Delegate from WKWebView. The WKWebView use the Delegate, too. You need to save the Delegate local.
var oldDelegate: UIImagePickerControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
oldDelegate = picker.delegate // save the Delegate from WKWebView
picker.delegate = self
}
now you you can run your code in your delegate. In the end of your method imagePickerController() you have to invoke imagePickerController() from the old delegate.
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject]){
var myinfo = info
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2
// myImageView.contentMode = .scaleAspectFit //3
// myImageView.image = chosenImage //4
myinfo[UIImagePickerControllerOriginalImage] = chosenImage
myinfo[UIImagePickerControllerImageURL] = nil
oldDelegate?.imagePickerController!(picker, didFinishPickingMediaWithInfo: myinfo)
}
I set the URL nil, because the WKWebView load the image direct from drive if the URL is filled.
myinfo[UIImagePickerControllerImageURL] = nil
I hope this is helpful.
Okay I figured it out,
So I didn't know what IBAction was but basically that means that the function that I'm going to declare after it is something that is called by an Interface Builder Element such as a UIButton or something like that. After realizing this, I just changed the function to
func photoFromLibrary() {
picker.allowsEditing = false
picker.sourceType = .photoLibrary
picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
present(picker, animated: true, completion: nil)
}
then in my JavaScript interface I simply called the function when I received the call from JavasScript.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
webView?.evaluateJavaScript("window.settings.setImageBase64FromiOS()") { (result, error) in
if error != nil {
print("failure")
} else {
self.photoFromLibrary()
}
}
}
Stay tuned and I will post how to encode an image to a Base64 string while also shrinking it at load time.(Once I find out how to do that ofcourse)
EDIT: I figured out how to resize the image and convert to Base64 string very quickly.
I implemented this extension...
extension UIImage {
func resized(withPercentage percentage: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: size.width * percentage, height: size.height * percentage)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
}
func resized(toWidth width: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
and then I returned the string like so...
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject])
{
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
let thumb = chosenImage.resized(toWidth: 72.0)
let imageData:NSData = UIImagePNGRepresentation(thumb!)! as NSData
let dataImage = imageData.base64EncodedString(options: .lineLength64Characters)
print(dataImage)
dismiss(animated:true, completion: nil) //5
}

Is it possible to return the result of a JS confirmation dialog presented by JXBrowser back to the JS section that called it?

I'm using JavaFX/JXBrowser to show an alert/dialog when the web page loaded into the Browser calls on Window.alert or window.confirm. However, I can't figure out how to return the result of the confirmation dialog (true/false) to JS. Since alert.showAndWait() is a blocking function, JS should wait for this result. However, showAndWait is also called in a Platform.runLater runnable, so I can't return the result. Short of writing JS functions to do the true/false code and calling those based on the result of showAndWait, is there any other option?
browser.setDialogHandler(new DialogHandler() {
#Override
public CloseStatus onConfirmation(DialogParams params) {
Platform.runLater(new Runnable() {
#Override
public void run() {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Yes/No");
alert.setHeaderText(null);
alert.setContentText("Yes/No");
Optional<ButtonType> result = alert.showAndWait();
if(result.isPresent())
{
if(result.get()==ButtonType.YES)
{
//Send true to calling JS function
}else
{
//Send false to calling JS function
}
}else
{
System.out.println("No result!");
}
}
});
return null; //because I need to return something and I can't figure out how to get values from the runnable
}
...
}
You can use the following approach:
#Override
public CloseStatus onConfirmation(DialogParams params) {
final AtomicReference<CloseStatus> status = new AtomicReference<CloseStatus>();
final CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(new Runnable() {
#Override
public void run() {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Yes/No");
alert.setHeaderText(null);
alert.setContentText("Yes/No");
Optional<ButtonType> result = alert.showAndWait();
if (result.isPresent()) {
if (result.get() == ButtonType.YES) {
status.set(CloseStatus.OK);
} else {
status.set(CloseStatus.CANCEL);
}
} else {
System.out.println("No result!");
}
}
});
try {
latch.await();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
return status.get();
}

use javascript data in tableView Swift

hello i'm new with swift programming and have the following issue. I read about how i can get data from safari in my action extension. using code inside viewDidLoad. so far so good.
i build a tableView with a tutorial and it works fine.
know i would like to use data data i extracted form safari in to my tableView. The problem is that my tableView is loaded first and after that my data is pulled from safari.
how can i use the data directly in my TableView?
here my code. beware i am new at this and it is under construction. The tableVie is now filled with information as demonstrated in the tutorial.
import UIKit
import MobileCoreServices
var webDataArray : [String] = []
class ActionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var myTableView: UITableView!
var arrayOfWebData: [CustomCellContents] = [CustomCellContents]()
var effectStannd = false
override func viewDidLoad() {
super.viewDidLoad()
for item: AnyObject in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: AnyObject in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as! String) {
itemProvider.loadItemForTypeIdentifier(kUTTypePropertyList as! String, options: nil, completionHandler: {(list, error) in
if let results = list as? NSDictionary {
NSOperationQueue.mainQueue().addOperationWithBlock {
var webData = results.description
webDataArray = split(webData) {$0 == ","}
var testArray = split(webDataArray[0]) {$0 == " "}
webDataArray.removeAtIndex(0)
webDataArray.append(testArray[7])
testArray = split(webDataArray[13]) {$0 == " "}
webDataArray.removeAtIndex(13)
webDataArray.append(testArray[1])
//println(webDataArray)
println(webDataArray.count)
}
}
})
}
}
}
println(webDataArray.count)
self.setUpWebData()
self.addEffect()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequestReturningItems(self.extensionContext!.inputItems, completionHandler: nil)
}
func setUpWebData()
{
var webData1 = CustomCellContents(fileName: "Torrent", fileKind: "img1.jpeg")
var webData2 = CustomCellContents(fileName: "Magnet", fileKind: "img2.jpeg")
var webData3 = CustomCellContents(fileName: "Exe", fileKind: "img1.jpeg")
var webData4 = CustomCellContents(fileName: "DMG", fileKind: "img2.jpeg")
arrayOfWebData.append(webData1)
arrayOfWebData.append(webData2)
arrayOfWebData.append(webData3)
arrayOfWebData.append(webData4)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return arrayOfWebData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomCell
let CustomCellContents = arrayOfWebData[indexPath.row]
cell.setCell(CustomCellContents.fileName, imageName: CustomCellContents.fileKind)
return cell
}
/////// Custom swip from Right
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
var shareAction = UITableViewRowAction(style: .Normal, title: "Download") { (action: UITableViewRowAction!, indexPath: NSIndexPath!) -> Void in
let firstActivityItem = self.arrayOfWebData[indexPath.row]
let activityViewControler = UIActivityViewController(activityItems: [firstActivityItem], applicationActivities: nil)
self.presentViewController(activityViewControler, animated: true, completion: nil)
}
shareAction.backgroundColor = UIColor.blueColor()
return [shareAction]
}
//// de volgende functie zorgt ervoor dat de rij automatisch word gedeselecteerd
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
self.myTableView.deselectRowAtIndexPath(indexPath, animated: true)
}
func addEffect(){
if effectStannd{
var effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
var effectView = UIVisualEffectView(effect: effect)
effectView.frame = CGRectMake(0, 0, 320, 600)
view.addSubview(effectView)
effectStannd = false
}
}
}
I found the solution in refreshing my tableview after loading. It works fine like this.
func didRefreshList(){
var tableView = myTableView
self.tableData = webDataArray3
tableView.reloadData()
self.refreshControl.endRefreshing()
}

Categories