Есть ли задокументированный способ установить ориентацию iPhone?

94

У меня есть приложение, в котором я хотел бы поддерживать поворот устройства в определенных представлениях, но другие не имеют особого смысла в ландшафтном режиме, поэтому, когда я меняю местами представления, я хотел бы принудительно установить поворот в портретном режиме.

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

[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];

Есть ли какие-нибудь задокументированные способы заставить ориентироваться?

Обновление: я подумал, что приведу пример, поскольку я не ищу shouldAutorotateToInterfaceOrientation, поскольку я уже реализовал это.

Я хочу, чтобы мое приложение поддерживало альбомную и портретную ориентацию в представлении 1, но только портретное положение в представлении 2. Я уже реализовал shouldAutorotateToInterfaceOrientation для всех представлений, но если пользователь находится в альбомном режиме в представлении 1, а затем переключается на представление 2, я хочу принудительно телефон, чтобы вернуться в портретную ориентацию.

Дэйв Вервер
источник
11
Я сообщил об ошибке, предлагая Apple либо предоставить вышеуказанный API, либо соблюдать YES, NO, возвращаемые методом shouldRotate, при первой загрузке представления, а не только при вращении телефона.
rustyshelf
2
Я зарегистрировал ошибку с просьбой об открытии setOrientation
Jessedc
1
[[UIDevice currentDevice] setOrientation: UIInterfaceOrientationPortrait]; Этот метод устарел и больше не существует.
Hikmat Khan

Ответы:

6

Это больше не проблема в более позднем iPhone 3.1.2 SDK. Теперь он, кажется, соблюдает запрошенную ориентацию представления, возвращаемого в стек. Скорее всего, это означает, что вам нужно будет обнаруживать более старые версии iPhone OS и применять setOrientation только тогда, когда он предшествует последней версии.

Неясно, поймет ли статический анализ Apple, что вы работаете в обход старых ограничений SDK. Мне лично Apple посоветовал удалить вызов метода в моем следующем обновлении, поэтому я еще не уверен, пройдет ли процесс утверждения взлома для старых устройств.

Джон К.
источник
В случае с iPhone Simulator 3.3.2 дело обстоит иначе. Это все еще не работает.
Дак
4
@user Как вы запрашиваете ориентацию, когда помещаете представление в стек?
progrmr
4
Вы можете обойти статический анализ Apple. Я успешно использую недокументированный код, используя performSelector и полагаясь на потрясающий динамизм Obj-C.
Кеннет Балленеггер,
@KennethBallenegger Я помню, как читал в руководящих принципах и / или соглашении, что скрытие подобных вещей может привести к удалению вашего приложения и, возможно, даже вашей учетной записи. Я бы не стал рисковать, просто используя недокументированный API.
Иван Вучица
101

Это уже давно постфактум, но на всякий случай придет кто-нибудь, кто не использует контроллер навигации и / или не желает использовать недокументированные методы:

UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];

Достаточно представить и закрыть ванильный контроллер представления.

Очевидно, вам все равно потребуется подтвердить или отклонить ориентацию при переопределении shouldAutorotateToInterfaceOrientation. Но это приведет к тому, что система снова вызовет shouldAutorotate ...

Джош
источник
1
Замечательно, этот ответ не отмечен как правильный ответ в этом посте, но это решило половину моей проблемы stackoverflow.com/questions/5030315 . Трюк с модельным обзором «заставляет» портретную ориентацию, знаете ли вы, что он заставляет пейзажную?
epologee
5
Ха-ха !! это слишком глупо! НО ЭТО РАБОТАЕТ! - конечно, есть! : D
hfossli 07
Круто, долго боролся с этим. Спасибо! товарищ
Рахул Чоудхари
3
Кажется, это работает как для iOS5, так и для iOS6: [self presentViewController: [UIViewController new] анимированный: NO завершение: ^ {[self dismissViewControllerAnimated: NO завершение: nil]; }];
Inferis
1
Кажется, это работает на iOS 7 и iOS 8, но появляется заметный черный экран, который мерцает (в основном iOS 8).
levigroker
16

Если вы хотите заставить его повернуться из книжной в альбомную, вот код. Просто обратите внимание, что вам нужно отрегулировать центр вашего обзора. Я заметил, что мой вид не в нужном месте. В остальном все работало отлично. Спасибо за чаевые.

