Swift - Как определить изменение ориентации


96

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

Я пробовал этот ответ, но требуется только одно изображение

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.currentDevice().orientation.isLandscape.boolValue {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

Я новичок в разработке iOS, буду благодарен за любой совет!


1
Не могли бы вы отредактировать свой вопрос и отформатировать код? Вы можете сделать это с помощью cmd + k, когда у вас выбран код.
— jbehrens94

2
проверьте это stackoverflow.com/questions/25666269/…
— DSAjit

Правильно ли печатается альбомная / портретная ориентация?
— Дэниел

да он печатает правильно @simpleBob
— Raghuram

Вы должны использовать ориентацию интерфейса вместо ориентации устройства => stackoverflow.com/a/60577486/8780127
— Вилфрид Джоссет,

Ответы:


121
let const = "Background" //image name
let const2 = "GreyBackground" // image name
    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.image = UIImage(named: const)
        // Do any additional setup after loading the view.
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        if UIDevice.current.orientation.isLandscape {
            print("Landscape")
            imageView.image = UIImage(named: const2)
        } else {
            print("Portrait")
            imageView.image = UIImage(named: const)
        }
    }

Спасибо, это работает для imageView, что, если я хочу добавить фоновое изображение в viewController ??
— Рагурам,

1
Поместите imageView на фон. Задайте ограничение для imageView для верхнего, нижнего, ведущего, замыкающего основного представления ViewController, чтобы покрыть весь фон.
— Рутвик Канбарги

4
Не забудьте вызвать super.viewWillTransitionToSize в своем методе переопределения
— Брайан Сачетта,

Вы знаете, почему я не могу переопределить viewWillTransitionпри создании подклассов UIView?
— Xcoder

2
Существует еще один тип ориентации: .isFlat. Если вы хотите только поймать, portraitя предлагаю вам изменить предложение else на else if UIDevice.current.orientation.isPortrait. Примечание: .isFlatможет происходить как в портретной, так и в альбомной ориентации, и только с помощью else {} он будет использоваться по умолчанию всегда (даже если это плоский пейзаж).
— PMT

78

Использование NotificationCenter и UIDevice «SbeginGeneratingDeviceOrientationNotifications

Swift 4.2+

override func viewDidLoad() {
    super.viewDidLoad()        

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

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

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

Swift 3

override func viewDidLoad() {
    super.viewDidLoad()        

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}

deinit {
     NotificationCenter.default.removeObserver(self)
}

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

1
Определенно - мой подход требует дополнительного кода. Но действительно «обнаруживает изменения ориентации».
— maslovsa

3
@Michael Потому что viewWillTransition:ожидает, что изменение произойдет при использовании, UIDeviceOrientationDidChangeвызывается после завершения изменения.
— Линдси Скотт

1
@LyndseyScott Я по-прежнему считаю, что описанного поведения можно добиться без использования потенциально опасного NSNotificationCenter. Просмотрите следующий ответ и дайте мне знать свои мысли: stackoverflow.com/a/26944087/1306884
— Майкл

4
Что интересно в этом ответе, так это то, что он даст текущую ориентацию, даже если для контроллера shouldAutorotateпредставления установлено значение false. Это удобно для таких экранов, как главный экран приложения «Камера» - ориентация заблокирована, но кнопки могут вращаться.
— yoninja

1
Начиная с iOS 9 вам даже не нужно явно вызывать deinit. Вы можете узнать больше об этом здесь: useyourloaf.com/blog/…
— Джеймс Джордан Тейлор

63

Swift 3 Код выше обновлен:

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

    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

1
Вы знаете, почему я не могу переопределить viewWillTransition при создании подкласса UIView?
— Xcoder

Это приведет к сбою, если вы попытаетесь сослаться на любое из представлений в контроллере.
— Джеймс Джордан Тейлор

@JamesJordanTaylor, не могли бы вы объяснить, почему он выйдет из строя?
— orium

Это было 6 месяцев назад, когда я прокомментировал, но я думаю, что причина, по которой Xcode привел, когда я попробовал, была исключение нулевого указателя, потому что само представление еще не было создано, а только viewController. Хотя я мог не вспомнить.
— Джеймс Джордан Тейлор

@Xcoder - это функция UIViewController, а не UIView - поэтому вы не можете ее переопределить.
— LightningStryk

25

⚠️Device Orientation! = Ориентация интерфейса⚠️

Swift 5. * iOS14 и ниже

Вам действительно стоит различать:

  • Ориентация устройства => Указывает ориентацию физического устройства
  • Ориентация интерфейса => Указывает ориентацию интерфейса, отображаемого на экране

Существует множество сценариев, в которых эти 2 значения не совпадают, например:

  • Когда вы блокируете ориентацию экрана
  • Когда у вас плоское устройство

В большинстве случаев вы захотите использовать ориентацию интерфейса, и вы можете получить ее через окно:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
}

