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получает выталкивается из UISplitViewControllerviewcontrollersмассив, и надевается на магистр UINavigationController. Если вы попытаетесь найти контроллер подробного представления до завершения вращения, он может не существовать и выйдет из строя.

MH175
источник
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!

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

WeiseRatel
источник
Хорошее замечание о том, чтобы назвать супер Не вызывать супер-метод в переопределенной функции - действительно небрежное кодирование, и это может привести к непредсказуемому поведению в приложениях. И потенциально ошибки, которые действительно сложно отследить!
Адам Фриман
Вы знаете, почему я не могу переопределить 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)
RLoniello
источник
Должен ли я включать поддержку ориентации на уровне проекта?
Ericpoon
3

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

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

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

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

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

Ingconti
источник
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()
}

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

Lenooh
источник
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)
    //...
}
ckc
источник