Inject content from Swift code to WKWebView - ios

I am developing an app which uses WKWebView to show an html document. I want to inject content from my Swift code to this document. This is what I tried:
var myString: String
// ... Code which assigns some value to myString
targetView.evaluateJavaScript("injector.injectSnippet(\(myString);", completionHandler: nil)
This works exactly as I expect so long as the argument in the JavaScript function call is hardcoded:
targetView.evaluateJavaScript("injector.injectSnippet(\"Hello World\");", completionHandler: nil)
The injector object is in a JavaScript file which I have already added to the web view's user controller:
var injector = (function () {
"use strict"
var msgBox;
var injectSnippet = function (test) {
msgBox.innerHTML = "Received value: " + test;
}
var init = function () {
// Create msgBox div element and insert it into the document
}
return {
init: init,
injectSnippet: injectSnippet
};
} ());
injector.init();
How do I get content into the WKWebView?
I went through the Apple docs WebKit Objective-C Programming Guide and WebKit DOM Programming Topics but I don't understand what sort of WebView class they are using there. Both WKWebView and UIWebView don't have all the API they use in these documents.
Is there some way to manipulate the DOM directly in Swift? I get the sense that WebScriptObject might be what I'm looking for but can I use this with WKWebView?

If you want to continue using evaluateJavaScript, you need to inject the value into the javascript string.
Something like
var jsTemplate = "injector.injectSnippet(\"%#\");"
var js = String(format:jsTemplate , myString)

Related

GTK4 Vala - show FileChooserDialog

I am playing around with Vala and GTK4.
FileChooserDialog is not working for me
using Gtk;
int main (string[] argv) {
// Create a new application
var app = new Gtk.Application ("com.example.GtkApplication",
GLib.ApplicationFlags.FLAGS_NONE);
app.activate.connect (() => {
// Create a new window
var window = new Gtk.ApplicationWindow (app);
window.title = "File chooser";
window.set_default_size (350, 70);
window.resizable = false;
// Create a new button
var file_choose_button = new Gtk.Button.with_label ("...");
file_choose_button.clicked.connect (() => {
var fileChooser = new FileChooserDialog(
"Select File",
window,
FileChooserAction.OPEN,
"Cancel",
ResponseType.CANCEL,
"Open",
ResponseType.ACCEPT,
null);
fileChooser.response.connect(()=> {
stdout.printf("File selectd!");
});
// WHAT TO DO IN ORDER TO SHOW FILE CHOOSER?
});
window.set_child (file_choose_button);
// Show
window.present ();
});
return app.run (argv);
}
I am missing some important piece of code, that will cause the FileChooserDialog to "appear".
In previous Versions of GTK there is "dialog.run" - which is missing in GTK4.
The C-Example on https://docs.gtk.org/gtk4/class.FileChooserDialog.html uses makro(?) "gtk_widget_show(xxx)" for which I was not able to find an representation in Vala.
Any help appreciated!
Best Regards
Emil
After some struggle the solution was found (and is pretty simple).
As stated in the Vala Documentaion Site - File Chooser Dialog
It inherits from couple of classes one of which is GTK.Window.
So it is as simple as calling the present() method.
Thus the missing command above is:
fileChooser.present();
One should not forget to use the close() method once file was selected or selection was canceled.
Important note:
"gtk_widget_show()" representation in Vala is GTK.Widget.show() BUT
I was not clever enough to find out how to prepare the parameter.
It expects pointer (GtkWidget*) and simply passing the "fileChooser" causes all kinds of compiler exceptions.
May be someone can throw more light on this (as I am using Vala to avoid the use of C - I am clearly not the expert in this area)

Memory Leak Kotlin Native library in iOS

