iOS 6: Как ограничить одни виды портретной ориентацией и разрешить вращение других?

99

У меня есть приложение для iPhone, которое использует UINavigationControllerдля представления интерфейса детализации: сначала одно представление, затем другое, до четырех уровней в глубину. Я хочу, чтобы первые три вида были ограничены портретной ориентацией, и только последний вид должен иметь возможность вращаться в альбомную. При возврате из четвертого вида к третьему и четвертому виду в альбомной ориентации я хочу, чтобы все повернулось обратно в портретную ориентацию.

В iOS 5 я просто определил shouldAutorotateToInterfaceOrientation:в каждом из своих контроллеров представления, чтобы он возвращал YES для допустимых ориентаций. Все работало, как описано выше, включая возврат к портретной ориентации, даже если устройство удерживалось в альбомной ориентации при возврате из контроллера представления №4 в №3.

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

Больше ответственности переносится на приложение и его делегата. Теперь контейнеры iOS (такие как UINavigationController) не консультируются со своими дочерними элементами, чтобы определить, следует ли им выполнять авторотацию. [...] Система запрашивает самый верхний полноэкранный контроллер представления (обычно корневой контроллер представления) о поддерживаемых ориентациях интерфейса всякий раз, когда устройство вращается или когда контроллер представления представлен в полноэкранном модальном стиле представления. Более того, поддерживаемые ориентации извлекаются, только если этот контроллер представления возвращает YES из своего shouldAutorotateметода. [...] Система определяет, поддерживается ли ориентация, пересекая значение, возвращаемое методом приложения, supportedInterfaceOrientationsForWindow:со значением, возвращаемым supportedInterfaceOrientationsметодом самого верхнего полноэкранного контроллера.

Итак, я создал подклассы UINavigationController, присвоил своему MainNavigationControllerлогическому свойству landscapeOKи использовал его, чтобы вернуть допустимые ориентации supportedInterfaceOrientations. Затем в каждом из моих viewWillAppear:методов контроллеров представления у меня есть такая строка

    [(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

сказать мне MainNavigationControllerжелаемое поведение.

Возникает вопрос: если я сейчас перейду к своему четвертому виду в портретном режиме и переверну телефон, он повернется в альбомный. Теперь я нажимаю кнопку «Назад», чтобы вернуться к третьему виду, который должен работать только в портретной ориентации. Но не вращается назад. Как мне это сделать?

Я попытался

    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]

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

Харальд Бёгехольц
источник

Ответы:

95

У меня была такая же проблема, и я нашел решение, которое мне подходит. Для того, чтобы сделать его работу, он не достаточно для реализации - (NSUInteger)supportedInterfaceOrientationsв вашем UINavigationController. Вам также необходимо реализовать этот метод в вашем контроллере №3, который первым будет работать только в портретной ориентации после включения контроллера №4. Итак, у меня в UINavigationController есть следующий код:

- (BOOL)shouldAutorotate
{
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    if (self.isLandscapeOK) {
        // for iPhone, you could also return UIInterfaceOrientationMaskAllButUpsideDown
        return UIInterfaceOrientationMaskAll;
    }
    return UIInterfaceOrientationMaskPortrait;
}

В контроллере представления №3 добавьте следующее:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

Вам не нужно ничего добавлять к вашим контроллерам представления №1, №2 и №4. У меня это работает, надеюсь, вам поможет.

Фло
источник
8
вы решили то, что люди пытались решить месяцами! Вы заслуживаете за это большой признательности! Я добавил supportedInterfaceOrientations в категорию на UIViewController, и он отлично работает по умолчанию для этих контроллеров только для портретной ориентации.
dwery
3
Среди 100 разных ответов на SO это действительно работает. Спасибо :-)
Christer Nordvik
16
У меня есть эта настройка, но я все еще получаю неправильную ориентацию, когда возвращаюсь с портретной ориентации на контроллер просмотра, ограниченный только пейзажем. Он не автоповорачивается к ландшафту - кто-нибудь еще его видит?
Одед Бен Дов
1
Что делать, если желаемый контроллер ландшафтного представления представлен из перехода и нет контроллера навигации?
jesses.co.tt 02
1
Измените последний, чтобы он был NSUInteger как возвращаемый тип.
Абсолютный
68

