Swift - Jak wykryć zmiany orientacji

96

Chcę dodać dwa obrazy do widoku pojedynczego obrazu (np. Jeden obraz poziomy i inny obraz pionowy), ale nie wiem, jak wykryć zmiany orientacji przy użyciu szybkich języków.

Wypróbowałem tę odpowiedź, ale wystarczy jedno zdjęcie

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

Jestem nowy w rozwoju iOS, każda rada byłaby bardzo mile widziana!

Raghuram
źródło
1
Czy mógłbyś edytować swoje pytanie i sformatować kod? Możesz to zrobić za pomocą cmd + k, gdy masz wybrany kod.
jbehrens94
Czy prawidłowo drukuje krajobraz / portret?
Daniel,
tak, drukuje poprawnie @simpleBob
Raghuram.
Powinieneś użyć orientacji interfejsu zamiast orientacji urządzenia => stackoverflow.com/a/60577486/8780127
Wilfried Josset

Odpowiedzi:

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)
        }
    }
Rutvik Kanbargi
źródło
Dziękuję, działa dla imageView, a co jeśli chcę dodać obraz tła do viewController?
Raghuram
1
Umieść imageView w tle. Daj ograniczenie do imageView dla górnego, dolnego, wiodącego i końcowego widoku głównego ViewController, aby pokryć całe tło.
Rutvik Kanbargi
4
Nie zapomnij zadzwonić do super.viewWillTransitionToSize w ramach metody nadpisywania
Brian Sachetta
Czy wiesz, dlaczego nie mogę nadpisać viewWillTransitionpodklasy UIView?
Xcoder,
2
Jest jeszcze inny rodzaj orientacji: .isFlat. Jeśli chcesz tylko złapać portrait, sugeruję zmianę klauzuli else na else if UIDevice.current.orientation.isPortrait. Uwaga: .isFlatmoże się zdarzyć zarówno w orientacji pionowej, jak i poziomej, aw przypadku just else {} zawsze będzie tam domyślnie (nawet jeśli jest to płaski krajobraz).
PMT
78

Korzystanie NotificationCenter and 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")
    }
}

Szybki 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")
    }
}
maslovsa
źródło
1
Zdecydowanie - moje podejście wymaga dodatkowego kodu. Ale tak naprawdę „wykrywa zmiany orientacji”.
maslovsa
3
@Michael Ponieważ viewWillTransition:przewiduje, że zmiana nastąpi, podczas używania UIDeviceOrientationDidChangejest wywoływana po zakończeniu zmiany.
Lyndsey Scott
1
@LyndseyScott Nadal uważam, że opisane zachowanie można osiągnąć bez użycia potencjalnie niebezpiecznego NSNotificationCenter. Zapoznaj się z następującą odpowiedzią i daj mi znać, co myślisz: stackoverflow.com/a/26944087/1306884
Michael
4
Interesujące w tej odpowiedzi jest to, że da to aktualną orientację nawet wtedy, gdy kontroler widoku shouldAutorotatejest ustawiony na false. Jest to przydatne na ekranach, takich jak główny ekran aplikacji Aparat - orientacja jest zablokowana, ale przyciski mogą się obracać.
yoninja
1
Od iOS 9 nie musisz nawet jawnie wywoływać deinit. Możesz przeczytać więcej na ten temat tutaj: useyourloaf.com/blog/…
James Jordan Taylor
63

Swift 3 Powyższy kod zaktualizowany:

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")
    }
}
Yaroslav Bai
źródło
1
Czy wiesz, dlaczego nie mogę przesłonić viewWillTransition podczas tworzenia podklasy UIView?
Xcoder,
Spowoduje to awarię, jeśli spróbujesz odwołać się do któregokolwiek z widoków w kontrolerze.
James Jordan Taylor,
@JamesJordanTaylor czy mógłbyś wyjaśnić, dlaczego się zawiesi?
orium
To było 6 miesięcy temu, kiedy skomentowałem, ale myślę, że powodem, dla którego Xcode podał, gdy próbowałem, był wyjątek wskaźnika zerowego, ponieważ sam widok nie został jeszcze utworzony, tylko viewController. Mogłem jednak źle pamiętać.
James Jordan Taylor,
@Xcoder to jest funkcja na UIViewController, a nie UIView - dlatego nie możesz jej nadpisać.
LightningStryk
25

⚠️ Device Orientation! = Interface Orientation⚠️

Swift 5. * iOS14 i starsze

Naprawdę powinieneś zrobić różnicę między:

  • Device Orientation => Wskazuje orientację fizycznego urządzenia
  • Orientacja interfejsu => Wskazuje orientację interfejsu wyświetlanego na ekranie

