Как ограничить авторотацию одной ориентацией для некоторых видов, позволяя при этом все ориентации на других?

Этот вопрос касается вращения устройства iOS и нескольких контролируемых представлений в UINavigationController. Некоторые взгляды должны быть ограничены ориентацией на портрет, а некоторые должны автоматически открываться. Если вы попытаетесь создать простейшую настройку с тремя видами, вы заметите, что поведение авторотации имеет несколько очень неприятных причуд. Сценарий, однако, очень прост, поэтому я думаю, что я либо не выполняю реализацию авторотации правильно, либо забываю что-то.

У меня есть очень базовое демонстрационное приложение, которое показывает странность, и я сделал видео, показывающее его в действии.

Настройка очень простая: три контроллера вида, называемые FirstViewController, SecondViewController и ThirdViewController, расширяют AbstractViewController, который показывает метку с именем класса и возвращает YES для shouldAutorotateToInterfaceOrientation:, когда устройство находится в портретной ориентации. SecondViewController переопределяет этот метод, чтобы разрешить все вращения. Все три конкретных класса добавляют несколько цветных квадратов, чтобы можно было перемещаться между представлениями, нажимая и выталкивая контроллеры в положение UINavigationController. Я бы сказал, что это очень простой сценарий.

Если вы держите устройство в портретной или альбомной ориентации, это результат, который я бы не только хотел достичь, но и ожидал. На первом изображении вы видите, что все представления являются "вертикальными", а во втором вы видите, что только второй контроллер просмотра вращает ориентацию устройства. Чтобы быть ясным, должно быть возможно перемещаться со второго представления в ландшафтном режиме на третье, но поскольку эта третья только поддерживает портретную ориентацию, ее следует показывать только в портретной ориентации. Самый простой способ убедиться, что результаты в порядке, - это посмотреть на позицию несущей.

Expected view orientation for the device in portrait modeExpected view orientation for the device in landscape mode

Но этот вопрос здесь, потому что фактический результат совершенно другой. В зависимости от того, с каким видом вы находитесь, когда вы вращаете устройство, и в зависимости от того, какой вид вы переходите к следующему, представления не будут вращаться (чтобы быть конкретным, метод didOrientFromInterfaceOrientation: никогда не вызывается). Если вы находитесь в ландшафте на втором и переходите к третьему, он будет иметь ту же ориентацию, что и второй (= плохой). Однако, если вы переходите со второго назад на первое, экран будет вращаться в режиме принудительного портрета, а панель оператора будет находиться на физическом верхнем углу устройства, независимо от того, как вы держите его. Видео показывает это более подробно.

Actual view orientation for the device in landscape mode

Мой вопрос двоякий:

  • Почему первый контроллер просмотра вращается назад, но не третий?
  • Что нужно сделать, чтобы получить правильное поведение из ваших представлений, когда вы хотите, чтобы некоторые виды автоматически открывались, но не другие?

Cheers, ЕР.

РЕДАКТИРОВАТЬ: В качестве крайней меры, прежде чем положить на нее щедрость, я полностью переписал этот вопрос, чтобы быть короче, яснее и, надеюсь, более привлекательным, чтобы дать ответ.

Ответ 1

Короткий ответ заключается в том, что вы используете UINavigationController, и это не будет работать так, как вы этого хотите. Из документов Apple:

Почему мой UIViewController не будет вращаться вместе с устройством?

Все дочерние контроллеры в вашем UITabBarController или UINavigationController не согласен общий набор ориентации.

Чтобы убедиться, что все ваши дочерние элементы контроллеры вращаются правильно, вы должны воплощать в жизнь shouldAutorotateToInterfaceOrientation для каждого контроллера представления, представляющего каждой вкладке или уровне навигации. каждый должны согласиться с той же ориентацией для которые вращаются. То есть, они все должны вернуть YES для того же положения ориентации.

Подробнее о смотрите здесь вопросы о ротации.

Вам нужно будет свернуть собственный контроль стека/контроллера стека за то, что вы хотите сделать.

Ответ 2

Сделайте bolean в делетете приложения, чтобы контролировать, какую ориентацию вы хотите, например, сделать bool, чтобы включить Portrait и в вашем контроллере просмотра, который вы хотите разрешить Portrait, разрешить его совместно используемым приложением

в вашем контроллере просмотра, где вы хотите включить или отключить любую ориентацию, которую вы хотите.

((APPNAMEAppDelegate *)[[UIApplication sharedApplication] delegate]).enablePortrait= NO;

в App Delegate.

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    NSLog(@"Interface orientations");
    if(!enablePortrait)
        return UIInterfaceOrientationMaskLandscape;
    return UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskPortrait;
}

Этот метод будет запускаться каждый раз, когда вы поворачиваете устройство. На основе этих BOOL разрешите ориентацию, которую вы хотите.

Ответ 3

