1

I'm trying to build a tvOS app that contains both native and TVML content. Ideally, I'd like to present TVML content inside a container view; failing that, I'd be happy if I could present a native UIViewController modally over the TVML content. I have so far failed in both approaches.

The closest I've got so far is presenting modally, but my problem is that the modal view controller always appears blurred, behind the TVML content. The code for this is below (it was based on a snippet from Ray Wenderlich's site):-

// URL and context
NSString *baseURL = @"http://localhost:9001/";
NSString *bootURL = [NSString stringWithFormat:@"%@js/application.js", baseURL];
TVApplicationControllerContext *context = [[TVApplicationControllerContext alloc] init];
context.javaScriptApplicationURL = [NSURL URLWithString:bootURL];
context.launchOptions = @{@"BASEURL": baseURL};

// only seems to work with a full-screen window
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

// instantiate TVApplicationController
self.tvAppController = [[TVApplicationController alloc] initWithContext:context window:window delegate:self];

// get a simple test view controller from storyboard and present modally
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"testVC"];
[self.tvAppController.navigationController pushViewController:controller animated:YES];

There are a couple of things I don't understand here.

Firstly, I need to allocate a brand new, full-size window for this to work. Trying to use the existing self.view.window, or trying to use a smaller window both fail, i.e. no TVML is shown. So I'm assuming the new window is getting added into the view hierarchy at a pretty high level and getting in front of everything else? This seems pretty limited - am I missing something?

Secondly, Apple's docs suggest that the window can be omitted to give more control over how the TVML view will appear:-

If no window is provided, the navigation controller can be presented and dismissed manually within the binary app.

However, I can make little practical sense from that statement. If I set the window parameter to nil, the TVML content simply does not appear. How do they want me to "present" the read-only navigation controller? (I've tried embedding it in a container view but I get exceptions.)

I've tried just about everything I can think of here. Has anyone managed to get mixed TVML and tvOS native views working at the same time?

Echelon
  • 7,306
  • 1
  • 36
  • 34

2 Answers2

4

If you would like to show TVML over native views, create TVAppController without window and present it just like this.

// your viewController

- (IBAction)buttonAction:(id)sender {
    TVApplicationControllerContext *context = ...;
    self.tvAppController = [[TVApplicationController alloc] initWithContext:context window:nil delegate:self];

    [self presentViewController:self.tvAppController.navigationController animated:YES completion:nil];
}
bluedome
  • 2,449
  • 1
  • 22
  • 17
  • Not loading the http path link : Warning: Attempt to present <_TVAppNavigationController: 0x7ffe2380da00> on whose view is not in the window hierarchy! – Pankaj Bhardwaj Oct 27 '15 at 11:12
  • @bluedome Although your code is pushing TVML on top of a native view controller (rather than the other way around, as my code is), it actually helped a great deal. By delaying the creation of the `TVApplicationController` by doing it asynchronously rather than in `viewDidLoad`, I can now successfully push native controllers on top of TVML, which may help my solve my problem. Thanks! – Echelon Oct 27 '15 at 12:06
  • This solution resolved my issue where I was trying to present TVML templates over my native UIVIewController. – Audible Oct 27 '15 at 22:25
  • 1
    @Pankaj Bhardwaj call `presentViewController:animated:completion:` after `viewWillAppear:`, plz. – bluedome Oct 28 '15 at 04:05
0

When you pass self.window to [TVApplicationController alloc] initWithContext:context window:self.window delegate:self], TVApplicationController automatically sets it's navigationController as a rootViewController of provided self.window. This means that self.tvAppController.navigationController will be the initial controller of your app. But you are free to push controllers instantiated inside your xcode app to self.tvAppController.navigationController at any moment. However, the reason why your code presents TVApplicationController above UIViewController is because it takes more time for TVApplicationController to init, then it does for UIViewController, which results in an asynch problem. You can verify this with a simple dispatch_after. If you want a UIViewController to be an initial controller of your app, you will have to pass nil instead of self.window to initWithContext:window:delegate: and then present a TVApplicationController with [self.window.rootViewController presentViewController:self.appController.navigationController animated:YES completion:nil]; Of course, at this point, instead of presenting TVApplicationController modally you can do the same thing as initWithContext:window:delegate: does, which is [self.window setRootViewController:self.appController.navigationController]; and proceed pushing UIViewControllers to self.appController.navigationController, but it seems less flexible to me. Consider this example:

...
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
[self.window makeKeyAndVisible];

self.appController = [[TVApplicationController alloc] initWithContext:appControllerContext window:nil delegate:self];

UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"testVC"];

[self.window setRootViewController:viewController];
//Note that you don't have to init window and UIViewController if you have your storyboard set as Main in info.plist and the storyboard has an initial controller selected. In this case, you'll just need the following code:

[self.window.rootViewController presentViewController:self.appController.navigationController animated:YES completion:nil];
Alex Ozun
  • 66
  • 7
  • I don't follow what your code is trying to achieve. You are creating a new window and assigning it to `self.window`, then passing `nil` as the `window` argument to the `TVApplicationController` initialiser. My problem is not making my main view controller the root - as you say, I can do that by setting it to be the initial view controller in the storyboard, My problem is pushing different view controllers modally on top of the `TVApplicationController`'s navigation stack. Is your code is trying to solve that? – Echelon Oct 27 '15 at 12:03
  • My code implements the flow, where you have your native UIViewController as a root controller and then you modally present a `TVApplicationController` somewhere later in time. If you want a reversed scenario, you pass `self.window` to `TVApplicationController` and present native controllers with `[tvAppController.navigationController presentViewController:nativeViewController animated:YES completion:nil]`. But pay attention to that async problem, that I wrote about earlier. – Alex Ozun Oct 27 '15 at 23:55