Если вы также хотите поддерживать <iOS 13 (например, iOS 12), вы должны сделать следующее:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    if #available(iOS 13.0, *) {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    } else {
        return UIApplication.shared.statusBarOrientation
    }
}

Теперь нужно определить, где реагировать на изменение ориентации интерфейса окна. Есть несколько способов сделать это, но оптимальное решение - сделать это внутри willTransition(to newCollection: UITraitCollection.

Этот унаследованный метод UIViewController, который можно переопределить, будет срабатывать каждый раз при изменении ориентации интерфейса. Следовательно, вы можете делать все свои модификации в последнем.

Вот пример решения :

class ViewController: UIViewController {
    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)
        
        coordinator.animate(alongsideTransition: { (context) in
            guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }
            
            if windowInterfaceOrientation.isLandscape {
                // activate landscape changes
            } else {
                // activate portrait changes
            }
        })
    }
    
    private var windowInterfaceOrientation: UIInterfaceOrientation? {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    }
}

Реализовав этот метод, вы сможете реагировать на любое изменение ориентации вашего интерфейса. Но имейте в виду, что он не будет запускаться при открытии приложения, поэтому вам также придется вручную обновить свой интерфейс в viewWillAppear().

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

Не стесняйтесь клонировать и запускать следующий репозиторий: https://github.com/wjosset/ReactToOrientation


как это сделать до iOS 13?
— Дэниел Спрингер

1
@DanielSpringer, спасибо за ваш комментарий, я отредактировал сообщение для поддержки iOS 12 и ниже
— Вилфрид Джоссет

1
Я считаю этот ответ самым лучшим и информативным. Но при тестировании на iPadOS13.5 использование предложенного func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)не помогло . Я заставил это работать, используя func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
— Андрей

12

Swift 4+: я использовал это для дизайна мягкой клавиатуры, и по какой-то причине UIDevice.current.orientation.isLandscapeметод продолжал говорить мне, что это Portraitтак, поэтому вот что я использовал вместо этого:

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

    if(size.width > self.view.frame.size.width){
        //Landscape
    }
    else{
        //Portrait
    }
}

ну, у меня есть аналогичная проблема в моем приложении iMessage. Разве это не странно? И даже ваше решение работает наоборот !! ¯_ (ツ) _ / ¯
— Шьям

Не используйте UIScreen.main.bounds, потому что он может дать вам границы экрана до перехода (обратите внимание на «Воля» в методе), особенно при нескольких быстрых поворотах. Вы должны использовать параметр size ...
— Дэн Боднар

@ dan-bodnar Вы имеете в виду параметр nativeBounds?
— asetniop

3
@asetniop, нет. sizeПараметр , который вводится в viewWillTransitionToSize: withCoordinator: метод , который вы написали выше. Это будет отражать точный размер вашего представления после перехода.
— Дэн Боднар

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

8

Swift 4.2, RxSwift

Если нам нужно перезагрузить collectionView.

NotificationCenter.default.rx.notification(UIDevice.orientationDidChangeNotification)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)

Swift 4, RxSwift

Если нам нужно перезагрузить collectionView.

NotificationCenter.default.rx.notification(NSNotification.Name.UIDeviceOrientationDidChange)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)

5

Я считаю , что правильный ответ на самом деле является сочетание обоих подходов: viewWIllTransition(toSize:)и NotificationCenter«s UIDeviceOrientationDidChange.

viewWillTransition(toSize:)уведомляет вас перед переходом.

NotificationCenter UIDeviceOrientationDidChangeуведомит вас после .

Вы должны быть очень осторожны. Например, в UISplitViewControllerпри вращении устройства в определенные ориентации, то DetailViewControllerполучает выталкивается из UISplitViewControllerS» viewcontrollersмассив, и надевается на магистр UINavigationController. Если вы попытаетесь найти контроллер подробного представления до завершения вращения, он может не существовать и выйдет из строя.


5

Если вы используете Swift версии> = 3.0, вы должны применить некоторые обновления кода, как уже говорили другие. Только не забудьте позвонить super:

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

   super.viewWillTransition(to: size, with: coordinator)

   // YOUR CODE OR FUNCTIONS CALL HERE

}

Если вы думаете использовать StackView для своих изображений, имейте в виду, что вы можете сделать что-то вроде следующего:

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

   super.viewWillTransition(to: size, with: coordinator)

   if UIDevice.current.orientation.isLandscape {

      stackView.axis = .horizontal

   } else {

      stackView.axis = .vertical

   } // else

}