if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){

        [UIView beginAnimations:@"View Flip" context:nil];
        [UIView setAnimationDuration:0.5f];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
        self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
        self.view.center = CGPointMake(160.0f, 240.0f);

        [UIView commitAnimations];
}
Майкл Гейлорд
источник
Будет ли это работать, если я добавлю UIAlertView? Я попытался CGAffineTransformMakeRotationповернуть представление, но UIAlertView по-прежнему соответствует ориентации строки состояния, т.е. -degreeToRadian (angle)? У вас получилось что-то подобное?
NeverHopeless
Самый простой способ сделать это с помощью a UIAlertView- установить контроллер представления в качестве делегата alertView, а затем на: - (void) didPresentAlertView:(UIAlertView *) alertViewвы выполните вращение на основе текущего [UIApplication sharedApplication].statusBarOrientation. Если не анимировать, не должно выглядеть слишком странно.
Майкл Гейлорд
16

Насколько я могу судить, setOrientation:метод не работает (или, возможно, больше не работает). Вот что я делаю для этого:

сначала поместите это определение в начало файла, прямо под #imports:

#define degreesToRadian(x) (M_PI * (x) / 180.0)

то в viewWillAppear:методе

[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];     
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {  
    self.view.transform = CGAffineTransformIdentity;
    self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
    self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}

если вы хотите, чтобы это было анимировано, вы можете обернуть все это в блок анимации, например:

[UIView beginAnimations:@"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];     
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {  
    self.view.transform = CGAffineTransformIdentity;
    self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
    self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}
[UIView commitAnimations];

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

Bdebeez
источник
1
Вместо self.view вы должны повернуть self.window.view. Это удобно, если у вас есть UITabBar или другие контроллеры поверх вашего представления.
Борут Томазин
7

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

Я заметил, что shouldAutorotateToInterfaceOrientation:метод не вызывается, когда новый контроллер представления помещается в стек, а вызывается, когда контроллер представления выталкивается из стека .

Воспользовавшись этим, я использую этот фрагмент кода в одном из своих приложений:

- (void)selectHostingAtIndex:(int)hostingIndex {

    self.transitioning = YES;

    UIViewController *garbageController = [[[UIViewController alloc] init] autorelease];
    [self.navigationController pushViewController:garbageController animated:NO];
    [self.navigationController popViewControllerAnimated:NO];

    BBHostingController *hostingController = [[BBHostingController alloc] init];
    hostingController.hosting = [self.hostings objectAtIndex:hostingIndex];
    [self.navigationController pushViewController:hostingController animated:YES];
    [hostingController release];

    self.transitioning = NO;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    if (self.transitioning)
        return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
    else
        return YES;
}

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

Эндрю Вилчак
источник
Привет, я только что реализовал ваш метод, чтобы заставить VC в правильной ориентации, прежде чем нажимать его, но я обнаружил, что он работает только при принудительной ориентации портретной ориентации (если текущий вид - портретный, и вы хотите принудительно установить следующий вид в альбомный, он выиграл 't call shouldAutorotateToInterfaceOrientation: при появлении). Вы нашли обходной путь для принудительного просмотра в альбомной ориентации?
Рафаэль Нобре
Интересно - у меня не было этой проблемы, извините. Я бы попробовал поиграть с последовательностью толчков и хлопков, есть неплохие шансы, что вы что-нибудь придумаете.
Эндрю Вилчак,
7

Существует простой способ программно принудительно настроить iPhone в нужной ориентации - используя два из уже предоставленных ответов kdbdallas , Джош :

//will rotate status bar
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];


//will re-rotate view according to statusbar
UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];

работает как шарм :)

РЕДАКТИРОВАТЬ:

для iOS 6 мне нужно добавить эту функцию: (работает на модальном контроллере просмотра)