Добавить CustomNavigationController

Переопределите в нем эти методы:

-(BOOL)shouldAutorotate
{
    return [[self.viewControllers lastObject] shouldAutorotate];
}

-(NSUInteger)supportedInterfaceOrientations
{
    return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}

Теперь добавьте все ориентации в список

введите описание изображения здесь

В контроллере представления добавьте только необходимые:

-(BOOL)shouldAutorotate
{
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

эти методы переопределяют методы контроллера навигации

Зараки
источник
5
Вы действительно справились с этим ответом! Огромное спасибо. Вы действительно очень помогли. Некоторое время это было для меня большой проблемой. Прекрасная работа.
K2Digital
Спасибо! На это у меня ушло так много времени и сил!
bkbeachlabs 06
Вы спасаете жизнь .... Это работает и в моем приложении. На самом деле я хочу, чтобы в моем приложении все ViewControllerбыли в портретном режиме, а мой ViewController- в режиме «Пейзаж и портрет». Я также поддерживаю как iOS 5.0, так и iOS 6.0. Вот почему я не понимаю, как обойтись, но это отличное решение. Я добавляю CustomNavigationController в свой контроллер корневого представления и предоставляю поддержку в соответствии со своими потребностями. Еще раз спасибо.
Bhavin_m
Фантастика. Именно то, что мне нужно. Спасибо.
mszaro
Что делать, если желаемый контроллер ландшафтного представления представлен из перехода и нет контроллера навигации?
jesses.co.tt 02
9

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

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

введите описание изображения здесь

Затем создайте категорию UINavigationController(поскольку Apple говорит не создавать подклассы):

@implementation UINavigationController (iOS6AutorotationFix)

-(BOOL)shouldAutorotate {
    return [self.topViewController shouldAutorotate];
}

@end

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

-(BOOL)shouldAutorotate {

    BOOL shouldRotate = NO;

    if ([navigationController.topViewController isMemberOfClass:[RotatingViewController class]] ) {
        shouldRotate = [navigationController.topViewController shouldAutorotate];
    }

    return shouldRotate;
}

Наконец, в вашей RotatingViewControllerреализации shouldAutorotateиsupportedInterfaceOrientations следующим образом:

-(BOOL)shouldAutorotate {
    // Preparations to rotate view go here
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAllButUpsideDown; // or however you want to rotate
}

Причина, по которой вам нужно это сделать, заключается в том, что iOS 6 передает управление вращением контроллеру корневого представления, а не контроллеру вида сверху. Если вы хотите, чтобы вращение отдельного представления велось иначе, чем другие представления в стеке, вам нужно написать для него конкретный случай в контроллере корневого представления.

Брайан
источник
2
Это отличный ответ, заслуживающий еще большей любви. Вот часть информации, которую он содержит, которую я не мог найти больше нигде и которая имеет решающее значение: если вы хотите, чтобы вращение отдельного представления велось иначе, чем другие представления в стеке, вам нужно записать конкретный случай для него в корне view controller
шмим
Я полагаю, это сработает, если у вас есть navigationController в качестве первого члена раскадровки вашего приложения в качестве начального контроллера, но как насчет случая, когда начальным контроллером является просто ViewController?
Duck
4

У меня недостаточно репутации, чтобы комментировать ответ @Brian, поэтому я добавлю здесь свое примечание.

Брайан упомянул, что iOS6 передает управление вращением rootViewController - это может быть не только UINavigationController, как уже упоминалось, но также UITabBarController, которым он был для меня. Моя структура выглядит так:

  • UITabBarController
    • UINavigationController
      • UIViewControllers ...
    • UINavigationController
      • UIViewControllers ...

Поэтому я добавил методы сначала в пользовательский UITabBarController, затем в пользовательский UINavigationController и, наконец, в конкретный UIViewController.

Пример из UITabBarController и UINavigationController:

- (BOOL)shouldAutorotate {
    return [self.viewControllers.lastObject shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations {
    return [self.viewControllers.lastObject supportedInterfaceOrientations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return [self.viewControllers.lastObject shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.viewControllers.lastObject preferredInterfaceOrientationForPresentation];
}
micmdk
источник
Я действительно использовал UITabBarController в качестве переменной экземпляра в моем корневом контроллере представления, потому что вы не должны создавать подклассы UITabBarController в версиях iOS до 6.0, и мне нужно было поддерживать обратно до iOS 5.0.
Брайан
@micmdk У меня такая же проблема. пожалуйста, посоветуйте, как решить, я скопировал ваш код и поместил в пользовательский контроллер навигации и customUabbarController и установил другой контроллер представления как Guided Брайана. но все еще не получает фиксированный контроллер просмотра в портретной ориентации.
Ram S
@ Брайан, у меня такая же проблема. пожалуйста, посоветуйте, как решить, я скопировал ваш код и поместил в пользовательский контроллер навигации и customUabbarController и установил другой контроллер представления как управляемый Micmdk. но все еще не получает фиксированный контроллер просмотра в портретной ориентации в ios8.
Ram S
3

Я хотел бы дать частичный ответ на свой вопрос. Я обнаружил, что следующая строка кода, используемая в viewWillAppearмоем третьем методе UIViewController, работает:

[[UIDevice currentDevice] 
      performSelector:NSSelectorFromString(@"setOrientation:") 
           withObject:(id)UIInterfaceOrientationPortrait];

Но мне это решение не очень нравится. Он использует уловку для присвоения свойства только для чтения, которое, согласно документации Apple, представляет физическую ориентацию устройства. Это похоже на указание iPhone перейти в правильную ориентацию в руке пользователя.

Мне очень хочется оставить это в моем приложении, потому что оно просто работает. Но это кажется неправильным, поэтому я хотел бы оставить этот вопрос открытым для чистого решения.

Харальд Бёгехольц
источник
4
Ваше приложение, скорее всего, будет отклонено, если вы оставите его там. Я тоже ищу решение этой проблемы, но похоже, что это невозможно. Все, что я смог найти, - это ответ на этот вопрос: stackoverflow.com/questions/7196300/… он вроде работает, но по какой-то причине нарушает обработку касания кнопок в одном из моих контроллеров просмотра
Лопе
Я рискнул и подчинился Apple. Все еще жду обзора ... Буду держать вас в курсе.
Harald Bögeholz 08
Apple только что одобрила мое приложение с этим взломом, ага! Надеюсь, в будущем обновлении iOS это не приведет к неприятным последствиям. Тем временем я все еще открыт для предложений по чистому решению!
Харальд Бёгехольц
1
@ HaraldBögeholz Grt suggetion. но когда я работаю в новом xCode с ARC, я не могу использовать ans. Я получаю сообщение «Приведение NSInteger (aka int) к идентификатору запрещено с помощью ARC. PerformSelector может вызвать утечку, потому что его селектор неизвестен», как я могу решить эту проблему? Я очень рад, если у нас есть решение. Когда я выключаю ARC для perticular View, происходит сбой приложения из-за утечки данных. Пожалуйста, предложите мне
Hitarth
4
Использование частного API НИКОГДА не является решением.
Хавьер Сото
1

Это я использую для поддержки ориентации в iOS 6.0.

-(BOOL)shouldAutorotate{
    return YES;
}  

 - (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;
}


- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{  
  return UIInterfaceOrientationPortrait;
}
АШИШТ
источник
1

Это очень просматриваемая ветка. Я подумал, что добавлю то, что считаю самым простым ответом. Это работает для ios8 и выше

-(BOOL)shouldAutorotate
{
    return YES;
}

и

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

Вот и все. Наслаждайтесь!

О, и мои ViewControllers встроены в контроллер навигации, который мне не нужно было подклассифицировать или настраивать каким-либо образом.

Reeder32
источник
0

Это может не сработать для всех, но для меня это работает отлично. Вместо реализации ...

[(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

в viewWillAppear во всех моих контроллерах я решил централизовать этот процесс внутри своего подкласса UINavigationController, переопределив метод UINavigationControllerDelegatenavigationController:willShowViewController:animated:

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

    self.previousVCLandscapeOK = self.isLandscapeOK; // Store the current VC's orientation preference before pushing on the new VC so we can set this again from within the custom "back" method
    self.isLandscapeOK = NO; // Set NO as default for all VC's
    if ([viewController isKindOfClass:[YourViewController class]]) {
        self.isLandscapeOK = YES;
    }
}

Я обнаружил, что этот метод делегата не вызывается при извлечении VC из стека навигации. Для меня это не было проблемой, потому что я обрабатываю обратную функциональность из своего подкласса UINavigationController, так что я могу установить правильные кнопки панели навигации и действия для определенных VC, например, этого ...

if ([viewController isKindOfClass:[ShareViewController class]]) {

    UIButton* backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 57, 30)];
    [backButton setImage:[UIImage imageNamed:@"back-arrow"] forState:UIControlStateNormal];
    [backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem* backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backButtonItem;

    UIImageView* shareTitle = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"share-title"]];
    [shareTitle setContentMode:UIViewContentModeScaleAspectFit];
    [shareTitle setFrame:CGRectMake(0, 0, shareTitle.frame.size.width - 10, shareTitle.frame.size.height - 10)];
    viewController.navigationItem.titleView = shareTitle;

} else if(...) {
    ...
}

Вот как backвыглядит мой метод для обработки выталкивания VC из стека и установки соответствующего предпочтения вращения ...

- (void)back {
    self.isLandscapeOK = self.previousVCLandscapeOK;
    self.previousVCLandscapeOK = NO;
    [self popViewControllerAnimated:YES];
}

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

@property (nonatomic) BOOL isLandscapeOK;
@property (nonatomic) BOOL previousVCLandscapeOK;

в navigationController:willShowViewController:animated: котором будет определено, какие поддерживаемые ориентации находятся в том виртуальном канале, который будет представлен. При извлечении VC вызывается мой собственный метод «back», и затем я устанавливаю isLandscapeOK на то, что было сохранено через предыдущее значение VCLandscapeOK.

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

Надеюсь, это поможет кому-то так же, как и мне. Спасибо, Джереми.

Джереми Фокс
источник
0

Перейдите к файлу Info.plist и внесите изменениявведите описание изображения здесь

Некель Латоя Селестин
источник
0

Вы хотите заставить портрет приложения iOS 6 только тогда, когда вы можете добавить в подкласс UIViewController методы ниже

- (BOOL)shouldAutorotate {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        return YES;
    } else {
        return NO;
    }
}


- (NSUInteger)supportedInterfaceOrientations {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        return UIInterfaceOrientationMaskAll;
    } else {
        return UIInterfaceOrientationMaskPortrait;
    }
}
Вайбхав Саран
источник
2
Проблема в том, что если это представление не является корневым (которого нет в этом вопросе), shouldAutorotate не вызывается.
Брайан
по какой-либо причине возвращаемый тип NSUIntegerвыдает предупреждение, несмотря на то, что это правильный тип var. если у вас есть ОКР, используйте UIInterfaceOrientationMaskвместо этого.
Крис Дж,
Кроме того, это решение отлично работало для контроллера модального представления (представления камеры), который у меня есть, без необходимости создания подкласса UINavigationController. Я предполагаю, что модальные окна обрабатываются независимо. Не имеет отношения к исходному вопросу, но полезная информация для дизайна.
Крис Дж,
0

