Чи існує задокументований спосіб встановити орієнтацію iPhone?


94

У мене є програма, де я хотів би підтримувати обертання пристрою в певних поданнях, але інші особливо не мають сенсу в альбомному режимі, тому, замінюючи подання, я б хотів змусити поворот встановити на портрет.

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

[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];

Чи існують задокументовані способи змусити орієнтуватися?

Оновлення: Я думав, що надам приклад, оскільки я не шукаю shouldAutorotateToInterfaceOrientation, оскільки я це вже реалізував.

Я хочу, щоб моя програма підтримувала альбомну та книжкову в View 1, але лише портретну у View 2. Я вже реалізував shouldAutorotateToInterfaceOrientation для всіх видів, але якщо користувач перебуває в альбомному режимі у View 1, а потім перемикається на View 2, я хочу змусити телефон, щоб повернути його назад до Книжкової.


11
Я повідомив про цю помилку, пропонуючи Apple або виставити вищевказаний API, або виконати ТАК, НІ повертатися з методу shouldRotate при першому завантаженні подання, а не лише при обертанні телефону.
rustyshelf

2
Я повідомив про помилку з проханням викрити setOrientation
Jessedc

1
[[UIDevice currentDevice] setOrientation: UIInterfaceOrientationPor Portrait]; Цей метод застарілий і більше не існує.
Хікмат-хан

Відповіді:


6

Це вже не проблема на пізнішому iPhone 3.1.2 SDK. Тепер, здається, дотримується запитувана орієнтація вигляду, що відсувається назад у стек. Це, ймовірно, означає, що вам потрібно буде виявити старіші версії ОС iPhone і застосовувати setOrientation лише тоді, коли це до останнього випуску.

Не зрозуміло, чи статичний аналіз Apple зрозуміє, що ви обходите застарілі обмеження SDK. Apple особисто мені наказав видалити виклик методу під час мого наступного оновлення, тому я ще не впевнений, що хакерство для старих пристроїв пройде процес затвердження.


Це не стосується iPhone Simulator 3.3.2. Це все ще не працює.
Качка

4
@user Як ви запитуєте орієнтацію, коли натискаєте подання на стек?
progrmr

4
Ви можете обійти статичний аналіз Apple. Я успішно використовую недокументований код, використовуючи perforSelector і покладаючись на дивовижний динамізм Obj-C.
Kenneth Ballenegger

@KennethBallenegger Я пам’ятаю, як я читав у керівних принципах та / або угоді про те, що приховування подібних речей може призвести до видалення вашої програми та, можливо, навіть до вашого облікового запису. Я б не ризикував цим просто використовувати недокументований API.
Ivan Vučica

101

Це вже давно, але на той випадок, якщо прийде хтось, хто не використовує навігаційний контролер та / або не бажає використовувати бездокументарні методи:

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

Досить представити та відмовитись від контролера подання ванілі.

Очевидно, вам все одно доведеться підтвердити або заперечити орієнтацію у вашому перевизначенні shouldAutorotateToInterfaceOrientation. Але це призведе до того, що Autorotate ... буде знову викликаний системою.


1
Чудово, це не позначено як правильну відповідь на цю публікацію, але це вирішило половину моєї проблеми stackoverflow.com/questions/5030315 . Трюк подання моделі "змушує" портретну орієнтацію, чи знаєте ви його також, щоб змусити пейзаж?
epologee

5
Ха-ха-ха !! це просто занадто дурно! АЛЕ ЦЕ ПРАЦЮЄ! - звичайно, так! : D
hfossli

Дивовижно, боровся з цим довгий час. Дякую! товариш
Рахул Чудхарі

3
Здається, це працює як для iOS5, так і для iOS6: [self presentViewController: [UIViewController new] анімований: НІЯКОГО завершення: ^ {[self dismissViewControllerAnimated: НЕ завершеного: нуль]; }];
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 (кут)? Ви отримали щось подібне?
NeverHopeless

Найпростіший спосіб зробити це за допомогою a UIAlertView- це встановити ваш контролер перегляду як делегат alertView, а потім:: - (void) didPresentAlertView:(UIAlertView *) alertView, ви виконуєте обертання на основі поточного [UIApplication sharedApplication].statusBarOrientation. Якщо ви не анімуєте це, не повинно виглядати надто дивно.
Michael Gaylord

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];

Потім у вашому контролері портретного режиму ви можете зробити зворотне - перевірте, чи перебуває він в даний час в альбомному режимі, і якщо так, поверніть його назад у книжковий.


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 у правильній орієнтації перед натисканням, але я виявив, що він працює лише при форсуванні портретних орієнтацій (якщо поточний вигляд - це портрет, а ви хочете, щоб наступний вийшов у альбомний режим, він виграв не дзвоніть слідAutorotateToInterfaceOrientation: при вискакуванні). Ви знайшли обхідний шлях, щоб примусити подання до альбомної?
Рафаель Нобре

Цікаво - цього питання у мене не було, вибачте. Я спробував би погратись із послідовністю натискань та сплесків, є пристойна ймовірність, що ви щось придумаєте.
Ендрю Вільчак,

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

Це працює на моєму кінці, як на модальному, так і на push-контролері перегляду. Але тільки на iOS 5. :(
Guntis Treulands

Так, я теж працював у ios5. Тепер у ios6 висота навігаційної панелі зменшується після
відключення

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методом (припускаючи, що ви використовуєте a 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 ];

Це єдина з відповідей на цій сторінці, яку я зміг успішно адаптувати до мого коду контролера навігації. Одна незначна проблема полягає в тому, що повернена навігаційна панель не змінює висоту між портретною та альбомною орієнтацією, як, якщо б поверталася за допомогою звичайного механізму.
Dan Dyer

@DanDyer Ви вирішили проблему з неправильною висотою панелі навігації? (Особливо в ios6 висота неправильна)
AlexWien

@AlexWien Мені врешті-решт вдалося переконати клієнта, що ми не повинні цього робити. Для iOS 6 вам може знадобитися щось зробити за допомогою нового shouldAutorotateметоду (див. Stackoverflow.com/a/12586002/5171 ).
Dan Dyer

@DanDyer вирішив це вчора, просте приховування та показ навігації та statusBar у ViewDidAppear вирішило. Тепер усі роботи (SDK ios6, цільовий ios5) я маю повторно протестувати з цільовим ios6
AlexWien

2

Це працює для мене (дякую Генрі Куку):

Метою для мене було мати справу лише зі змінами ландшафтної орієнтації.

метод init:

[[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 ];
}

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 явно скидаються ...

Єдиним застереженням до цього методу для збереження вигляду Landscape є властива анімація перемикання між орієнтаціями, що має відбутися, коли було б краще, щоб він не мав змін.


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];

Я переконався, що цього достатньо, щоб це працювало і в 4.1. Однак, оскільки мій рядок стану встановлений як прихований, я отримую 20-піксельний розрив вгорі :(
Ентоні Мейн,

setOrientation - це приватний API, а додаток, що використовує його, відхилено.
alones
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.