objective c - settitle - ¿Cómo enlazar TVML/JavaScriptCore con UIKit/Objective-C(Swift)?
uicontrol swift (2)
Este video de la WWDC explica cómo comunicarse entre JavaScript y Obj-C
Aquí es cómo me comunico de Swift a JavaScript:
//when pushAlertInJS() is called, pushAlert(title, description) will be called in JavaScript.
func pushAlertInJS(){
//allows us to access the javascript context
appController!.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//get a handle on the "pushAlert" method that you''ve implemented in JavaScript
let pushAlert = evaluation.objectForKeyedSubscript("pushAlert")
//Call your JavaScript method with an array of arguments
pushAlert.callWithArguments(["Login Failed", "Incorrect Username or Password"])
}, completion: {(Bool) -> Void in
//evaluation block finished running
})
}
Aquí es cómo me comunico de JavaScript a Swift (requiere configuración en Swift):
//call this method once after setting up your appController.
func createSwiftPrint(){
//allows us to access the javascript context
appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//this is the block that will be called when javascript calls swiftPrint(str)
let swiftPrintBlock : @convention(block) (String) -> Void = {
(str : String) -> Void in
//prints the string passed in from javascript
print(str)
}
//this creates a function in the javascript context called "swiftPrint".
//calling swiftPrint(str) in javascript will call the block we created above.
evaluation.setObject(unsafeBitCast(swiftPrintBlock, AnyObject.self), forKeyedSubscript: "swiftPrint")
}, completion: {(Bool) -> Void in
//evaluation block finished running
})
}
[ACTUALIZACIÓN] Para aquellos de ustedes que quieran saber cómo se vería "pushAlert" en el lado de javascript, compartiré un ejemplo implementado en application.js
var pushAlert = function(title, description){
var alert = createAlert(title, description);
alert.addEventListener("select", Presenter.load.bind(Presenter));
navigationDocument.pushDocument(alert);
}
// This convenience funnction returns an alert template, which can be used to present errors to the user.
var createAlert = function(title, description) {
var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<alertTemplate>
<title>${title}</title>
<description>${description}</description>
</alertTemplate>
</document>`
var parser = new DOMParser();
var alertDoc = parser.parseFromString(alertString, "application/xml");
return alertDoc
}
Hasta ahora, tvOS
admite dos formas de hacer aplicaciones de TV, TVML y UIKit, y no hay menciones oficiales sobre cómo mezclar cosas para hacer una interfaz de usuario TVML (que es básicamente XML) con la parte del contador nativo para la lógica de la aplicación y / O (como reproducción, transmisión, persistencia de iCloud, etc.).
Entonces, ¿cuál es la mejor solución para mezclar TVML
y UIKit
en una nueva aplicación de tvOS
?
A continuación, he intentado una solución siguiendo fragmentos de código adaptados de los foros de Apple y preguntas relacionadas sobre el enlace JavaScriptCore a ObjC / Swift. Esta es una clase envoltura simple en su proyecto Swift.
import UIKit
import TVMLKit
@objc protocol MyJSClass : JSExport {
func getItem(key:String) -> String?
func setItem(key:String, data:String)
}
class MyClass: NSObject, MyJSClass {
func getItem(key: String) -> String? {
return "String value"
}
func setItem(key: String, data: String) {
print("Set key:/(key) value:/(data)")
}
}
donde el delegado debe conformar un TVApplicationControllerDelegate
:
typealias TVApplicationDelegate = AppDelegate
extension TVApplicationDelegate : TVApplicationControllerDelegate {
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let myClass: MyClass = MyClass();
jsContext.setObject(myClass, forKeyedSubscript: "objectwrapper");
}
func appController(appController: TVApplicationController, didFailWithError error: NSError) {
let title = "Error Launching Application"
let message = error.localizedDescription
let alertController = UIAlertController(title: title, message: message, preferredStyle:.Alert ) self.appController?.navigationController.presentViewController(alertController, animated: true, completion: { () -> Void in
})
}
func appController(appController: TVApplicationController, didStopWithOptions options: [String : AnyObject]?) {
}
func appController(appController: TVApplicationController, didFinishLaunchingWithOptions options: [String : AnyObject]?) {
}
}
En este punto el javascript es muy simple como. Eche un vistazo a los métodos con parámetros nombrados, tendrá que cambiar el nombre del método de contador de JavaScript:
App.onLaunch = function(options) {
var text = objectwrapper.getItem()
// keep an eye here, the method name it changes when you have named parameters, you need camel case for parameters:
objectwrapper.setItemData("test", "value")
}
App. onExit = function() {
console.log(''App finished'');
}
Ahora, supongo que tienes una interfaz js muy compleja para exportar como
@protocol MXMJSProtocol<JSExport>
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3;
- (NSString*)getVersion;
@end
@interface MXMJSObject : NSObject<MXMJSProtocol>
@end
@implementation MXMJSObject
- (NSString*)getVersion {
return @"0.0.1";
}
puedes hacer como
JSExportAs(boot,
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3 );
En este punto, en la parte del contador JS, no hará la funda de camello:
objectwrapper.bootNetworkUser(statusChanged,networkChanged,userChanged)
pero vas a hacer:
objectwrapper.boot(statusChanged,networkChanged,userChanged)
Finalmente, mira esta interfaz otra vez:
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3;
El valor JSValue * pasado. Es una forma de pasar los controladores de finalización entre ObjC/Swift
y JavaScriptCore
. En este punto en el código nativo, todos llamamos con argumentos:
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *state = [NSNumber numberWithInteger:status];
[networkChanged.context[@"setTimeout"]
callWithArguments:@[networkChanged, @0, state]];
});
En mis conclusiones, he visto que MainThread se bloqueará si no se envía en el hilo principal y en async. Así que llamaré a la llamada "setTimeout" de javascript que llama a la devolución de llamada del controlador de finalización.
Así que el enfoque que he usado aquí es:
- Use
JSExportAs
paraJSExportAs
métodos con parámetros nombrados y evite usar las contrapartes de javascript de casos de camellos como callMyParam1Param2Param3 - Use
JSValue
como parámetro para deshacerse de los manejadores de finalización. Use callWithArguments en el lado nativo. Use las funciones de javascript en el lado JS; -
dispatch_async
para los controladores de finalización, posiblemente llamando a un setTimeout 0-delayed en el lado de JavaScript, para evitar que la interfaz de usuario se congele.
[ACTUALIZACIÓN] He actualizado esta pregunta para ser más claro. Estoy encontrando una solución técnica para unir TVML
y UIKit
con el fin de
- Comprende el mejor modelo de programación con
JavaScriptCode
- Tenga el puente correcto de
JavaScriptCore
aObjectiveC
y viceversa - Tenga las mejores actuaciones cuando llame a
JavaScriptCode
desdeObjective-C
Usted provocó una idea que funcionó ... casi. Una vez que haya mostrado una vista nativa, no hay un método sencillo hasta el momento para insertar una vista basada en TVML en la pila de navegación. Lo que he hecho en este momento es:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.appController?.navigationController.popViewControllerAnimated(true)
dispatch_async(dispatch_get_main_queue()) {
tvmlContext!.evaluateScript("showTVMLView()")
}
... entonces en el lado de JavaScript:
function showTVMLView() {setTimeout(function(){_showTVMLView();}, 100);}
function _showTVMLView() {//push the next document onto the stack}
Esta parece ser la forma más limpia de mover la ejecución del subproceso principal y al subproceso JSVirtualMachine y evitar el bloqueo de la interfaz de usuario. Tenga en cuenta que tuve que abrir al menos el controlador de vista nativo actual, ya que de lo contrario se enviaría un selector mortal.