Istnieje wiele scenariuszy, w których te dwie wartości są niezgodne, na przykład:

  • Gdy zablokujesz orientację ekranu
  • Kiedy masz urządzenie płaskie

W większości przypadków chciałbyś użyć orientacji interfejsu i możesz to uzyskać za pomocą okna:

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

Jeśli chcesz również obsługiwać <iOS 13 (na przykład iOS 12), wykonaj następujące czynności:

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

Teraz musisz zdefiniować, gdzie zareagować na zmianę orientacji interfejsu okna. Można to zrobić na wiele sposobów, ale optymalnym rozwiązaniem jest zrobienie tego w ramach willTransition(to newCollection: UITraitCollection.

Ta odziedziczona metoda UIViewController, którą można przesłonić, zostanie wyzwolona za każdym razem, gdy orientacja interfejsu zostanie zmieniona. W konsekwencji możesz wprowadzić wszystkie swoje modyfikacje w tym drugim.

Oto przykład rozwiązania :

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
    }
}

Wdrażając tę ​​metodę, będziesz mógł reagować na każdą zmianę orientacji interfejsu. Pamiętaj jednak, że nie zostanie to uruchomione po otwarciu aplikacji, więc będziesz musiał również ręcznie zaktualizować interfejs w viewWillAppear().

Stworzyłem przykładowy projekt, który podkreśla różnicę między orientacją urządzenia a orientacją interfejsu. Dodatkowo pomoże Ci zrozumieć różne zachowania w zależności od tego, który etap cyklu życia zdecydujesz się zaktualizować swój interfejs użytkownika.

Zapraszam do sklonowania i uruchomienia następującego repozytorium: https://github.com/wjosset/ReactToOrientation

Wilfried Josset
źródło
jak to zrobić przed iOS 13?
Daniel Springer
1
@DanielSpringer dziękuję za komentarz, zredagowałem post, aby obsługiwał iOS 12 i
starsze
1
Uważam, że ta odpowiedź jest najlepsza i najbardziej pouczająca. Ale testowanie na iPadOS13.5 sugerowane func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)nie zadziałało. Zrobiłem to za pomocą func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
Andrej
12

Swift 4+: Używałem tego do projektowania miękkiej klawiatury iz jakiegoś powodu UIDevice.current.orientation.isLandscapemetoda powtarzała mi, że Portraittak, więc oto, czego użyłem:

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
    }
}
asetniop
źródło
cóż, mam podobny problem w mojej aplikacji iMessage. Czy to naprawdę nie jest dziwne? Nawet Twoje rozwiązanie działa odwrotnie! ¯_ (ツ) _ / ¯
Shyam
Nie używaj UIScreen.main.bounds, ponieważ może to dać ci granice ekranu przed przejściem (pamiętaj o 'Will' w metodzie), szczególnie przy wielu szybkich obrotach. Powinieneś użyć parametru „rozmiar” ...
Dan Bodnar,
@ dan-bodnar Czy masz na myśli parametr nativeBounds?
asetniop
3
@asetniop, nie. sizeParametrem, który jest wtryskiwany w viewWillTransitionToSize: withCoordinator: metoda napisałeś powyżej. Będzie to odzwierciedlać dokładny rozmiar, jaki będzie miał Twój widok po przejściu.
Dan Bodnar,
Nie jest to dobre rozwiązanie, jeśli używasz podrzędnych kontrolerów widoku, rozmiar kontenera może być zawsze kwadratowy, niezależnie od orientacji.
bojan
8

Swift 4.2, RxSwift

Jeśli musimy ponownie załadować collectionView.

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

Swift 4, RxSwift

Jeśli musimy ponownie załadować collectionView.

NotificationCenter.default.rx.notification(NSNotification.Name.UIDeviceOrientationDidChange)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)
maslovsa
źródło
5

Wierzę, że prawidłowa odpowiedź jest faktycznie połączenie obu podejść: viewWIllTransition(toSize:)a NotificationCenter„s UIDeviceOrientationDidChange.

viewWillTransition(toSize:)powiadamia Cię przed przejściem.

NotificationCenter UIDeviceOrientationDidChangepowiadomi Cię po .

Musisz bardzo uważać. Na przykład, UISplitViewControllergdy obraca się urządzenie do pewnych założeniach, DetailViewControllerzostaje zdejmowana z UISplitViewController„S viewcontrollerstablicy i nasunąć na Master UINavigationController. Jeśli zaczniesz szukać kontrolera widoku szczegółów przed zakończeniem obrotu, może on nie istnieć i ulec awarii.