- (NSUInteger)supportedInterfaceOrientations
{
    return (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight);
}
Гунтис Треуландс
источник
Для меня это не работает как шарм: панель навигации и панель инструментов уменьшаются до высоты, которую они имели бы в альбомном режиме. Метод с удалением и повторным добавлением самого верхнего представления сохраняет правильную высоту, но также имеет недостаток: моя панель навигации немного опускается (как если бы панель отображения включена)
AlexWien
Он работает с моей стороны, как на модальном, так и на подталкиваемом контроллере просмотра. Но только на iOS 5. :(
Guntis Treulands
Да, у меня тоже на ios5 работает. Теперь в ios6 высота панели навигации уменьшается после закрытия модального контроллера ландшафтного представления
AlexWien
7

Я копал и копал в поисках хорошего решения этой проблемы. Нашел это сообщение в блоге, в котором есть трюк: удалите самое внешнее представление из ключа UIWindowи добавьте его снова, затем система повторно запросит shouldAutorotateToInterfaceOrientation:методы из ваших контроллеров представления, применяя правильную ориентацию. Смотрите: iphone заставляет uiview переориентировать

Рафаэль Нобре
источник
Это решение сработало для меня лучше всего. Важно отметить, что исправление, которое представляет собой пустой modalViewController, вызывает некорректную анимацию помещения контроллеров представления в стек. Этот метод UIWindow не страдает той же проблемой.
Рик Смит-Унна,
5

Ответ Джоша отлично подходит для меня.

Однако я предпочитаю публиковать уведомление «ориентация изменилась, обновите интерфейс» . Когда это уведомление получено контроллером представления, он вызывает shouldAutorotateToInterfaceOrientation:, позволяя вам установить любую ориентацию, вернувшись YESк желаемой ориентации.

[[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil];

Единственная проблема в том, что это приводит к переориентации без анимации. Вам нужно будет обернуть эту линию между beginAnimations:и, commitAnimationsчтобы добиться плавного перехода.

Надеюсь, это поможет.

Кристиан Шнорр
источник
3

FWIW, вот моя реализация ручной настройки ориентации (для перехода в корневой контроллер представления вашего приложения, natch):

-(void)rotateInterfaceToOrientation:(UIDeviceOrientation)orientation{

    CGRect bounds = [[ UIScreen mainScreen ] bounds ];
    CGAffineTransform t;
    CGFloat r = 0;
    switch ( orientation ) {
        case UIDeviceOrientationLandscapeRight:
            r = -(M_PI / 2);
            break;
        case UIDeviceOrientationLandscapeLeft:
            r  = M_PI / 2;
            break;
    }
    if( r != 0 ){
        CGSize sz = bounds.size;
        bounds.size.width = sz.height;
        bounds.size.height = sz.width;
    }
    t = CGAffineTransformMakeRotation( r );

    UIApplication *application = [ UIApplication sharedApplication ];

    [ UIView beginAnimations:@"InterfaceOrientation" context: nil ];
    [ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
    self.view.transform = t;
    self.view.bounds = bounds;
    [ UIView commitAnimations ];

    [ application setStatusBarOrientation: orientation animated: YES ];     
}

в сочетании со следующим UINavigationControllerDelegateметодом (при условии, что вы используете UINavigationController):

-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
    // rotate interface, if we need to
    UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
    BOOL bViewControllerDoesSupportCurrentOrientation = [ viewController shouldAutorotateToInterfaceOrientation: orientation ];
    if( !bViewControllerDoesSupportCurrentOrientation ){
        [ self rotateInterfaceToOrientation: UIDeviceOrientationPortrait ];
    }
}

Это позаботится о повороте корневого представления в зависимости от того, UIViewControllerподдерживает ли входящий текущую ориентацию устройства. Наконец, вы захотите подключиться rotateInterfaceToOrientationк фактическим изменениям ориентации устройства, чтобы имитировать стандартные функции iOS. Добавьте этот обработчик событий в тот же контроллер корневого представления:

-(void)onUIDeviceOrientationDidChangeNotification:(NSNotification*)notification{
    UIViewController *tvc = self.rootNavigationController.topViewController;
    UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
    // only switch if we need to (seem to get multiple notifications on device)
    if( orientation != [[ UIApplication sharedApplication ] statusBarOrientation ] ){
        if( [ tvc shouldAutorotateToInterfaceOrientation: orientation ] ){
            [ self rotateInterfaceToOrientation: orientation ];
        }
    }
}

Наконец, зарегистрируйтесь для получения UIDeviceOrientationDidChangeNotificationуведомлений initили loadviewпримерно так:

[[ NSNotificationCenter defaultCenter ] addObserver: self
                                           selector: @selector(onUIDeviceOrientationDidChangeNotification:)
                                               name: UIDeviceOrientationDidChangeNotification
                                             object: nil ];
[[ UIDevice currentDevice ] beginGeneratingDeviceOrientationNotifications ];
Генри Кук
источник
Это единственный из ответов на этой странице, который мне удалось успешно адаптировать к своему коду контроллера навигации. Одна небольшая проблема заключается в том, что повернутая панель навигации не меняет высоту между портретной и альбомной ориентацией, как если бы она была повернута с помощью обычного механизма.
Дэн Дайер,
@DanDyer, вы решили проблему с неправильной высотой панели навигации? (Особенно в ios6 высота неправильная)
AlexWien
@AlexWien В конце концов мне удалось убедить клиента, что мы не должны этого делать. Для iOS 6 вам может потребоваться что-то сделать с помощью нового shouldAutorotateметода (см. Stackoverflow.com/a/12586002/5171 ).
Дэн Дайер
@DanDyer решил это вчера, простое скрытие и отображение навигации и statusBar в ViewDidAppear решило это. Теперь все работает (SDK ios6, target ios5), мне нужно повторно протестировать с target ios6
AlexWien
2

У меня это работает (спасибо, Генри Кук):

Для меня целью было иметь дело только с изменением ориентации ландшафта.

метод инициализации:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(orientationChanged:)
                                             name:UIDeviceOrientationDidChangeNotification
                                           object:nil];

- (void)orientationChanged:(NSNotification *)notification {    
    //[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
    CGRect bounds = [[ UIScreen mainScreen ] bounds ];
    CGAffineTransform t;
    CGFloat r = 0;
    switch ( orientation ) {
        case UIDeviceOrientationLandscapeRight:
            r = 0;
            NSLog(@"Right");
            break;
        case UIDeviceOrientationLandscapeLeft:
            r  = M_PI;
            NSLog(@"Left");
            break;
        default:return;
    }

    t = CGAffineTransformMakeRotation( r );

    UIApplication *application = [ UIApplication sharedApplication ];
    [ UIView beginAnimations:@"InterfaceOrientation" context: nil ];
    [ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
    self.view.transform = t;
    self.view.bounds = bounds;
    [ UIView commitAnimations ];

    [ application setStatusBarOrientation: orientation animated: YES ];
}
user324168
источник
1

У меня есть приложение, в котором я хотел бы поддерживать поворот устройства в определенных представлениях, но другие не имеют особого смысла в ландшафтном режиме, поэтому, когда я меняю местами представления, я хотел бы принудительно установить поворот в портретном режиме.

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

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

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

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)pInterfaceOrientation {
    BOOL lHideNavBar = self.interfaceOrientation == UIInterfaceOrientationPortrait ? NO : YES;
    [self.navigationController setNavigationBarHidden:lHideNavBar animated:YES];
}

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

Надеюсь это поможет.

Стивенкэмпбелл
источник
1

В конце концов, я решил это довольно легко. Я пробовал все предложения, приведенные выше, и все равно не смог, так что это было моим решением:

В ViewController, который должен оставаться в альбомной ориентации (влево или вправо), я прислушиваюсь к изменениям ориентации:

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(didRotate:)
                                             name:UIDeviceOrientationDidChangeNotification object:nil];