Я хотел, чтобы все мои VC были заблокированы для портретной ориентации, кроме одного. Это то, что у меня сработало.

  1. Добавьте поддержку всех ориентаций в файл plist.
  2. В корневом контроллере представления определите тип контроллера представления, который находится в верхней части окна, и установите соответствующую ориентацию приложения в методе supportedInterfaceOrientations . Например, мне нужно, чтобы мое приложение вращалось только тогда, когда веб-просмотр находился на вершине стека. Вот что я добавил в свой rootVC:

    -(NSUInteger)supportedInterfaceOrientations
    {
        UIViewController *topMostViewController = [[Utils getAppDelegate] appNavigationController].topViewController;
        if ([topMostViewController isKindOfClass:[SVWebViewController class]]) {
            return UIInterfaceOrientationMaskAllButUpsideDown;
        }
        return UIInterfaceOrientationMaskPortrait;
    }
Трунал Бхансе
источник
Trunal, ему несколько месяцев, но не могли бы вы рассказать немного подробнее о том, как вы определяете тип контроллера представления, который находится сверху. В частности, я не уверен, что вы здесь делаете [[Utils getAppDelegate] appNavigationController]. Utils Я предполагаю, что у вас есть какой-то класс, но я не понимаю, что вы в нем делаете. Любая помощь была бы замечательной!
Бен
0

