Используя UIScenes в iOS 13, как сделать зеркальное отображение экрана в AirPlay (по умолчанию используется внешний дисплей)
Если я компилирую на устройство iOS 12 (не использует UIScene) и AirPlay Mirror на свой Apple TV, приложение зеркально отображается на телевизоре, как и ожидалось.
На устройстве iOS 13 он, кажется, рассматривает его как внешний дисплей, формат которого соответствует размеру экрана (но у меня нет возможности управлять им).
Я бы предпочел старую функциональность - просто зеркальное отображение.
Как выполнить зеркалирование на iOS 13? Я копаюсь в документации для:
application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
И в UISceneConfiguration
есть role
собственность (имеет UISceneSession.Role.windowExternalDisplay
когда я пытаюсь использовать AirPlay Mirror), но, похоже, это не имеет никакого значения, например UISceneSession.Role.windowMirror
.
4 ответа
Я играл с зеркалированием и внешними дисплеями, и при правильной комбинации кода / настроек существуют различные возможности, но некоторые функции кажутся невозможными.
В iOS 13 (с приложением, созданным с использованием базового SDK iOS 13) вы можете настроить отображение своего приложения на внешнем дисплее. Но выполнение этой работы не позволяет вашему приложению отображать различный контент на внешнем дисплее. В основном ваше приложение только отражает или показывает только уникальную сцену для внешнего дисплея.
Если вы хотите, чтобы ваше приложение отражалось только в зеркале, убедитесь в следующем:
- Удалить
application(_:configurationForConnecting:options:)
из вашего делегата приложения. - Убедитесь, что в Info.plist нет записи для "Роль сеанса внешнего дисплея" в разделе "Конфигурация сцены" в "Манифесте сцены приложения".
Если ни одна из этих двух вещей не является частью вашего приложения, ваше приложение будет просто зеркально отображать любой внешний экран, когда вы активируете Screen Mirroring на устройстве iOS.
Просто столкнулся с этой проблемой сам. Мое решение действительно пришло из моегоUIWindowSceneDelegate
класс.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
// External displays should not get assigned a window. When a window isn't assigned, the default behavior is mirroring.
guard session.role != .windowExternalDisplay else { return }
/* the rest of your setup */
}
Когда вы не назначаете окно, кажется, что зеркальное отображение становится вариантом по умолчанию. До этого изменения мои внешние дисплеи (зеркальное отображение экрана) получали собственный уникальный экземпляр UIWindow.
Я нигде не вижу этого документированного, и это не интуитивно понятно. Из-за этого я немного боюсь, что в будущем он сломается.
Надеюсь, это все еще помогает.
Я обнаружил, что с реализацией Objective-C вы можете добиться зеркального отображения экрана, вернув nil
в application:configurationForConnectingSceneSession:options:
.
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
if (connectingSceneSession.role == UIWindowSceneSessionRoleExternalDisplay) {
return nil;
}
UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Main" sessionRole:connectingSceneSession.role];
configuration.storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
configuration.delegateClass = [SceneDelegate class];
configuration.sceneClass = [UIWindowScene class];
return configuration;
}
Имейте в виду, что этот способ не задокументирован и может сломаться в будущем.
Отредактировано: в Swift вы можете добиться этого с помощью метода swizzling:
@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
override init() {
_ = AppDelegate.performSceneConfigurationSwizzle
super.init()
}
private static let performSceneConfigurationSwizzle: Void = {
method_exchangeImplementations(
class_getInstanceMethod(AppDelegate.self, #selector(AppDelegate.application(_:configurationForConnecting:options:)))!,
class_getInstanceMethod(AppDelegate.self, #selector(AppDelegate.swizzle_application(_:configurationForConnecting:options:)))!
)
}()
@objc func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
fatalError("Should never reach.")
}
@objc private func swizzle_application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration? {
if connectingSceneSession.role == .windowExternalDisplay {
return nil
}
// build scene configuration as usual…
}
}
Вместо реализации метода настройки сцены AppDelegate в iOS 13:
@available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
configuration.delegateClass = SceneDelegate.self
return configuration
}
Вместо этого я переключился на использование варианта Info.plist (и удалил приведенный выше код), где вместо этого вы фактически указываете все вышеперечисленное в своем Info.plist. (Для получения последней версии того, что ожидается в файле Info.plist, просто создайте новый проект в Xcode и скопируйте содержимое из нового файла Info.plist дляApplication Scene Manifest
ключ).
Теперь он работает отлично, и AirPlay Mirror отражает, как и ожидалось. Я пытался изменитьrole
к windowApplication
как iOS, похоже, делает с вариантом Info.plist, но он все еще не работает.