TVML is a good choice, when you prefer simplicity over dynamic UIKit implementation. TVMLKitchen helps to manage your TVML with or without additional client-server. Put TVML templates in Main Bundle, then you're ready to go.
Loading a TVML view is in this short.
Kitchen.serve(xmlFile:"Catalog.xml")
Kitchen automatically looks for the xmlFile in your Main Bundle, parse it, then finally pushes it to navigationController.
-
Put your Sample.xml to your app's main bundle.
-
Prepare your Kitchen in AppDelegate's
didFinishLaunchingWithOptions:.let cookbook = Cookbook(launchOptions: launchOptions) Kitchen.prepare(cookbook) -
Launch the template from anywhere.
Kitchen.serve(xmlFile: "Sample.xml")
Got TVML server ? Just pass the URL String and you're good to go.
Kitchen.serve(urlString: "https://raw.githubusercontent.com/toshi0383/TVMLKitchen/master/SampleRecipe/Catalog.xml")
Set URL to template attributes of focusable element. Kitchen will send asynchronous request and present TVML. You can specify preferred presentationType too. Note that if actionID present, these attributes are ignored.
<lockup
template="https://raw.githubusercontent.com/toshi0383/TVMLKitchen/master/SampleRecipe/Oneup.xml"
presentationType="Modal"
>
There are currently three presentation styles that can be used when serving views: Default, Modal and Tab. The default style acts as a "Push" and will change the current view. Modal will overlay the new view atop the existing view and is commonly used for alerts. Tab is only to be used when defining the first view in a tabcontroller.
Kitchen.serve(xmlFile: "Sample.xml")
Kitchen.serve(xmlFile: "Sample.xml", type: .Default)
Kitchen.serve(xmlFile: "Sample.xml", type: .Modal)
Kitchen.serve(xmlFile: "Sample.xml", type: .Tab)Should you wish to use tabs within your application you can use KitchenTabBar recipe. First, create a TabItem struct with a title and a handler method. The handler method will be called every time the tab becomes active.
Note: The PresentationType for initial view should always be set to .Tab.
struct MoviesTab: TabItem {
let title = "Movies"
func handler() {
Kitchen.serve(xmlFile: "Sample.xml", type: .Tab)
}
}Present tabbar using serve(recipe:) method.
let tabbar = KitchenTabBar(items:[
MoviesTab(),
MusicsTab()
])
Kitchen.serve(recipe: tabbar)cookbook.onError = { error in
let title = "Error Launching Application"
let message = error.localizedDescription
let alertController = UIAlertController(title: title, message: message, preferredStyle:.Alert )
Kitchen.navigationController.presentViewController(alertController, animated: true) { }
}
cookbook.evaluateAppJavaScriptInContext = {appController, jsContext in
/// set Exception handler
/// called on JS error
jsContext.exceptionHandler = {context, value in
debugPrint(context)
debugPrint(value)
assertionFailure("You got JS error. Check your javascript code.")
}
/// - SeeAlso: http://nshipster.com/javascriptcore/
/// Inject native code block named 'debug'.
let consoleLog: @convention(block) String -> Void = { message in
print(message)
}
jsContext.setObject(unsafeBitCast(consoleLog, AnyObject.self),
forKeyedSubscript: "debug")
}
You can set actionID and playActionID attributes in your focusable elements. (e.g. lockup or button SeeAlso: https://forums.developer.apple.com/thread/17704 ) Kitchen receives Select or Play events, then fires actionIDHandler or playActionHandler if exists.
<lockup actionID="showDescription" playActionID="playContent">
cookbook.actionIDHandler = { actionID in
print(actionID)
}
cookbook.playActionIDHandler = {actionID in
print(actionID)
}
Handlers are currently globally shared. This is just an idea, but we can pass parameters via actions like this,
actionID="showHogeView,12345678,hogehoge"
then parse it in Swift side.
actionID.componentsSeparatedByString(",")
You can set httpHeaders and responseObjectHandler to Cookbook configuration object. So for example you can manage custom Cookies.
cookbook.httpHeaders = [
"Cookie": "Hello;"
]
cookbook.responseObjectHandler = { response in
/// Save cookies
if let fields = response.allHeaderFields as? [String: String],
let url = response.URL
{
let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(fields, forURL: url)
for c in cookies {
NSHTTPCookieStorage.sharedCookieStorageForGroupContainerIdentifier(
"group.jp.toshi0383.tvmlkitchen.samplerecipe").setCookie(c)
}
}
return true
}
Though TVML is static xmls, we can generate TVML dynamically by defining Recipe. Built-in Recipes are still middle of the way. You can send PRs!
let alert = AlertRecipe(
title: Sample.title,
description: Sample.description)
)
Kitchen.serve(recipe: alert)
SearchRecipe supports dynamic view manipulation.
Subclass SearchRecipe and override filterSearchText method.
SeeAlso: SampleRecipe/MySearchRecipe.swift, SearchResult.xml
Use PresentationType.TabSearch. This will create keyboard observer in addition to .Tab behavior.
struct SearchTab: TabItem {
let title = "Search"
func handler() {
let search = MySearchRecipe(type: .TabSearch)
Kitchen.serve(recipe: search)
}
}
- Catalog
- Catalog with select action handler
- Alert with button handler
- Descriptive Alert with button handler
- Search
- Rating with handler
- Compilation with select action handler
- Product with select action handler
- Product Bundle with select action handler
- Stack with select action handler
- Stack Room with select action handler
- Stack Separator with select action handler
and more...
We don't support dynamic view reloading in most cases. For now, if you need 100% dynamic behavior, go ahead and use UIKit.
Put this to your Cartfile,
github "toshi0383/TVMLKitchen"
Follow the instruction in carthage's Getting Started section.
Add the following to your Podfile
pod 'TVMLKitchen'
For implementation details, my slide is available.
TVML + Native = Hybrid
Any contribution is welcomed🎉