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🎉