MH175
źródło
5

Jeśli używasz wersji Swift> = 3.0, istnieje kilka aktualizacji kodu, które musisz zastosować, jak powiedzieli już inni. Tylko nie zapomnij zadzwonić do super:

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

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

   // YOUR CODE OR FUNCTIONS CALL HERE

}

Jeśli myślisz o użyciu StackView do swoich obrazów, pamiętaj, że możesz wykonać następujące czynności:

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

}

Jeśli korzystasz z Interface Builder, nie zapomnij wybrać niestandardowej klasy dla tego obiektu UIStackView w sekcji Identity Inspector w prawym panelu. Następnie po prostu utwórz (również za pomocą Interface Builder) odwołanie IBOutlet do niestandardowej instancji UIStackView:

@IBOutlet weak var stackView: MyStackView!

Weź pomysł i dostosuj go do swoich potrzeb. Mam nadzieję, że to ci pomoże!

WeiseRatel
źródło
Dobra uwaga na temat dzwonienia super. Niewywoływanie metody super w nadpisanej funkcji jest naprawdę niedbałym kodowaniem i może powodować nieprzewidywalne zachowanie w aplikacjach. I potencjalnie błędy, które są naprawdę trudne do wyśledzenia!
Adam Freeman,
Czy wiesz, dlaczego nie mogę przesłonić viewWillTransition podczas tworzenia podklasy UIView?
Xcoder
@Xcoder Przyczyną (zazwyczaj), że obiekt nie stosuje przesłonięcia, jest to, że instancja środowiska wykonawczego nie została utworzona z klasą niestandardową, ale z wartością domyślną. Jeśli używasz Interface Builder, upewnij się, że wybrałeś klasę niestandardową dla tego obiektu UIStackView, w sekcji Identity Inspector w prawym panelu.
WeiseRatel
5

Szybki 4

Podczas aktualizowania widoku ViewControllers za pomocą UIDevice.current.orientation , takich jak aktualizowanie ograniczeń komórek widoku tabeli podczas obracania lub animacji widoków podrzędnych.

Zamiast powyższych metod obecnie porównuję rozmiar przejścia z rozmiarem widoku kontrolerów widoku. Wydaje się, że jest to właściwy sposób, ponieważ w tym miejscu kodu mamy dostęp do obu:

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()
        }
    }


}

Wyjście na 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
źródło
Czy powinienem włączać wsparcie orientacyjne z poziomu projektu?
Ericpoon
3

Wszystkie poprzednie wpisy są w porządku, ale mała uwaga:

a) jeśli orientacja jest ustawiona w plist, tylko portret lub np Będziesz nie zgłoszone przez viewWillTransition

b) jeśli mimo wszystko potrzebujemy wiedzieć, czy użytkownik obrócił urządzenie (na przykład grę itp.), możemy użyć tylko:

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

testowany na Xcode8, iOS11

ingconti
źródło
2

Możesz użyć viewWillTransition(to:with:)i dotknąć, animate(alongsideTransition:completion:)aby uzyskać orientację interfejsu PO zakończeniu przejścia. Wystarczy zdefiniować i zaimplementować protokół podobny do tego, aby wykorzystać zdarzenie. Zwróć uwagę, że ten kod został użyty w grze SpriteKit, a Twoja konkretna implementacja może się różnić.

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)
    }

Możesz ustawić punkty przerwania w tych funkcjach i obserwować, że interfaceOrientationChanged(to orientation: UIInterfaceOrientation)jest on zawsze wywoływany po viewWillTransition(to size: CGSize)ze zaktualizowaną orientacją.

Rossinator
źródło
1

Aby uzyskać prawidłową orientację podczas uruchamiania aplikacji , musisz to sprawdzićviewDidLayoutSubviews() . Inne opisane tutaj metody nie będą działać.

Oto przykład, jak to zrobić:

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()
}

Będzie to działać zawsze, przy pierwszym uruchomieniu aplikacji i podczas obracania, gdy aplikacja jest uruchomiona .

lenooh
źródło
0

Innym sposobem wykrywania orientacji urządzeń jest użycie funkcji traitCollectionDidChange (_ :). System wywołuje tę metodę, gdy zmienia się środowisko interfejsu iOS.

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

Ponadto możesz użyć funkcji willTransition (to: with :) (która jest wywoływana przed traitCollectionDidChange (_ :)), aby uzyskać informacje tuż przed zastosowaniem orientacji.

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}
ckc
źródło