Свіфт - Як виявити зміни орієнтації


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


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

та він друкує правильно @simpleBob
Raghuram

Ви повинні використовувати орієнтацію інтерфейсу замість орієнтації пристрою => stackoverflow.com/a/60577486/8780127
Вільфрід Josset

Відповіді:


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може траплятися як у книжковому, так і в альбомному режимі, а просто з іншим {} він завжди буде за замовчуванням (навіть якщо це плоский пейзаж).
ПМТ

78

Використання NotificationCenter і UIDevice «SbeginGeneratingDeviceOrientationNotifications

Свіфт 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")
    }
}

Стрімкий 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, викликається, як тільки зміна буде завершена.
Lyndsey Scott

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

4
Що цікаво у цій відповіді, це те, що це дасть поточну орієнтацію, навіть коли для контролера перегляду shouldAutorotateвстановлено значення false. Це зручно для таких екранів, як головний екран програми Camera - орієнтація заблокована, але кнопки можуть обертатися.
yoninja

1
Починаючи з iOS 9, вам навіть не потрібно явно викликати deinit. Детальніше про це ви можете прочитати тут: useyourloaf.com/blog/…
Джеймс Джордан Тейлор

63

Swift 3 Above code updated:

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, чи не могли б Ви пояснити, чому він впаде?
оріум

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

@Xcoder це функція на UIViewController, а не UIView - ось чому ви не можете її замінити.
LightningStryk

24

⚠️Орієнтація на пристрій! = Орієнтація на інтерфейс⚠️

Свіфт 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

Свіфт 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: метод , який ви написали вище. Це відображатиме точний розмір вашого перегляду після переходу.
Ден Боднар

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

8

Свіфт 4.2, RxSwift

Якщо нам потрібно перезавантажити collectionView.

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

Свіфт 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, є деякі оновлення коду, які ви повинні застосувати, як вже говорили інші. Тільки не забудьте зателефонувати супер:

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, у розділі Інспектор посвідчень на правій панелі. Потім просто створіть (також через Interface Builder) посилання IBOutlet на власний екземпляр UIStackView:

@IBOutlet weak var stackView: MyStackView!

Візьміть ідею та адаптуйте її до своїх потреб. Сподіваюся, це може вам допомогти!


Хороший момент щодо дзвінка супер. Не викликати неабиякого кодування, якщо не викликати метод super у перевизначеній функції, і це може спричинити непередбачувану поведінку в додатках. І потенційно помилки, які насправді важко відстежити!
Адам Фрімен,

Чи знаєте ви, чому я не можу замінити viewWillTransition під час підкласифікації UIView?
Xcoder

@Xcoder Причина (зазвичай) того, що об'єкт не застосовує перевизначення, полягає в екземплярі середовища виконання не за допомогою спеціального класу, а за замовчуванням. Якщо ви використовуєте Interface Builder, переконайтеся, що вибрали спеціальний клас для цього об’єкта UIStackView у розділі Інспектор посвідчень на правій панелі.
WeiseRatel

5

Стрімкий 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.