Как обнаружить изменение ориентации?


148

Я использую Swift и хочу иметь возможность загружать UIViewController при повороте на альбомную ориентацию. Может ли кто-нибудь указать мне правильное направление?

Я ничего не могу найти в Интернете и немного смущен документацией.


1
Я полагаю, что API не изменился, поэтому это должны быть «didRotateToOrientation» и «willRotateToOrientation», что-то вроде этого, посмотрите в документации Apple
David 'mArm' Ansermot

1
Привет @ mArm.ch, спасибо за быстрый ответ! Так как бы я это реализовал? (Это мое первое приложение ... Я очень новичок в IOS) :)
Дэвид

Я сохранил как ответ, для других людей. Можете ли вы принять это, если это нормально для вас?
Дэвид 'mArm' Ansermot

Ответы:


194

Вот как я получил это работает:

В AppDelegate.swiftвнутри didFinishLaunchingWithOptions функции я ставлю:

NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)

а затем внутри класса AppDelegate я поместил следующую функцию:

func rotated() {
    if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
        print("Landscape")
    }

    if UIDeviceOrientationIsPortrait(UIDevice.current.orientation) {
        print("Portrait")
    }
}

Надеюсь, это поможет кому-нибудь еще!

Спасибо!


5
Я пытался добавить addObserver в AppDelegate, но продолжал получать SIGABRT в CoreFoundation с нераспознанным селектором. Однако, когда я переместил addObserver в viewDidLoad в моем первом представлении, он работал отлично. Просто для информации, если кто-то сталкивается с той же проблемой.
FractalDoctor

1
Я новичок в кодировании, но не должен selectorиметь формат строки "rotated:"??
Хамелеон

4
Я уверен, что это только в том случае, если вы принимаете аргументы (а rotated()это не так)
Дэвид

26
Будьте осторожны, потому что UIDeviceOrientationэто что-то отличное от UIInterfaceOrientation... Это потому, что UIDeviceOrientationтакже обнаруживает ориентацию лицевой стороной вниз и лицевой стороной вверх, что означает, что ваш код может случайным образом перемещаться между книжной и альбомной ориентацией, если ваше устройство лежит на почти плоской, но слегка неровной поверхности (т.е. покачивание на выступающей поверхности). камера
6/6

4
Этот метод НЕ РАБОТАЕТ, если в телефоне отключен автоповорот.
chengsam

178
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    }
    if UIDevice.current.orientation.isFlat {
        print("Flat")
    } else {
        print("Portrait")
    }
}

71
Это будет называться ДО вращения. Если вам нужен, например, размер кадра после поворота, это решение не будет работать.
Upvote

не работал в расширении. Пейзаж или портрет до сих пор печатают «портрет»
TomSawyer

4
Этот метод НЕ РАБОТАЕТ, если в телефоне отключен автоповорот.
Ченгсам

5
на iPad, поскольку размерный класс одинаков как для альбомной, так и для портретной ориентации, этот метод никогда не вызывается.
Джеки

Это также не удастся, если устройство вернет isFlat. developer.apple.com/documentation/uikit/uideviceorientation/…
CodeBender

57

Необходимо обнаружить вращение при использовании камеры с AVFoundation, и обнаружил, что didRotateнастоящее время не рекомендуется ) &willTransition методы были ненадежными для моих нужд. Использование уведомления, опубликованного Дэвидом, сработало, но не актуально для Swift 3.x / 4.x.

Swift 4.2 Имя уведомления было изменено.

Значение закрытия остается таким же, как Swift 4.0:

var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other")
        }
    }

Чтобы настроить уведомление для Swift 4.2 :

NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification,
                                       object: nil,
                                       queue: .main,
                                       using: didRotate)

Чтобы снять уведомление для Swift 4.2 :

NotificationCenter.default.removeObserver(self,
                                          name: UIDevice.orientationDidChangeNotification,
                                          object: nil)

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

Все ниже здесь устарело с XCode 10 и iOS 4.2

Swift 4.0 В Swift 4.0 Apple рекомендовала нам избегать использования #selector, поэтому в этом подходе теперь используется блок завершения. Этот подход также обратно совместим со Swift 3.x и будет рекомендованным подходом в будущем.

Это предупреждение компилятора, которое вы получите в проекте Swift 4.x, если вы используете #selectorфункцию из-за устаревания @objcвывода:

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

Вступление в стремительную эволюцию по этому изменению .

Настройте обратный вызов:

// If you do not use the notification var in your callback, 
// you can safely replace it with _
    var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other")
        }
    }

Настройте уведомление:

NotificationCenter.default.addObserver(forName: .UIDeviceOrientationDidChange,
                                       object: nil,
                                       queue: .main,
                                       using: didRotate)

Снеси его:

NotificationCenter.default.removeObserver(self, name: .UIDeviceOrientationDidChange, object: nil)

4
Есть ли у вас ссылки на то, где Apple говорит о нежелании использовать #selector в Swift 4? Я хотел бы прочитать о том, почему они так говорят.
Jeffjv

@jeffjv Конечно, у меня нет прямой ссылки на документ Apple, но я включил снимок экрана с предупреждением компилятора, которое предоставляет XCode, если вы используете предыдущий подход.
CodeBender

1
Я добавил ссылку на swift-evolution, в которой обсуждаются изменения.
CodeBender

1
@CodeBender: предупреждение компилятора не означает, что вы предлагаете. #selector не амортизируется, только вывод "@objc". Это означает, что при использовании функции в качестве #selector вы должны явно пометить ее, чтобы компилятор сгенерировал дополнительный правильный код, потому что компилятор больше не будет пытаться вывести ее из вашего использования. Следовательно, если вы добавите «@obj» к своей функции rotate () в своем решении Swift 3.0, код будет скомпилирован без предупреждения.
Mythlandia

Спасибо @Mythlandia, я обновил ответ, чтобы разрешить путаницу с моим первоначальным утверждением.
CodeBender

19

Использование -orientationсвойства of UIDeviceявляется неправильным (даже если оно может работать в большинстве случаев) и может привести к некоторым ошибкам, например, UIDeviceOrientationучитывайте также ориентацию устройства, если оно направлено вверх или вниз, UIInterfaceOrientationдля перечисления нет прямой пары в перечислении. ценности.
Кроме того, если вы заблокируете свое приложение в какой-то определенной ориентации, UIDevice предоставит вам ориентацию устройства без учета этого.
С другой стороны iOS8 устарела interfaceOrientationсобственность на UIViewControllerкласс.
Для определения ориентации интерфейса доступно 2 варианта:

  • Использовать ориентацию строки состояния
  • Используйте классы размеров, на iPhone, если они не переопределены, они могут дать вам возможность понять текущую ориентацию интерфейса

Чего еще не хватает, так это способа понять направление изменения ориентации интерфейса, что очень важно во время анимации.
На сессии WWDC 2014 «Просмотр продвижения контроллера в iOS8» докладчик также предлагает решение этой проблемы, используя заменяющий метод -will/DidRotateToInterfaceOrientation.

Здесь предлагаемое решение частично реализовано, подробнее здесь :

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        let orientation = orientationFromTransform(coordinator.targetTransform())
        let oldOrientation = UIApplication.sharedApplication().statusBarOrientation
        myWillRotateToInterfaceOrientation(orientation,duration: duration)
        coordinator.animateAlongsideTransition({ (ctx) in
            self.myWillAnimateRotationToInterfaceOrientation(orientation,
            duration:duration)
            }) { (ctx) in
                self.myDidAnimateFromInterfaceOrientation(oldOrientation)
        }
    }

11

Я знаю, что этот вопрос для Swift, но так как это одна из главных ссылок для поиска Google, и если вы ищете тот же код в Objective-C:

// add the observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rotated:) name:UIDeviceOrientationDidChangeNotification object:nil];

// remove the observer
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];

// method signature
- (void)rotated:(NSNotification *)notification {
    // do stuff here
}

11

Просто, это работает в iOS8 и 9 / Swift 2 / Xcode7, просто поместите этот код в ваш viewcontroller.swift. Он будет печатать размеры экрана при каждом изменении ориентации, вместо этого вы можете указать свой собственный код:

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
        getScreenSize()
    }
    var screenWidth:CGFloat=0
    var screenHeight:CGFloat=0
    func getScreenSize(){
        screenWidth=UIScreen.mainScreen().bounds.width
        screenHeight=UIScreen.mainScreen().bounds.height
        print("SCREEN RESOLUTION: "+screenWidth.description+" x "+screenHeight.description)
    }

5
Эта функция устарела, используйте вместо нее «viewWillTransitionToSize: withTransitionCoordinator:»
Masa S-AiYa

Помимо того, что устарел, didRotateFromInterfaceOrientation()не работает надежно. Он пропускает некоторые повороты. iewWillTransitionToSize:withTransitionCoordinator:работает нормально
Андрей

@ Andrej Многие вещи теперь устарели благодаря Swift 3
Джош


9

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

SWIFT 5:

    //ask the system to start notifying when interface change
    UIDevice.current.beginGeneratingDeviceOrientationNotifications()
    //add the observer
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(orientationChanged(notification:)),
        name: UIDevice.orientationDidChangeNotification,
        object: nil)

