Каков «правильный» способ обработки изменений ориентации в iOS 8?

104

Подскажите, пожалуйста, «правильный» или «лучший» подход к работе с портретной и альбомной ориентацией интерфейса в iOS 8? Кажется, что все функции, которые я хочу использовать для этой цели, устарели в iOS 8, и мое исследование не показало четкой и элегантной альтернативы. Я действительно должен смотреть на ширину и высоту, чтобы определить для себя, в портретном или ландшафтном режиме?

Например, в моем контроллере представления, как мне реализовать следующий псевдокод?

if we are rotating from portrait to landscape then
  do portrait things
else if we are rotating from landscape to portrait then
  do landscape things
rmp251
источник
3
Прочтите документацию для UIViewController. См. Раздел «Обработка
поворотов
То, что они устарели, является подсказкой. Вы должны использовать что-то еще .... это что-то еще должно быть AutoLayout и Size Classes :-)
Аарон

Ответы:

263

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

Однако переход с книжной ориентации на альбомную в iPad достаточно значительный, и вам может потребоваться внести небольшие изменения в пользовательский интерфейс, даже если классы размеров не изменились. Поскольку методы, связанные с ориентацией интерфейса UIViewController, устарели, Apple теперь рекомендует реализовать следующий новый метод в UIViewControllerкачестве замены:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    // Code here will execute before the rotation begins.
    // Equivalent to placing it in the deprecated method -[willRotateToInterfaceOrientation:duration:]

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        // Place code here to perform animations during the rotation.
        // You can pass nil or leave this block empty if not necessary.

    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        // Code here will execute after the rotation has finished.
        // Equivalent to placing it in the deprecated method -[didRotateFromInterfaceOrientation:]

    }];
}

Большой! Теперь вы получаете обратные вызовы прямо перед началом вращения и после его завершения. Но как насчет того, чтобы на самом деле знать, какой поворот - портретный или альбомный?

Apple рекомендует рассматривать вращение как простое изменение размера родительского представления. Другими словами, во время поворота iPad с книжной на альбомную вы можете думать об этом как о представлении корневого уровня, просто меняя его bounds.sizeс {768, 1024}на {1024, 768}. Зная это, вы должны использовать sizeпереданный в viewWillTransitionToSize:withTransitionCoordinator:метод выше, чтобы выяснить, поворачиваетесь ли вы в портретную или альбомную ориентацию.

Если вам нужен еще более простой способ переноса устаревшего кода на новый способ работы iOS 8, рассмотрите возможность использования этой простой категории в UIView, которая может использоваться для определения того, является ли представление «портретным» или «ландшафтным» на основе его размер.

Резюмируем:

  1. Вы должны использовать классы размеров, чтобы определить, когда показывать принципиально разные пользовательские интерфейсы (например, пользовательский интерфейс, похожий на iPhone, а не на интерфейс iPad).
  2. Если вам нужно внести меньшие корректировки в свой пользовательский интерфейс, когда классы размеров не меняются, а размер вашего контейнера (родительского представления) изменяется, например, когда iPad вращается, используйте viewWillTransitionToSize:withTransitionCoordinator:обратный вызов в UIViewController.
  3. Каждое представление в вашем приложении должно принимать решения о макете только на основе пространства, в котором оно было отведено для макета. Позвольте естественной иерархии представлений каскадировать эту информацию.
  4. Точно так же не используйте statusBarOrientationсвойство - которое в основном является свойством на уровне устройства - для определения того, следует ли размещать вид для «портретной» или «альбомной» ориентации. Ориентация строки состояния должна использоваться только кодом, имеющим дело с вещами, вроде тех, UIWindowкоторые фактически находятся на самом корневом уровне приложения.