Затем в didRotate:

- (void) didRotate:(NSNotification *)notification
{   if (orientationa == UIDeviceOrientationPortrait) 
    {
        if (hasRotated == NO) 
        {
            NSLog(@"Rotating to portait");
            hasRotated = YES;
            [UIView beginAnimations: @"" context:nil];
            [UIView setAnimationDuration: 0];
            self.view.transform = CGAffineTransformIdentity;
            self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-90));
            self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
            self.view.frame = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
            [UIView commitAnimations];

    }
}
else if (UIDeviceOrientationIsLandscape( orientationa))
{
    if (hasRotated) 
    {
        NSLog(@"Rotating to lands");
        hasRotated = NO;
        [UIView beginAnimations: @"" context:nil];
        [UIView setAnimationDuration: 0];
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0));
        self.view.bounds = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
        self.view.frame = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
        [UIView commitAnimations];

    }
}

Имейте в виду любые Super Views / Subviews, которые используют автоматическое изменение размера, поскольку view.bounds / frame явно сбрасываются ...

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

Дэвид ван Дугтерен
источник
1

Решение iOS 6:

[[[self window] rootViewController] presentViewController:[[UIViewController alloc] init] animated:NO completion:^{
    [[[self window] rootViewController] dismissViewControllerAnimated:NO completion:nil];
}];

Точный код зависит от приложения, а также от того, где вы его разместили (я использовал его в своем AppDelegate). Замени [[self window] rootViewController]на то, что используешь. Я использовал UITabBarController.

Бо А
источник
0

Я нашел решение и написал что-то на французском (но код на английском). Вот

Способ состоит в том, чтобы добавить контроллер в представление окна (контроллер должен иметь хорошую реализацию функции shouldRotate ....).

Винциус
источник
-3

Если вы используете UIViewControllers, есть этот метод:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

Возвращение NO для контроллеров представлений, содержащих представления, которые вы не хотите вращать.

Больше информации здесь

Мартин Гордон
источник
1
Это останавливает вращение, то, что я ищу, - это вернуть вращение к портрету, если пользователь находится в альбомной ориентации. Я добавил пример выше, чтобы лучше выразить то, что я имею в виду.
Дэйв Вервер,
-3

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

Эндрю Грант
источник
Это возможно с кодом, который я изначально опубликовал, вы просто никогда не знаете, когда он может перестать работать;)
Дэйв Вервер,
-3

Это то, что я использую. (Вы получаете некоторые предупреждения о компиляции, но это работает как в симуляторе, так и в iPhone)

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
Kdbdallas
источник
Я обнаружил, что этого достаточно, чтобы он работал и в 4.1. Однако, поскольку моя строка состояния скрыта, я получаю 20-пиксельный промежуток вверху :(
Энтони Мэйн,
setOrientation - это частный API, и приложение, использующее его, отклоняется.
Alones