чем кэширование уведомления

    @objc func orientationChanged(notification : NSNotification) {
        //your code there
    }

8

В Задаче С

-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

В быстром

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

Переопределите этот метод, чтобы обнаружить изменение ориентации.


8

Свифт 3 | Уведомление UIDeviceOrientationDidChange наблюдается слишком часто

Следующий код печатает «deviceDidRotate» каждый раз, когда ваше устройство меняет ориентацию в трехмерном пространстве - независимо от перехода из портретной в альбомную ориентацию. Например, если вы держите свой телефон в портретной ориентации и наклоняете его вперед и назад - deviceDidRotate () вызывается повторно.

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}

Чтобы обойти это, вы можете сохранить предыдущую ориентацию устройства и проверить наличие изменений в deviceDidRotate ().

var previousDeviceOrientation: UIDeviceOrientation = UIDevice.current.orientation

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    if UIDevice.current.orientation == previousDeviceOrientation { return }
    previousDeviceOrientation = UIDevice.current.orientation
    print("deviceDidRotate")
}

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

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIApplicationDidChangeStatusBarOrientation, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}

6

Полная рабочая реализация того, как обнаружить изменение ориентации в Swift 3.0.

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

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //1
        NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

    }

    deinit {
        //3
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    func deviceOrientationDidChange() {
        //2
        switch UIDevice.current.orientation {
        case .faceDown:
            print("Face down")
        case .faceUp:
            print("Face up")
        case .unknown:
            print("Unknown")
        case .landscapeLeft:
            print("Landscape left")
        case .landscapeRight:
            print("Landscape right")
        case .portrait:
            print("Portrait")
        case .portraitUpsideDown:
            print("Portrait upside down")
        }
    }

}

Важные моменты, на которые следует обратить внимание:

  1. Вы слушаете поток уведомлений DeviceOrientationDidChange и связываете его с функцией deviceOrientationDidChange
  2. Затем вы включаете ориентацию устройства, обязательно обратите внимание, что время от времени unknownориентация есть.
  3. Как и любое уведомление, перед деинициализацией viewController обязательно прекратите наблюдать поток уведомлений.

Надеюсь, кто-то найдет это полезным.


Спасибо, отлично
Мухаммед Разипур

Так что я в замешательстве, в настоящее время все мои виды настраиваются в зависимости от портрета и пейзажа, но не меняется ли устройство лицом вверх? Как бы я использовал ваш код выше, чтобы добиться того же эффекта, когда его лицом вверх !?
Famic Tech

Можете ли вы дать немного больше контекста? Я не уверен, какой эффект вы имеете в виду. Этот код работает просто для определения ориентации. Если вы используете несколько методов определения ориентации, у вас могут возникнуть проблемы.
Роб Норбак

6
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
    //swift 3
    getScreenSize()
}


func getScreenSize(){
   let screenWidth = UIScreen.main.bounds.width
   let  screenHeight = UIScreen.main.bounds.height
    print("SCREEN RESOLUTION: \(screenWidth.description) x \(screenHeight.description)")
}

Самый чистый ответ. Работал на меня.
Дорад

Сейчас это устарело
Азиз Джавед

5

Если вы хотите сделать что-то ПОСЛЕ того, как вращение завершено, вы можете использовать UIViewControllerTransitionCoordinatorобработчик завершения следующим образом

public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    // Hook in to the rotation animation completion handler
    coordinator.animate(alongsideTransition: nil) { (_) in
        // Updates to your UI...
        self.tableView.reloadData()
    }
}

5

Начиная с iOS 8 это правильный способ сделать это.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    coordinator.animate(alongsideTransition: { context in
        // This is called during the animation
    }, completion: { context in
        // This is called after the rotation is finished. Equal to deprecated `didRotate`
    })
}

4

Проверьте, изменилось ли вращение с: viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

С coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext) вы можете проверить, завершен ли переход.

Смотрите код ниже:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {

    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext) in
        // if you want to execute code after transition finished
        print("Transition finished")
    }

    if size.height < size.width {
        // Landscape
        print("Landscape")
    } else {
        // Portrait
        print("Portrait")
    }

}

4

Вот простой способ определить ориентацию устройства: ( Swift 3 )

override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
            handleViewRotaion(orientation: toInterfaceOrientation)
        }

    //MARK: - Rotation controls
    func handleViewRotaion(orientation:UIInterfaceOrientation) -> Void {
        switch orientation {
        case .portrait :
            print("portrait view")
            break
        case .portraitUpsideDown :
            print("portraitUpsideDown view")
            break
        case .landscapeLeft :
            print("landscapeLeft view")
            break
        case .landscapeRight :
            print("landscapeRight view")
            break
        case .unknown :
            break
        }
    }

