uikit - developer - tvos app store
¿Puedo mezclar UIKit y TVMLKit dentro de una aplicación? (4)
Como se mencionó en la respuesta aceptada, puede llamar a casi cualquier función Swift desde el contexto de JavaScript. Tenga en cuenta que, como su nombre lo indica, setObject:forKeyedSubscript:
también aceptará objetos (si se ajustan a un protocolo que hereda de JSExport) además de los bloques, lo que le permite acceder a métodos y propiedades en ese objeto. Aquí un ejemplo
import Foundation
import TVMLKit
// Just an example, use sessionStorage/localStorage JS object to actually accomplish something like this
@objc protocol JSBridgeProtocol : JSExport {
func setValue(value: AnyObject?, forKey key: String)
func valueForKey(key: String) -> AnyObject?
}
class JSBridge: NSObject, JSBridgeProtocol {
var storage: Dictionary<String, String> = [:]
override func setValue(value: AnyObject?, forKey key: String) {
storage[key] = String(value)
}
override func valueForKey(key: String) -> AnyObject? {
return storage[key]
}
}
Luego en su controlador de aplicación:
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let bridge:JSBridge = JSBridge();
jsContext.setObject(bridge, forKeyedSubscript:"bridge");
}
Entonces puedes hacer esto en tu JS: bridge.setValue([''foo'', ''bar''], "baz")
No solo eso, sino que puede anular vistas para elementos existentes, o definir elementos personalizados para usar en su marca, y respaldarlos con vistas nativas:
// Call lines like these before you instantiate your TVApplicationController
TVInterfaceFactory.sharedInterfaceFactory().extendedInterfaceCreator = CustomInterfaceFactory()
// optionally register a custom element. You could use this in your markup as <loadingIndicator></loadingIndicator> or <loadingIndicator /> with optional attributes. LoadingIndicatorElement needs to be a TVViewElement subclass, and there are three functions you can optionally override to trigger JS events or DOM updates
TVElementFactory.registerViewElementClass(LoadingIndicatorElement.self, forElementName: "loadingIndicator")
Ejemplo de elemento personalizado rápido:
import Foundation
import TVMLKit
class LoadingIndicatorElement: TVViewElement {
override var elementName: String {
return "loadingIndicator"
}
internal override func resetProperty(resettableProperty: TVElementResettableProperty) {
super.resetProperty(resettableProperty)
}
// API''s to dispatch events to JavaScript
internal override func dispatchEventOfType(type: TVElementEventType, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) {
//super.dispatchEventOfType(type, canBubble: canBubble, cancellable: isCancellable, extraInfo: extraInfo, completion: completion)
}
internal override func dispatchEventWithName(eventName: String, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) {
//...
}
}
Y aquí es cómo configurar una fábrica de interfaces personalizada:
class CustomInterfaceFactory: TVInterfaceFactory {
let kCustomViewTag = 97142 // unlikely to collide
override func viewForElement(element: TVViewElement, existingView: UIView?) -> UIView? {
if (element.elementName == "title") {
if (existingView != nil) {
return existingView
}
let textElement = (element as! TVTextElement)
if (textElement.attributedText!.length > 0) {
let label = UILabel()
// Configure your label here (this is a good way to set a custom font, for example)...
// You can examine textElement.style or textElement.textStyle to get the element''s style properties
label.backgroundColor = UIColor.redColor()
let existingText = NSMutableAttributedString(attributedString: textElement.attributedText!)
label.text = existingText.string
return label
}
} else if element.elementName == "loadingIndicator" {
if (existingView != nil && existingView!.tag == kCustomViewTag) {
return existingView
}
let view = UIImageView(image: UIImage(named: "loading.png"))
return view // Simple example. You could easily use your own UIView subclass
}
return nil // Don''t call super, return nil when you don''t want to override anything...
}
// Use either this or viewForElement for a given element, not both
override func viewControllerForElement(element: TVViewElement, existingViewController: UIViewController?) -> UIViewController? {
if (element.elementName == "whatever") {
let whateverStoryboard = UIStoryboard(name: "Whatever", bundle: nil)
let viewController = whateverStoryboard.instantiateInitialViewController()
return viewController
}
return nil
}
// Use this to return a valid asset URL for resource:// links for badge/img src (not necessary if the referenced file is included in your bundle)
// I believe you could use this to cache online resources (by replacing resource:// with http(s):// if a corresponding file doesn''t exist (then starting an async download/save of the resource before returning the modified URL). Just return a file url for the version on disk if you''ve already cached it.
override func URLForResource(resourceName: String) -> NSURL? {
return nil
}
}
Desafortunadamente, view / viewControllerForElement: no será llamado para todos los elementos. Algunos de los elementos existentes (como las vistas de colección) manejarán la representación de sus propios elementos secundarios, sin involucrar a su fábrica de interfaces, lo que significa que tendrá que anular un elemento de nivel superior, o tal vez usar una categoría / swizzling o UIAppearance para obtener El efecto que quieras.
Finalmente, como acabo de dar a entender, puede usar UIAppearance para cambiar el aspecto de ciertas vistas integradas. Esta es la forma más fácil de cambiar la apariencia de la barra de pestañas de su aplicación TVML, por ejemplo:
// in didFinishLaunching...
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().backgroundColor = UIColor(white: 0.5, alpha: 1.0)
Estoy explorando tvOS
y descubrí que Apple ofrece un buen conjunto de templates escritas con TVML
. Me gustaría saber si una aplicación de tvOS
que utiliza plantillas de TVML
también puede usar UIKit
.
¿Puedo mezclar UIKit y TVMLKit dentro de una aplicación?
Encontré un hilo en el Foro de desarrolladores de Apple, pero no responde completamente a esta pregunta y estoy revisando la documentación para encontrar una respuesta.
Sí tu puedes. La visualización de plantillas TVML requiere que use un objeto que controle el contexto de JavaScript: TVApplicationController .
var appController: TVApplicationController?
Este objeto tiene una propiedad UINavigationController asociada a él. Así que cada vez que lo creas, puedes llamar:
let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)
Esto le permite empujar un controlador de vista UIKit personalizado en la pila de navegación. Si desea volver a las plantillas de TVML, simplemente saque el viewController de la pila de navegación.
Si lo que desea saber es cómo comunicarse entre JavaScript y Swift, aquí hay un método que crea una función javascript llamada pushMyView ()
func createPushMyView(){
//allows us to access the javascript context
appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//this is the block that will be called when javascript calls pushMyView()
let pushMyViewBlock : @convention(block) () -> Void = {
() -> Void in
//pushes a UIKit view controller onto the navigation stack
let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)
}
//this creates a function in the javascript context called "pushMyView".
//calling pushMyView() in javascript will call the block we created above.
evaluation.setObject(unsafeBitCast(pushMyViewBlock, AnyObject.self), forKeyedSubscript: "pushMyView")
}, completion: {(Bool) -> Void in
//done running the script
})
}
Una vez que llame a createPushMyView () en Swift, podrá llamar a pushMyView () en su código de javascript y empujará un controlador de vista a la pila.
SWIFT 4.1 ACTUALIZACIÓN
Solo unos pocos cambios simples a los nombres de los métodos y al casting:
appController?.evaluate(inJavaScriptContext: {(evaluation: JSContext) -> Void in
y
evaluation.setObject(unsafeBitCast(pushMyViewBlock, to: AnyObject.self), forKeyedSubscript: "pushMyView" as NSString)
Sí. Vea el Marco de TVMLKit , cuyos documentos comienzan con:
El marco de TVMLKit le permite incorporar archivos JavaScript y TVML en sus aplicaciones binarias para crear aplicaciones cliente-servidor.
Desde un vistazo rápido a esos documentos, parece que utilizas las distintas clases de TVWhateverFactory
para crear vistas de UIKit o ver controladores de TVML, después de lo cual puedes insertarlos en una aplicación de UIKit.
Si ya tiene una aplicación UIKit nativa para tvOS, pero le gustaría extenderla utilizando TVMLKit para alguna parte de ella, puede hacerlo.
Use TVMLKit como una sub aplicación en su aplicación nativa de tvOS. La siguiente aplicación muestra cómo hacerlo, al retener el TVApplicationController
y presentar el TVApplicationController
navigationController
desde el TVApplicationController
. TVApplicationControllerContext
se utiliza para transferir datos a la aplicación JavaScript, ya que la url se transfiere aquí:
class ViewController: UIViewController, TVApplicationControllerDelegate {
// Retain the applicationController
var appController:TVApplicationController?
static let tvBaseURL = "http://localhost:9001/"
static let tvBootURL = "/(ViewController.tvBaseURL)/application.js"
@IBAction func buttonPressed(_ sender: UIButton) {
print("button")
// Use TVMLKit to handle interface
// Get the JS context and send it the url to use in the JS app
let hostedContContext = TVApplicationControllerContext()
if let url = URL(string: ViewController.tvBootURL) {
hostedContContext.javaScriptApplicationURL = url
}
// Save an instance to a new Sub application, the controller already knows what window we are running so pass nil
appController = TVApplicationController(context: hostedContContext, window: nil, delegate: self)
// Get the navigationController of the Sub App and present it
let navc = appController!.navigationController
present(navc, animated: true, completion: nil)
}