Reputation: 111
Currently, any app that I make for iPhone/iPad can be mirrored to the Apple TV via AirPlay. However, even in landscape mode, it only takes up the center part of the screen, with black on left and right sides. What all is involved with getting it to AirPlay full-screen, in the way that apps like Real Racing HD have done?
EDIT: Per a suggestion, I have added in all the code I am using, and instead of telling the secondWindow to use same root view controller as normal, I set up a new VC with a different color to see if mechanics were set up right. They are NOT, as it still just does the normal mirroring, even when telling it to use a different VC.
Here is AppDelegate.h
#import <UIKit/UIKit.h>
@class MainView;
@class ViewController;
@interface AppDelegate : UIResponder <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *tabBarController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *tabBarController;
@property (nonatomic, retain) UIWindow *secondWindow;
@end
And relevant parts of AppDelegate.m
- (void)checkForExistingScreenAndInitializeIfPresent {
if ([[UIScreen screens] count] > 1) {
// Get the screen object that represents the external display.
UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];
// Get the screen's bounds so that you can create a window of the correct size.
CGRect screenBounds = secondScreen.bounds;
self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.secondWindow.screen = secondScreen;
// Set up initial content to display...
NSLog(@"Setting up second screen: %@", secondScreen);
ViewController *mainView = [[ViewController alloc] init];
self.secondWindow.rootViewController = mainView;
[self.secondWindow makeKeyAndVisible];
// Show the window.
// self.secondWindow.hidden = NO;
}
NSLog(@"Screen count too low");
}
- (void)setUpScreenConnectionNotificationHandlers {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleScreenDidConnectNotification:)
name:UIScreenDidConnectNotification object:nil];
[center addObserver:self selector:@selector(handleScreenDidDisconnectNotification:)
name:UIScreenDidDisconnectNotification object:nil];
}
- (void)handleScreenDidConnectNotification:(NSNotification*)aNotification {
UIScreen *newScreen = [aNotification object];
CGRect screenBounds = newScreen.bounds;
if (!self.secondWindow) {
NSLog(@"Initializing secondWindow/screen in notification");
self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.secondWindow.screen = newScreen;
// Set the initial UI for the window.
ViewController *mainView = [[ViewController alloc] init];
self.secondWindow.rootViewController = mainView;
} else {
NSLog(@"Second window already initialized.");
}
}
- (void)handleScreenDidDisconnectNotification:(NSNotification*)aNotification {
if (self.secondWindow) {
// Hide and then delete the window.
self.secondWindow.hidden = YES;
self.secondWindow = nil;
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window setRootViewController:tabBarController];
[self setUpScreenConnectionNotificationHandlers];
[self checkForExistingScreenAndInitializeIfPresent];
return YES;
}
Upvotes: 5
Views: 3610
Reputation: 3466
This works for me on both old and the new Apple TV with an iPad Air. The second window is displayed full screen on the TV while your normal UI is displayed on the iPad.
Insert in your AppDelegate.m:
@property UIWindow *secondWindow;
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([UIScreen screens].count > 1) {
[self setUpSecondWindowForScreen:[UIScreen screens][1]];
}
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleScreenDidConnectNotification:)
name:UIScreenDidConnectNotification object:nil];
[center addObserver:self selector:@selector(handleScreenDidDisconnectNotification:)
name:UIScreenDidDisconnectNotification object:nil];
return YES;
}
- (void)handleScreenDidConnectNotification:(NSNotification*)notification {
if (!self.secondWindow) {
[self setUpSecondWindowForScreen:[notification object]];
}
}
- (void)handleScreenDidDisconnectNotification:(NSNotification*)notification {
if (self.secondWindow) {
self.secondWindow = nil;
}
}
- (void)setUpSecondWindowForScreen:(UIScreen*)screen {
self.secondWindow = [[UIWindow alloc] init];
self.secondWindow.screen = screen;
self.secondWindow.screen.overscanCompensation = UIScreenOverscanCompensationNone;
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [UIColor redColor];
self.secondWindow.rootViewController = viewController;
[self.secondWindow makeKeyAndVisible];
}
Code inspired on: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/WindowAndScreenGuide/UsingExternalDisplay/UsingExternalDisplay.html
Upvotes: 0
Reputation: 2184
What all is involved with getting it to AirPlay full-screen...
If you want to dive right in, here is where you start learning about Windows and Screens in the iOS world: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/WindowAndScreenGuide/UsingExternalDisplay/UsingExternalDisplay.html
If you want to take a broader overview first, here is the aggregation page with videos and other tutorials/documentation:
https://developer.apple.com/airplay/
[EDIT] Here's an excerpt of code from an app of mine where I'm setting up the second display. I've taken much of this from the links I gave you. NOTE: "main" is the for the device and "remote" is for the TV.
NOTE: This code is not production-complete; there are still state changes it does not respond to. Connect to an AirPlay Receiver and turn on Mirroring before running this.
I have this:
@interface AppDelegate () {
SFCManagerMainViewController *_mainVC;
SFCRemoteMonitorViewController *_remoteVC;
}
@end
The header:
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIWindow *secondWindow;
and this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setUpScreenConnectionNotificationHandlers];
[self checkForExistingScreenAndInitializeIfPresent];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
_mainVC = [[SFCManagerMainViewController alloc] initWithNibName:nil bundle:nil];
self.window.rootViewController = _mainVC;
[self.window makeKeyAndVisible];
return YES;
}
- (void)checkForExistingScreenAndInitializeIfPresent {
if ([[UIScreen screens] count] > 1) {
// Get the screen object that represents the external display.
UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];
// Get the screen's bounds so that you can create a window of the correct size.
CGRect screenBounds = secondScreen.bounds;
self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.secondWindow.screen = secondScreen;
// Set up initial content to display...
NSLog(@"Setting up second screen: %@", secondScreen);
_remoteVC = [[SFCRemoteMonitorViewController alloc] initWithNibName:nil bundle:nil];
self.secondWindow.rootViewController = _remoteVC;
[self.secondWindow makeKeyAndVisible];
// Show the window.
self.secondWindow.hidden = NO;
}
}
- (void)setUpScreenConnectionNotificationHandlers {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleScreenDidConnectNotification:)
name:UIScreenDidConnectNotification object:nil];
[center addObserver:self selector:@selector(handleScreenDidDisconnectNotification:)
name:UIScreenDidDisconnectNotification object:nil];
}
- (void)handleScreenDidConnectNotification:(NSNotification*)aNotification {
UIScreen *newScreen = [aNotification object];
CGRect screenBounds = newScreen.bounds;
if (!self.secondWindow) {
NSLog(@"Initializing secondWindow/screen in notification");
self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.secondWindow.screen = newScreen;
// Set the initial UI for the window.
_remoteVC = [[SFCRemoteMonitorViewController alloc] initWithNibName:nil bundle:nil];
self.secondWindow.rootViewController = _remoteVC;
} else {
NSLog(@"Second window already initialized.");
}
}
- (void)handleScreenDidDisconnectNotification:(NSNotification*)aNotification {
if (self.secondWindow) {
// Hide and then delete the window.
self.secondWindow.hidden = YES;
self.secondWindow = nil;
}
}
Upvotes: 3
Reputation: 1566
You just need to set the overscan compensation:
UIScreen* secondScreen = (...);
secondScreen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;
If you want full code to test it, just put the following code somewhere in your AppDelegate.m
and on application:didFinishLaunchingWithOptions:
call [self setupAirplay];
-(void)setupAirplay{
// self.windows is a NSMutableArray property on AppDelegate
self.windows = [[NSMutableArray alloc] init];
NSArray* screens = [UIScreen screens];
for (UIScreen *_screen in screens){
if (_screen == [UIScreen mainScreen])
continue;
NSNotification* notification = [[NSNotification alloc] initWithName:UIScreenDidConnectNotification object:_screen userInfo:nil];
[self screenDidConnect:notification];
}
// Register for notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(screenDidConnect:)
name:UIScreenDidConnectNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(screenDidDisconnect:)
name:UIScreenDidDisconnectNotification
object:nil];
}
- (UIWindow *) createWindowForScreen:(UIScreen *)screen {
UIWindow *aWindow = nil;
// Do we already have a window for this screen?
for (UIWindow *window in self.windows){
if (window.screen == screen){
aWindow = window;
}
}
// Still nil? Create a new one.
if (aWindow == nil){
aWindow = [[UIWindow alloc] initWithFrame:[screen bounds]];
[aWindow setScreen:screen];
[self.windows addObject:aWindow];
}
return aWindow;
}
- (void) screenDidConnect:(NSNotification *) notification {
UIScreen* screen = [notification object];
NSLog(@"Screen connected: %.0f x %.0f", screen.bounds.size.width, screen.bounds.size.height);
// Create a view controller for the new window (you should use a Storyboard / XIB)
UIViewController* airplay = [[UIViewController alloc] init];
airplay.view = [[UIView alloc] initWithFrame:screen.bounds];
[airplay.view setBackgroundColor:[UIColor whiteColor]];
UILabel* label = [[UILabel alloc] initWithFrame:screen.bounds];
[label setTextAlignment:NSTextAlignmentCenter];
[label setFont:[UIFont systemFontOfSize:40.0f]];
[label setText:@"AirPlay Screen"];
[airplay.view addSubview:label];
// Comment this line and you'll get the four black borders:
screen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;
UIWindow* aWindow = [self createWindowForScreen:screen];
// Add the view controller to the window
[aWindow setRootViewController:airplay];
[aWindow setHidden:NO];
}
- (void) screenDidDisconnect:(NSNotification *) notification {
NSLog(@"Screen disconnected");
UIScreen* screen = [notification object];
// Find any window attached to this screen, remove it from our window list
for (UIWindow *oneWindow in self.windows){
if (oneWindow.screen == screen){
NSUInteger windowIndex = [self.windows indexOfObject:oneWindow];
[self.windows removeObjectAtIndex:windowIndex];
}
}
return;
}
I didn't get 1080p resolution in my Apple TV 3rd generation while testing with my iPad Mini 2, but I got 720p scaled up to full screen. I'm not sure if that's what's expected by the whole AirPlay concept or if something here isn't fast enough, but I wish I could get 1080p.
Let me know if you need any further help with setting AirPlay up, but it should work as is.
Upvotes: 1
Reputation: 7824
Firstly, is it possible that the TV itself is overriding some overscan settings?
There are a few APIs for UIScreen
that you may want to check out, specifically:
@property(nonatomic) UIScreenOverscanCompensation overscanCompensation
@property(nonatomic,retain) UIScreenMode *currentMode
@property(nonatomic,readonly,copy) NSArray *availableModes
Otherwise the other option is to disable mirroring and do your drawing into a view that is the size of the screen:
//Be sure to check for the screen's existence first :)
UIScreen *secondScreen = [UIScreen screens][1];
//Create a window that is the size of the second screen
self.externalWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, secondScreen.bounds.size.width, secondScreen.bounds.size.height)];
//Set to a view controller that should be displayed
self.externalWindow.rootViewController = [[MyViewController alloc] init];
//Move the new window to the second screen
self.externalWindow.screen = secondScreen;
self.externalWindow.hidden = NO;
Upvotes: 0