Если вы используете Interface Builder, не забудьте выбрать пользовательский класс для этого объекта UIStackView в разделе Identity Inspector на правой панели. Затем просто создайте (также через Interface Builder) ссылку IBOutlet на пользовательский экземпляр UIStackView:

@IBOutlet weak var stackView: MyStackView!

Возьмите идею и адаптируйте ее под свои нужды. Надеюсь, это поможет вам!


Хорошее замечание о том, чтобы назвать супер Не вызывать супер-метод в переопределенной функции - действительно небрежное кодирование, и это может привести к непредсказуемому поведению в приложениях. И потенциально ошибки, которые действительно сложно отследить!
— Адам Фриман

Вы знаете, почему я не могу переопределить viewWillTransition при создании подкласса UIView?
— Xcoder

@Xcoder Причина (обычно) того, что объект не применяет переопределение, заключается в том, что экземпляр среды выполнения был создан не с помощью настраиваемого класса, а вместо этого со значением по умолчанию. Если вы используете Interface Builder, убедитесь, что выбрали настраиваемый класс для этого объекта UIStackView в разделе Identity Inspector на правой панели.
— WeiseRatel 01

5

Swift 4

У меня возникли незначительные проблемы при обновлении представления ViewControllers UIDevice.current.orientation, например, при обновлении ограничений ячеек табличного представления во время вращения или анимации подпредставлений.

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

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    print("Will Transition to size \(size) from super view size \(self.view.frame.size)")

    if (size.width > self.view.frame.size.width) {
        print("Landscape")
    } else {
        print("Portrait")
    }

    if (size.width != self.view.frame.size.width) {
        // Reload TableView to update cell's constraints.
    // Ensuring no dequeued cells have old constraints.
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }


}

Вывод на iPhone 6:

Will Transition to size (667.0, 375.0) from super view size (375.0, 667.0) 
Will Transition to size (375.0, 667.0) from super view size (667.0, 375.0)

Должен ли я включать поддержку ориентации на уровне проекта?
— Ericpoon

3

Все предыдущие предложения хороши, но небольшое примечание:

а) если ориентация установлена в PLIST, только портрет или , например, Вы будете не уведомлены через viewWillTransition

б) если нам все равно нужно знать, повернул ли пользователь устройство (например, игра или подобное ..), мы можем использовать только:

NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

протестировано на Xcode8, iOS11


2

Вы можете использовать viewWillTransition(to:with:)и нажать, animate(alongsideTransition:completion:)чтобы получить ориентацию интерфейса ПОСЛЕ завершения перехода. Вам просто нужно определить и реализовать протокол, подобный этому, чтобы подключиться к событию. Обратите внимание, что этот код использовался для игры SpriteKit, и ваша конкретная реализация может отличаться.

protocol CanReceiveTransitionEvents {
    func viewWillTransition(to size: CGSize)
    func interfaceOrientationChanged(to orientation: UIInterfaceOrientation)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        guard
            let skView = self.view as? SKView,
            let canReceiveRotationEvents = skView.scene as? CanReceiveTransitionEvents else { return }

        coordinator.animate(alongsideTransition: nil) { _ in
            if let interfaceOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation {
                canReceiveRotationEvents.interfaceOrientationChanged(to: interfaceOrientation)
            }
        }

        canReceiveRotationEvents.viewWillTransition(to: size)
    }

Вы можете установить точки останова в этих функциях и наблюдать, что они interfaceOrientationChanged(to orientation: UIInterfaceOrientation)всегда вызываются после viewWillTransition(to size: CGSize)с обновленной ориентацией.


1

Чтобы получить правильную ориентацию при запуске приложения, вы должны его проверить viewDidLayoutSubviews(). Другие описанные здесь методы не работают.

Вот пример того, как это сделать:

var mFirstStart = true

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if (mFirstStart) {
        mFirstStart = false
        detectOrientation()
    }
}

func detectOrientation() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
        // do your stuff here for landscape
    } else {
        print("Portrait")
        // do your stuff here for portrait
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    detectOrientation()
}

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


0

Другой способ определить ориентацию устройства - использовать функцию traitCollectionDidChange (_ :). Система вызывает этот метод при изменении среды интерфейса iOS.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
    super.traitCollectionDidChange(previousTraitCollection)
    //...
}

Кроме того, вы можете использовать функцию willTransition (to: with :) (которая вызывается перед traitCollectionDidChange (_ :)), чтобы получить информацию непосредственно перед применением ориентации.

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.