У меня недостаточно репутации, чтобы ответить на вопрос @Ram S в ответе @micmdk, поэтому я добавлю здесь свое примечание.

Когда вы используете UITabbarController , попробуйте изменить self.viewControllers.lastObject в коде @ micmdk на self.selectedViewController следующим образом:

- (BOOL)shouldAutorotate {
    return [self.selectedViewController shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations {
    return [self.selectedViewController supportedInterfaceOrientations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return [self.selectedViewController shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
фантом_2
источник
0

Вы можете реализовать и переопределить переменные shouldAutorotate () и supportedInterfaceOrientations во всех ваших классах контроллера представления, которые должны быть представлены в ориентации, отличной от ориентации, определенной в PLIST вашего приложения.

Однако в нетривиальном пользовательском интерфейсе вы можете столкнуться с проблемой добавления его в десятки классов, и вы не хотите делать все из них подклассами нескольких общих, которые его поддерживают (MyBaseTableViewController, MyBaseNavigationController и MyBaseTabBarController).

Поскольку вы не можете напрямую переопределить этот метод / var в UIViewController, вы можете сделать это в его подклассах, которые обычно являются вашими базовыми классами, такими как UITableViewController, UINavigationController и UITabBarController.

Таким образом, вы можете реализовать несколько расширений и по-прежнему настроить MyPreciousViewController для отображения в другой ориентации, чем все другие, такие как этот фрагмент кода Swift 4:

extension UITableViewController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

    if let last = self.navigationController?.childViewControllers.last,
        last != self {
            return last.supportedInterfaceOrientations
    } else {
        return [.portrait]
    }
    }
}

extension MyPreciousViewController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

    return [.portrait,.landscape]
    }
}


extension UINavigationController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        return [.portrait]
    }
}


extension UITabBarController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        return [.portrait]
    }
}
Ведрано
источник
0

Я решил такую ​​же проблему.

Если вы используете UINavigationControllerдля нажатия контроллеров представления, вы должны установить методы ниже.

extension UINavigationController{

    override open var shouldAutorotate: Bool {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return true
        }
        return false
    }

    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return .portrait
        }
        return .landscapeRight

    }
    override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return .portrait
        }
        return .landscapeRight
    }
}

Вместо LoginViewControllerиспользования, которое UIViewControllerвы хотите показать. В моем случае я хочу показать LoginViewControllerв Portraitрежиме другой ViewControllersв landscapeрежиме.

Рамакришна
источник
-1

Используйте следующий метод для решения этой проблемы

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

верните только нужную вам ориентацию !!!

maxfiresolutions
источник
3
Я так и сделал, как писал. Это не позволяет моему контроллеру представления вращаться, но не заставляет его вернуться к единственной поддерживаемой ориентации, когда я вернусь к нему.
Harald Bögeholz
У меня это не работает. Даже включив это в мой контроллер представления, представление все равно вращается. Что дает?
прокладка
почему голосование против maxfiresolutions? Это сработало для меня, и тот же метод, что и @Flo, получил много голосов.
ViruMax