AirPlay для Apple TV в полноэкранном режиме

В настоящее время любое приложение, которое я делаю для iPhone/iPad, можно отразить на Apple TV через AirPlay. Однако даже в ландшафтном режиме он занимает только центральную часть экрана с черным цветом слева и справа. Что вообще нужно для того, чтобы перевести его в полноэкранный режим AirPlay, как это сделали такие приложения, как Real Racing HD?

РЕДАКТИРОВАТЬ: В соответствии с предложением я добавил весь код, который я использую, и вместо того, чтобы указывать второму окну использовать тот же контроллер корневого представления, что и обычно, я настроил новый VC с другим цветом, чтобы увидеть, правильно ли настроена механика. . Это НЕ так, поскольку он по-прежнему выполняет обычное зеркалирование, даже если ему говорят использовать другой VC.

Вот 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

И соответствующие части 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;
}

person user717452    schedule 02.06.2015    source источник
comment
ОС должна думать, что ваше приложение все еще находится в портретном режиме.   -  person StilesCrisis    schedule 03.06.2015
comment
@StilesCrisis нет, это поведение любого приложения, независимо от того, находится ли оно в альбомной ориентации, оно будет отображать масштабированный вид по центру. Я знаю, что на Apple TV можно сделать полноэкранный режим, но не знаю, как это сделать.   -  person user717452    schedule 03.06.2015


Ответы (4)


Что вообще нужно для того, чтобы перевести его в полноэкранный режим AirPlay...

Если вы хотите погрузиться в суть, вот где вы можете начать изучать Windows и экраны в мире iOS: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/WindowAndScreenGuide/UsingExternalDisplay/UsingExternalDisplay.html

Если вы хотите сначала получить более широкий обзор, вот страница агрегирования с видео и другими учебными пособиями/документацией:

https://developer.apple.com/airplay/

[EDIT] Вот фрагмент кода из моего приложения, в котором я настраиваю второй дисплей. Я взял большую часть этого из ссылок, которые я дал вам. ПРИМЕЧАНИЕ. «Основной» — для устройства, а «удаленный» — для телевизора.

ПРИМЕЧАНИЕ. Этот код не является полным; есть еще изменения состояния, на которые он не реагирует. Перед запуском подключитесь к приемнику AirPlay и включите зеркалирование.

У меня есть это:

@interface AppDelegate () {
    SFCManagerMainViewController *_mainVC;
    SFCRemoteMonitorViewController *_remoteVC;
}

@end

Заголовок:

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIWindow *secondWindow;

и это:

- (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;
    }
}
person Brad Brighton    schedule 02.06.2015
comment
Я точно следовал инструкциям из первой ссылки и получил пустой экран на своем Apple TV. - person user717452; 22.06.2015
comment
Это часть того, что я ожидал. В первой ссылке вы получаете дополнительный экран (похожий на второй монитор, а не дублирование существующего монитора), на который вы бы поместили содержимое, размер которого можно изменить до полной ширины/высоты. Если ваш интерфейс использует классы размеров/автоматический макет, вы сможете добавить свой контроллер представления на второй экран и увидеть больше того, что вы ожидаете. - person Brad Brighton; 22.06.2015
comment
Итак, следуя инструкциям Apple и добавляя ТОЧНО код, который они указали в ссылке, как мне затем ДУБЛИРОВАТЬ то, что находится на экране моего iPad, на второй экран? - person user717452; 22.06.2015
comment
Взгляните на редактирование ответа, которое я сделал, чтобы включить код. - person Brad Brighton; 22.06.2015
comment
Итак, если я хочу точно отразить отображение, а мой главный экран в didFinishLaunching выглядит как [window setRootViewController:tabBarController];, то не должен ли я сделать другой код [self.secondWindow.rootViewController = tabBarController];? Я попробовал это и получил пустой экран на Apple TV. - person user717452; 22.06.2015
comment
Я получаю 2015-06-21 16:57:18.741 PriceIsRightVBS[1692:444063] Initializing secondWindow/screen in notification в своей консоли, когда включаю зеркалирование AirPlay, но экран остается прежним, с полосами слева и справа. - person user717452; 22.06.2015
comment
В качестве общего утверждения я бы сказал, что да, вы можете это сделать. В качестве конкретного утверждения, я думаю, вам, вероятно, пора отредактировать свой вопрос, чтобы включить фактический код того, как вы реализуете, чтобы люди могли обнаружить упущения и т. д. Еще одна вещь, которую я настоятельно рекомендую, это чтобы вы взяли этот код мой (или похожий образец), поместите другой VC (даже если это просто цветной фон) на телевизор, чтобы убедиться, что механика правильная, а затем сосредоточьтесь на дублировании контента. - person Brad Brighton; 22.06.2015
comment
Давайте продолжим обсуждение в чате. - person user717452; 22.06.2015

Вам просто нужно установить компенсацию оверскана:

UIScreen* secondScreen = (...);
secondScreen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;

Если вы хотите, чтобы полный код протестировал его, просто поместите следующий код где-нибудь в свой AppDelegate.m и на application:didFinishLaunchingWithOptions: вызовите [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;
}

У меня не было разрешения 1080p на моем Apple TV 3-го поколения во время тестирования с iPad Mini 2, но я получил разрешение 720p, увеличенное до полноэкранного режима. Я не уверен, что это то, чего ожидает вся концепция AirPlay, или что-то здесь недостаточно быстрое, но я хотел бы получить 1080p.

Дайте мне знать, если вам нужна дополнительная помощь по настройке AirPlay, но он должен работать как есть.

person B.R.W.    schedule 21.06.2015
comment
Это работает для добавления другого представления для отображения на устройстве AirPlay, но я просто хочу отразить то, что находится в моем приложении, на Apple TV. - person user717452; 21.06.2015
comment
Если я вообще не добавляю код, он отражает Apple TV, но с полосами слева и справа. Если я добавляю код, я получаю пустой экран. - person user717452; 22.06.2015
comment
О, я вижу. Пробовали ли вы установить компенсацию оверскана на главном экране? (я сейчас не дома, проверить не могу) - person B.R.W.; 22.06.2015

Во-первых, возможно ли, что сам телевизор переопределяет некоторые настройки оверскана?

Есть несколько API для UIScreen, которые вы можете проверить, в частности:

@property(nonatomic) UIScreenOverscanCompensation overscanCompensation 
@property(nonatomic,retain) UIScreenMode *currentMode
@property(nonatomic,readonly,copy) NSArray *availableModes

В противном случае другой вариант — отключить зеркальное отображение и сделать свой рисунок в виде, который соответствует размеру экрана:

//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;
person Max Chuquimia    schedule 21.06.2015
comment
Если я вообще не добавляю код, он отражает Apple TV, но с полосами слева и справа. Если я добавляю код, я получаю пустой экран. - person user717452; 22.06.2015

У меня это работает как на старом, так и на новом Apple TV с iPad Air. Второе окно отображается в полноэкранном режиме на телевизоре, в то время как ваш обычный пользовательский интерфейс отображается на iPad.

Вставьте в свой 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];
}

Код, вдохновленный: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/WindowAndScreenGuide/UsingExternalDisplay/UsingExternalDisplay.html

person Tomasz    schedule 14.03.2016