смайлик
источник
5
Отличный ответ! Кстати, ваше видео на YouTube на AL было невероятно информативным. Спасибо за обмен. Проверьте это! youtube.com/watch?v=taWaW2GzfCI
smileBot
2
Единственная проблема, которую я обнаружил, заключается в том, что иногда вы хотите узнать ориентацию устройства, и это не позволит вам узнать об этом. На iPad он вызывается перед изменением строки состояния или значения ориентации устройства. Вы не можете сказать, держит ли пользователь устройство в портретном или перевернутом положении. Тестировать тоже невозможно, издевательство UIViewControllerTransitionCoordinator- это кошмар :-(
Даррарски 05
@Darrarski, нашли ли вы элегантное решение для решения этих проблем?
Xvolks
так где же viewdidlayoutsubviewsтогда? Я думал, viewdidlayoutsubviewsон используется для фиксации связанных изменений из-за вращения. Вы можете подробнее рассказать об этом?
Дорогая,
1
Как отличить левый пейзаж от правого, если мы не используем ориентацию строки состояния? Мне нужно это различие. Кроме того, существуют и другие проблемы, описанные здесь - stackoverflow.com/questions/53364498/…
Дипак Шарма
17

Основываясь на очень подробном (и принятом) ответе smileyborg, вот адаптация с использованием swift 3:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: nil, completion: {
        _ in
        self.collectionView.collectionViewLayout.invalidateLayout()
    })        
}

А в UICollectionViewDelegateFlowLayoutреализации

public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // retrieve the updated bounds
    let itemWidth = collectionView.bounds.width
    let itemHeight = collectionView.bounds.height
    // do whatever you need to do to adapt to the new size
}
CMont
источник
11

Я просто использую Центр уведомлений:

Добавьте переменную ориентации (объясню в конце)

//Above viewdidload
var orientations:UIInterfaceOrientation = UIApplication.sharedApplication().statusBarOrientation

Добавить уведомление при появлении представления

override func viewDidAppear(animated: Bool) {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name: UIDeviceOrientationDidChangeNotification, object: nil)
}

Удалить уведомление, когда просмотр исчезнет

override func viewWillDisappear(animated: Bool) {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIDeviceOrientationDidChangeNotification, object: nil) 
}

Получает текущую ориентацию при срабатывании уведомления

func orientationChanged (notification: NSNotification) {
    adjustViewsForOrientation(UIApplication.sharedApplication().statusBarOrientation)
}

Проверяет ориентацию (книжная / альбомная) и обрабатывает события

func adjustViewsForOrientation(orientation: UIInterfaceOrientation) {
    if (orientation == UIInterfaceOrientation.Portrait || orientation == UIInterfaceOrientation.PortraitUpsideDown)
    {
        if(orientation != orientations) {
            println("Portrait")
            //Do Rotation stuff here
            orientations = orientation
        }
    }
    else if (orientation == UIInterfaceOrientation.LandscapeLeft || orientation == UIInterfaceOrientation.LandscapeRight)
    {
       if(orientation != orientations) {
            println("Landscape")
            //Do Rotation stuff here
            orientations = orientation
        }
    }
}

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

inVINCEable
источник
11
Это не рекомендуемый Apple подход, потому что он означает, что каждое представление в вашем приложении решает, как отображать, на основе ориентации устройства, а не с учетом предоставленного ему пространства.
смайлик
2
Apple также не принимает во внимание оборудование в версии 8.0. Сообщите слою AVPreview, что ему не нужно беспокоиться об ориентации, если он представлен через контроллер представления. Не работает, но это исправлено в версии 8.2
Ник Тернер,
superиз viewDidAppearи viewWillDisappearдолжны называться
Андрей Богаевский
Примечание. UIDeviceOrientation! = UIInterfaceOrientation. В моих экспериментах statusBarOrientation ненадежен.
nnrales 06
2

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

См. Раздел « Черты, описывающие класс размера и масштаб интерфейса» здесь: https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS8.html

«В iOS 8 добавлены новые функции, которые делают работу с размером и ориентацией экрана намного более универсальной».

Это тоже хорошая статья: https://carpeaqua.com/thinking-in-terms-of-ios-8-size-classes/

ИЗМЕНИТЬ Обновленную ссылку: https://carpeaqua.com/2014/06/14/thinking-in-terms-of-ios-8-size-classes/ (Источник: Koen)

Аарон
источник
Да, похоже, весь его сайт сейчас не работает.
Аарон
Статью действительно стоит прочесть, и вот правильный адрес: carpeaqua.com/thinking-in-terms-of-ios-8-size-classes
uem
Но что, если мы говорим не о классах размеров и UI, а, например, об AVFoundation. При записи видео в некоторых случаях необходимо знать ориентацию viewControllers, чтобы установить правильные метаданные. Это просто невозможно. «универсальный».
Джулиан Ф. Вайнерт,
Это не то, о чем спрашивал / говорил ОП.
Аарон
1
Обновлена ссылка: carpeaqua.com/2014/06/14/...
Koen