Был аналогичный вопрос несколько лет назад с рядом ответов. Вот недавний ответ от кого-то на этот вопрос:
Есть ли документальный способ установки ориентации iPhone?

Из того, что я понимаю, это проблема, которую испытывают многие люди, и в основном хаки - единственный способ ее исправить. Просмотрите эту тему, если вы ее раньше не видели и видите, работает ли что-нибудь для вас.

На стороне примечания, у меня была аналогичная проблема некоторое время назад, когда я вызывал что-то в shouldAutorotate, и я добавил код viewWillAppear, чтобы попытаться его исправить. Я честно не помню, если это сработало, и у меня нет Mac больше, чтобы попробовать, но я нашел код, и я буду вставлять его здесь, если он дает какое-то вдохновение.

- (void)viewWillAppear:(BOOL)animated{
  UIInterfaceOrientation o;
  switch ([UIDevice currentDevice].orientation) {
    case UIDeviceOrientationPortrait:
        o = UIInterfaceOrientationPortrait;
        break;
    case UIDeviceOrientationLandscapeLeft:
        o = UIInterfaceOrientationLandscapeLeft;
        break;
    case UIDeviceOrientationLandscapeRight:
        o = UIInterfaceOrientationLandscapeRight;
        break;
    default:
        break;
  }

  [self shouldAutorotateToInterfaceOrientation:o];
}

Ответ 4

НЕ ИСПОЛЬЗУЙТЕ ЭТОТ ХАК, ЯБЛОК ОТКЛЮЧАЕТ ПРИЛОЖЕНИЕ, ОСНОВАННОЕ НА ИСПОЛЬЗОВАНИИ "ЧАСТНОГО API"

Для справки, я оставлю свой ответ здесь, но использование частного API не пройдет мимо обзорной доски. Сегодня я чему-то научился: D Как @younce правильно цитировал документы Apple, чего я не могу добиться с помощью UINavigationController.

У меня было два варианта. Во-первых, я мог бы написать свой собственный контроллер навигационного контроллера со всеми ужасами, с которыми он столкнулся при этом. Во-вторых, я мог бы взломать поворот в контроллерах представления, используя недокументированную функцию UIDevice под названием setOrientation:animated:

Потому что второй был соблазнительно легким, я пошел за этим. Это то, что я сделал. Вам понадобится категория для подавления предупреждений компилятора о том, что сеттер не существует:

@interface UIDevice (UndocumentedFeatures) 
-(void)setOrientation:(UIInterfaceOrientation)orientation animated:(BOOL)animated;
-(void)setOrientation:(UIInterfaceOrientation)orientation;
@end

Затем вам нужно проверить поддерживаемые ориентации на viewWillAppear:. Рядом с используемыми здесь методами UIDevice вы можете также заставить портретную ориентацию представить модальный контроллер, но это произойдет мгновенно и не анимировано, так что это мой предпочтительный способ:

-(void)viewWillAppear:(BOOL)animated {
    UIDevice *device = [UIDevice currentDevice];
    UIDeviceOrientation realOrientation = device.orientation;

    if ([self shouldAutorotateToInterfaceOrientation:realOrientation]) {
        if (realOrientation != [UIApplication sharedApplication].statusBarOrientation) {

            // Resetting the orientation will trigger the application to rotate
            if ([device respondsToSelector:@selector(setOrientation:animated:)]) {
                [device setOrientation:realOrientation animated:animated];
            } else {
                // Yes if Apple changes the implementation of this undocumented setter,
                // we're back to square one.
            }
        }
    } else if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortrait]) {
        if ([device respondsToSelector:@selector(setOrientation:animated:)]) {

            // Then set the desired orientation
            [device setOrientation:UIDeviceOrientationPortrait animated:animated];

            // And set the real orientation back, we don't want to truly mess with the iPhone balance system.
            // Because the view does not rotate on this orientation, it won't influence the app visually.
            [device setOrientation:realOrientation animated:animated];
        }
    }
}

Фокус в том, чтобы всегда поддерживать ориентацию внутреннего устройства в "реальной" ориентации устройства. Если вы начнете изменять это, вращение вашего приложения будет не в балансе.

Но, как я знаю сейчас, это просто верный способ отклонить ваше приложение. Поэтому вариант номер два - это просто плохой вариант. Перепишите, что NavigationController, или просто все ваши представления поддерживают один и тот же набор ориентации.

Cheers, EP.

Ответ 5

В iOS 6 это стало очень простой проблемой. Просто создайте специальный класс для просмотров, которые вы хотите авторотировать. Затем, в вашем rootVC, добавьте это.

-(BOOL)shouldAutorotate{
       BOOL should = NO;

       NSLog(@"%@", [self.viewControllers[self.viewControllers.count-1] class]);
       if ([self.viewControllers[self.viewControllers.count-1] isKindOfClass:[YourAutorotatingClass class]]) {
             should = YES;
       }


       return should;
}

Я знаю, что это старый вопрос, но я подумал, что стоит упомянуть.