Call a Swift function with completion handler from Java Script (TVML)

Viewed 601

I'm working on tvOS app (using TVML) and I'm trying to call a Swift function with completion handler from JS.

In the TVApplicationControllerDelegate I try with code like this:

let getVastData : @convention(block) (String,  @escaping (UInt32, [String], String) -> ()) -> Void = {
    (url : String, _ completion:@escaping (_ duration: UInt32, _ addLinks: [String], _ videoLink: String) -> ()) -> Void in
    VastManager.shared.startParsing(with: url, completion)
}

jsContext.setObject(unsafeBitCast(getVastData, to: AnyObject.self), forKeyedSubscript: "getVastData" as (NSCopying & NSObjectProtocol)!)

but I'm geting error: "TypeError: getVastData is not a function. (In 'getVastData', 'getVastData' is an instance of NSBlock"

Do you know where I'm wrong or how to correct call Swift func with completion handler from JS?

Thanks in advance, Milos

1 Answers

First, we need to register our Swift method in jsContext. Our Swift method needs to accept callback function, in our case a reference to a JavaScipt value which will be called after our operations in Swift are finished.

let getVastData : @convention(block) (String, JSValue) -> Void = {
    (url : String, completion : JSValue) -> Void in
    VastManager.shared.startParsing(from: url, completion: completion)
}
jsContext.setObject(unsafeBitCast(getVastData, to: AnyObject.self), forKeyedSubscript: "getVastData" as (NSCopying & NSObjectProtocol))

Because we want to call it asynchronously we need to store that reference. We will use JSManagedValue for storing JSValue to avoid retain cycle. A JSManagedValue object wraps a JSValue object, adding "conditional retain" behavior to provide automatic memory management of values. After our operations are finished, we can call our JavaScript callback function back with arguments. Briefly, we just passed the JS value from our TVML JS to Swift, store it and call it later.

class VastManager: NSObject {

    var completion: JSManagedValue?
    var url: String?

    public func startParsing(from url: String, completion: JSValue) {
        self.completion = JSManagedValue(value: completion)
        self.url = url
    }
    ...

    func parserDidEndDocument(_ parser: XMLParser) {

        ...

        _ = self.completion?.value?.call(withArguments: [duration, ads, videoUrl])
    }
}

In the end, we can call our Swift function from JavaScript and pass our callback function.

getVastData(vastTag, function(duration, adLinks, videoLink) {
   ...
}
// or
function updatePlayer(duration, adLinks, videoLink) {
   ...
}
getVastData(vastTag, updatePlayer) {
   ...
}
Related