2
willRotateустарела, лучше использовать viewWillTransition.
до

4

Свифт 4:

override func viewWillAppear(_ animated: Bool) {
    NotificationCenter.default.addObserver(self, selector: #selector(deviceRotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}

@objc func deviceRotated(){
    if UIDevice.current.orientation.isLandscape {
        //Code here
    } else {
        //Code here
    }
}

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


Вам также необходимо проверить, было ли устройство повернуто, когда контроллер вида исчез (в случае, если другой контроллер вида открыт выше текущего). На самом деле, вы можете пропустить viewWillDisappearи добавить addObserverв viewDidLoad. iOS отписывается от просмотра контроллера автоматически.
Александр Волков

Вы забылиUIDevice.current.orientation.isFlat
user924

3

Мой подход подобен тому, что показывает bpedit выше, но с фокусом на iOS 9+. Я хотел изменить область FSCalendar, когда представление вращается.

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.setScope(.Week, animated: true)
            self.calendar.appearance.cellShape = .Rectangle
        }
        else {
            self.calendar.appearance.cellShape = .Circle
            self.calendar.setScope(.Month, animated: true)

        }

        }, completion: nil)
}

Это ниже работало, но я чувствовал себя застенчиво об этом :)

coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.scope = .Week
            self.calendar.appearance.cellShape = .Rectangle
        }
        }) { (context) in
            if size.height > size.width {
                self.calendar.scope = .Month
                self.calendar.appearance.cellShape = .Circle
            }
    }

2

Я использую, UIUserInterfaceSizeClassчтобы определить ориентацию, измененную в UIViewControllerклассе, вот так:

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {

    let isiPadLandscapePortrait = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .regular
    let isiPhonePlustLandscape = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .compact
    let isiPhonePortrait = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .regular
    let isiPhoneLandscape = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .compact

     if isiPhonePortrait {
         // do something...
     }
}

1

Для Swift 3

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        //Landscape
    }
    else if UIDevice.current.orientation.isFlat {
        //isFlat
    }
    else {
        //Portrait
    }
}

Вы забылиUIDevice.current.orientation.isFlat
user924

1

Swift 5

Настройка класса для получения уведомлений об изменении ориентации устройства:

class MyClass {

    ...

    init (...) {

        ...

        super.init(...)

        // subscribe to device orientation change notifications
        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
        NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil)

        ...

    }

    ...

}

Код обработчика установки:

@objc extension MyClass {
    func orientationChanged(_ notification: NSNotification) {
        let device = notification.object as! UIDevice
        let deviceOrientation = device.orientation

        switch deviceOrientation {
        case .landscapeLeft:   //do something for landscape left
        case .landscapeRight:  //do something for landscape right
        case .portrait:        //do something for portrait
        case .portraitUpsideDown: //do something for portrait upside-down 
        case .faceDown:        //do something for face down
        case .faceUp:          //do something for face up
        case .unknown:         //handle unknown
        @unknown default:      //handle unknown default
        }
    }
}

0
- (void)viewDidLoad {
  [super viewDidLoad];
  [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}

-(void)OrientationDidChange:(NSNotification*)notification {
  UIDeviceOrientation Orientation=[[UIDevice currentDevice]orientation];

  if(Orientation==UIDeviceOrientationLandscapeLeft || Orientation==UIDeviceOrientationLandscapeRight) {
    NSLog(@"Landscape");
  } else if(Orientation==UIDeviceOrientationPortrait) {
    NSLog(@"Potrait Mode");
  }
}

ПРИМЕЧАНИЕ. Просто используйте этот код, чтобы определить, в каком направлении находится UIViewController.


Ваш код не работает в проекте. мне нужно что-то еще делать?
Сайед Али Салман

0
override func viewDidLoad() {
    NotificationCenter.default.addObserver(self, selector: #selector(MyController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
//...
}

@objc
private func rotated() {
    if UIDevice.current.orientation.isLandscape {

    } else if UIDevice.current.orientation.isPortrait {

    }

    //or you can check orientation separately UIDevice.current.orientation
    //portrait, portraitUpsideDown, landscapeLeft, landscapeRight... 

}

0

В iOS 13.1.2 ориентация всегда возвращает 0, пока устройство не будет повернуто. Мне нужно вызвать UIDevice.current.beginGeneratingDeviceOrientationNotifications (), чтобы получить фактическое вращение до того, как произойдет любое событие поворота.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.