I'm building a Kotlin library to use in my iOS app using Kotlin/Native. After I call some methods in the library from Swift, which works, I also want to call methods in Swift from the library. To accomplish this I implemented an interface in the library:
class Outbound {
interface HostInterfaceForTracking {
fun calcFeatureVector(bitmap: Any?): Array<Array<FloatArray>>?
}
var hostInterface: HostInterfaceForTracking? = null
fun registerInterface(hostInterface: HostInterfaceForTracking) {
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
}
}
This is implemented on the Swift side like this:
class HostInterfaceForTracking : OutboundHostInterfaceForTracking {
var t : Outbound? = nil
init() {
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
func calcFeatureVector(bitmap: Any?) -> KotlinArray<KotlinArray<KotlinFloatArray>>? {
do {
var test : Any? = (bitmap as! Bitmap).bitmap
return nil
} catch {
return nil
}
}
}
The TrackingWrapper looks like this:
class TrackingWrapper : NSObject {
static var instance: TrackingWrapper? = nil
var inbound: Inbound? = nil
var worker: Worker
override init() {
self.worker = Worker()
super.init()
initInboundInterface()
}
func initInboundInterface() {
runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
HostInterfaceForTracking()
}
}
func runOnMatchingLibraryThread(block: #escaping() -> Void) {
worker.enqueue {
block()
}
}
}
The function runOnMatchingLibraryThread is needed because every call to the TrackingLibrary needs to be called from the exact same thread, so the Worker class initializes a thread and enqueues every method to that thread.
The Bitmap in this case is simply a wrapper for an UIImage, which I already accessed with the .bitmap call, so I've tried to access the wrapped UIImage and save it in the test variable. The library gets the current camera frame from the Swift side every few frames and sends the current image wrapped as a Bitmap to the method calcFeatureVector depicted here.
Problem: My memory load starts increasing as soon as the app starts until the point it crashes. This is not the case if I don't access the wrapped UIImage (var test : Any? = (bitmap as! Bitmap)). So there is a huge memory leak, just by accessing the wrapped variable on the Swift side. Is there anything I've missed or is there any way to release the memory?
Looks like you have a circular dependency here:
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
You are asking a property inside HostInterfaceForTracking to maintain a strong reference to the same instance of HostInterfaceForTracking. You should be using [weak self] to avoid the circular reference.
EDIT:
Ok after seeing the rest of you code theres a lot to unpack. There is a lot of unnecessary bouncing back and forth between classes, functions and threads.
There is no need to use runOnMatchingLibraryThread to just create an instance of something. You only need to use that for the code processing the image itself (I would assume, I haven't seen anything so far that requires being split off into another thread). Inside TrackingWrapper, you can create a singleton more easily, and matching the swift pattern by simply doing this as the first line:
static let shared = TrackingWrapper()
And everywhere you want to use it, you can just call TrackingWrapper.shared. This is more common and will avoid one of the levels of indirection in the code.
I'm not sure what Worker or Inbound are, but again these can and should be created inside the TrackingWrapper init, rather than branching Inbound's init, to use another thread.
Inside initInboundInterface you are creating an instance of HostInterfaceForTracking() which doesn't get stored anywhere. The only reason HostInterfaceForTracking is continuing to stay in memory after its creation, is because of the internal circular dependency inside it. This is 100% causing some form of a memory issue for you. This should probably also be a property on TrackingWrapper, and again, its Init should not be called inside runOnMatchingLibraryThread.
Having HostInterfaceForTracking's init, also using runOnMatchingLibraryThread is problematic. If we inline all the code whats happening is this:
TrackingWrapper
init() {
self.runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
}
Having all these classes unnecessarily keep coming back to TrackingWrapper is going to cause issues.
Inside HostInterfaceForTracking 's init, no need to be creating Outbound on a separate thread. First line in this class can simply be:
var t : Outbound = OutBound()
Or do it in the init if you prefer. Either way will also remove the issue of needing to unwrap Outbound before using it.
Inside Outbound you are storing 2 references to the hostInterface instance:
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
I would have imagined there should only be 1. If there are now multiple copies of a class that has a circular dependency, which has multiple calls to separate threads. This again will cause issues.
I'm still not sure on the differences between Swift and Kotlin. In Swift when passing self into a function to be stored, the class storing it would mark the property as weak, like so:
weak var hostInterface: ......
Which will avoid any circular dependency from forming. A quick google says this isn't how things work in Kotlin. It might be better to look into the swift side passing in a closure (lambda on kotlin) and the kotlin side executing that. This might avoid the need to store a strong reference. Otherwise you need to be looking into some part of your code setting hostInterface back to null. Again its a bit hard to say only seeing some of the code and not knowing how its working.
In short, it looks like the code is very over complicated, and needs to be simplified, so that all these moving pieces can be tracked easier.

Creating a layer in javascript?

I have a code that is working with a canvas and I'd like to convert it into a layer.
The problem is that I do not want to use the build mechanism of OL3, I just want to use plain javascript.
At the moment, the problem I have is that my handleRender_ function is never called.
Here is my JS code :
ol.layer.MyLayerProperty = {
};
ol.layer.My = function (opt_options) {
var options = opt_options || {};
ol.layer.Layer.call(this, options);
this.on('render', this.handleRender_.bind(this)); //I suspect this is not working
};
ol.inherits(ol.layer.My, ol.layer.Layer);
ol.layer.My.prototype.handleRender_ = function (event) {
console.log('render process'); //never called
};
In fact, to display a canvas "above" openlayers, you simply have to use ImageCanvas.
see http://www.acuriousanimal.com/thebookofopenlayers3/chapter03_04_imagecanvas.html for example

Singleton Class and multiple asynchronous operations in Swift

I am writing an iOS Swift application that, upon startup, needs to get 2 (or more) sets of data from a server. During initialization, I must do some processing on the sets of data - kind of merging them together. And only then can I start my view controller and interact with the user. The data obtained during initialization is an array of objects that I reuse everywhere in my app.
At first, I put the code in the viewDidLoad of the view controller, but it became messy very quickly. Now, I am putting all this code inside a "singleton" class so that all my view controllers can reuse the same data. My singleton class is defined using the following code excerpt:
class Teams {
var teams: [Team]
static var sharedInstance = Teams()
private init() {
teams = []
let queryText = "CardTypeOWSTEXT:Root"
//The first async call (DataManager invokes the http REST to the server)
DataManager.spSearch (queryText, success: { teamsJson -> Void in
let json = JSON(data: teamsJson)
...
// all the code to handle the data received from the server
...
})
//The second async call
DataManager.spSocialFollowedSites ({ sitesJson -> Void in
let json = JSON(data: sitesJson)
...
// all the code to handle the data received from the server
...
})
I am very new to iOS development, and I try to replicate an existing app I wrote in AngularJS. In that app, it is very simple to invoke the REST calls to the server and using promises, all I have to do is wait for both to complete to execute the matching with a statement like:
$q.all([promise1, promise2]).then(...)
but this is javascript/angular, not Swift! I know there are promises libraries (eg. PromiseKit), but I am wondering if this is the right way to do it? Would it be simpler/better with GCD? Also, does my approach using a singleton makes sense? Its purpose is to hold data shared by all the other classes, and in angular, the same concept is a factory and works quite well...
I have read many places and could not get any good guidance, so any help would be appreciated!
I use PromiseKit in my code and I'm very satisfied, that said - you can always synchronise without it. It's called a semaphore. Basically:
Set a counter to 2.
when a function is done - decrement it by one and save the value.
When it hits zero, call a function.
Basically, something like:
var counter = 2
var firstResult: teamsJsonType?
var secondResult: sitesJsonType?
let handler = ... ; // function code that runs when both results are ready
DataManager.spSearch (queryText, success: { teamsJson -> Void in
firstResult = teamsJson
counter -= 1
if counter == 0 { handler() }
})
DataManager.spSocialFollowedSites ({ sitesJson -> Void in
secondResult = sitesJson
counter -= 1
if counter == 0 { handler() }
})

How to invoke an ActionScript method from JavaScript (HTMLLoader) object in AIR?

So I have an Application Sandbox HTMLLoader object which I create in AIR and simply want to call ActionScript methods from JavaScript. In Flash, this is accomplished through our trusty ExternalInterface.addCallback() function. However in AIR, things are quite a bit different, and I just can't seem to get it to work.
Here is a simplified overview of my project:
My AIR (ActionScript) main:
public class Main extends Sprite {
public var _as3Var:String = "testing";
public function as3Function():void
{
trace("as3Function called from Javascript");
}
public function Main() {
NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, onInvoke);
}
protected function onInvoke(e:InvokeEvent):void {
NativeApplication.nativeApplication.removeEventListener(InvokeEvent.INVOKE, onInvoke );
var app = new App();
addChild(app);
app.init(new ExternalContainer(), e.currentDirectory, e.arguments);
}
}
And this is how I create my HTMLLoader object:
{
_html = new HTMLLoader();
_html.useCache = false;
_html.runtimeApplicationDomain = ApplicationDomain.currentDomain;
_html.load(new URLRequest("sandbox/AirRoot.html"));
_html.width = 800;
_html.height = 600;
App.ref.addChild(_html);
}
And at last, here is my snippet of JavaScript in my AirRoot.html file which is trying to call the public method as3Function() declared in my Main class:
Exposed.testAs3 = function()
{
air.trace("Exposed.testAs3 called"); /* This works fine. */
air.trace("runtimeVersion:"); /* This works fine. */
air.trace(air.NativeApplication.nativeApplication.runtimeVersion); /* This works fine. */
air.trace("seeing if I can get to AS3 params..."); /* This works fine. */
/* This doesn't work - get the following error: TypeError: Value undefined does not allow function calls. */
air.NativeApplication.nativeApplication.as3Function();
}
What am I missing?
OK, I am going to answer my own question. I promise this was not a ploy to gain more reputation points, but I was seriously confused today but have now found the appropriate answers and documentation - which is usually the main problem to many an engineer's question...
Anyway, the answer:
The AIR HTMLLoader object contains a magical property, HTMLLoader.window, which is a proxy to the JavaScript window object. So setting HTMLLoader.window = AS3Function; is one way - or in relation to my previously included example (assuming I setup a static property called Main which pointed to the Main class):
_html.window.as3Function = Main.as3Function;
And now in JavaScript I can just call as3Function as:
<script>
window.as3Function();
</script>
Another interesting property is the JavaScript "window.htmlLoader" object. It is a proxy to the AS3 HTMLLoader parent object, in my case, the _html object. From this you can access things in relation to the _html object from JavaScript.
I'm not sure if this is a change in the new version of AIR, but you no longer need to reference the window in the javascript call, you can just do this:
<script>
as3Function();
</script>

Resources