Jak wspomniano w zaakceptowanej odpowiedzi, możesz wywołać prawie dowolną funkcję Swift z poziomu kontekstu JavaScript. Zauważ, że jak sama nazwa wskazuje, setObject:forKeyedSubscript:
będzie akceptować obiekty (jeśli są zgodne z protokołem dziedziczącym z JSExport) oprócz bloków, umożliwiając dostęp do metod i właściwości tego obiektu.Oto przykład
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]
}
}
Następnie w kontrolerze aplikacji:
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let bridge:JSBridge = JSBridge();
jsContext.setObject(bridge, forKeyedSubscript:"bridge");
}
Następnie można to zrobić w swoim JS: bridge.setValue(['foo', 'bar'], "baz")
Nie tylko to, ale można zastąpić widoki dla istniejących elementów, lub zdefiniuj niestandardowe elementy, które będą używane w znacznikach, i twórz kopie z natywnymi widokami:
// 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")
Szybkie przykładem niestandardowego elementu:
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)?) {
//...
}
}
A oto jak założyć fabrykę niestandardowego interfejsu:
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
}
}
Niestety, widok/viewControllerForElement: nie będzie wzywał do wszystkich elementów. Niektóre z istniejących elementów (np. Widoki kolekcji) będą obsługiwać renderowanie ich elementów potomnych, bez angażowania fabryki interfejsu, co oznacza, że będziesz musiał zastąpić element wyższego poziomu, lub może użyć kategorii/swizzling lub UIAppearance, aby uzyskać efekt, który chcesz.
Wreszcie, jak właśnie sugerowałem, możesz użyć UIAppearance, aby zmienić wygląd niektórych wbudowanych widoków. Oto najprostszy sposób na zmianę wyglądu TVML aplikacji zakładki paska, na przykład:
// in didFinishLaunching...
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().backgroundColor = UIColor(white: 0.5, alpha: 1.0)
Fantastycznie! To odpowiadało na więcej pytań niż pytanie, na które się zwrócono, takie jak interakcje pomiędzy JS i Swift w tym kontekście. Wygląda na to, że wszystkie funkcje javascript (takie jak ten przykład) powinny zostać wstrzyknięte przed wywołaniem pliku application.js. Targi? –
@FrankC. Cieszę się, że ci pomogło! Tak, ponieważ ta metoda zasadniczo wstrzykuje javascript do kontekstu, musisz zadzwonić do createPushMyView() kiedyś zanim zadzwonisz pushMyView(). Jeśli spróbujesz wywołać funkcję pushMyView() w JS bez wywoływania funkcji createPushMyView() w Swift, będziesz wywoływał niezdefiniowany obiekt i nie zadziała. – shirefriendship
@shirefriendship To jest świetna odpowiedź, wielkie dzięki. Chcę tylko podkreślić, że ta funkcja wypychania jest tylko przykładem tego, co wyjaśniłeś, że takie funkcje tej natury powinny być już wcześniej utworzone na stronie zaplecza JS. Jak wynika z innych przykładów i samouczków. – Placeable