Swift - So erkennen Sie Orientierungsänderungen

93

Ich möchte der Einzelbildansicht zwei Bilder hinzufügen (dh für ein Bild im Querformat und für ein anderes Bild im Hochformat), aber ich weiß nicht, wie ich Orientierungsänderungen mit schnellen Sprachen erkennen kann.

Ich habe diese Antwort versucht, aber es wird nur ein Bild aufgenommen

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

Ich bin neu in der iOS-Entwicklung, jeder Rat wäre sehr dankbar!

Raghuram
quelle
1
Könnten Sie bitte Ihre Frage bearbeiten und Ihren Code formatieren? Sie können dies mit cmd + k tun, wenn Sie Ihren Code ausgewählt haben.
jbehrens94
2
Überprüfen Sie dies aus stackoverflow.com/questions/25666269/…
DSAjit
Druckt es Quer- / Hochformat korrekt?
Daniel
Ja, es wird korrekt gedruckt. @simpleBob
Raghuram
Sie sollten die Schnittstellenorientierung anstelle der Geräteorientierung verwenden
Wilfried Josset

Antworten:

118
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
quelle
Vielen Dank, dass es für imageView funktioniert. Was ist, wenn ich viewController ein Hintergrundbild hinzufügen möchte?
Raghuram
1
Stellen Sie eine Bildansicht auf den Hintergrund. Geben Sie der imageView eine Einschränkung für die obere, untere, führende und nachfolgende ViewController-Hauptansicht, um den gesamten Hintergrund abzudecken.
Rutvik Kanbargi
4
Vergessen Sie nicht, super.viewWillTransitionToSize innerhalb Ihrer Override-Methode
aufzurufen
Wissen Sie, warum ich viewWillTransitionbeim Unterklassen nicht überschreiben kann UIView?
Xcoder
2
Es gibt einen anderen Orientierungstyp : .isFlat. Wenn Sie nur fangen wollen, portraitschlage ich vor, dass Sie die else-Klausel in ändern else if UIDevice.current.orientation.isPortrait. Hinweis: .isFlatKann sowohl im Hoch- als auch im Querformat auftreten. Mit nur else {} wird es dort immer standardmäßig verwendet (auch wenn es sich um eine flache Landschaft handelt).
PMT
74

Mit NotificationCenter und 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")
    }
}
maslovsa
quelle
1
Auf jeden Fall - mein Ansatz benötigt zusätzlichen Code. Aber es "erkennt wirklich Orientierungsänderungen".
Maslovsa
3
@Michael Da viewWillTransition:erwartet wird, dass die Änderung während der Verwendung eintreten wird, UIDeviceOrientationDidChangewird sie aufgerufen, sobald die Änderung abgeschlossen ist.
Lyndsey Scott
1
@LyndseyScott Ich glaube immer noch, dass das beschriebene Verhalten ohne Verwendung des potenziell gefährlichen NSNotificationCenter erreicht werden kann. Bitte überprüfen Sie die folgende Antwort und teilen
Michael
4
Das Interessante an dieser Antwort ist, dass dies die aktuelle Ausrichtung angibt, auch wenn der View Controller auf eingestellt shouldAutorotateist false. Dies ist praktisch für Bildschirme wie den Hauptbildschirm der Kamera-App. Die Ausrichtung ist gesperrt, die Tasten können sich jedoch drehen.
Yoninja
1
Ab iOS 9 müssen Sie nicht einmal explizit deinit aufrufen. Sie können hier mehr darüber lesen: useyourloaf.com/blog/…
James Jordan Taylor
63

Swift 3 Oben Code aktualisiert:

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")
    }
}
Jaroslaw Bai
quelle
1
Wissen Sie, warum ich viewWillTransition beim Unterklassifizieren von UIView nicht überschreiben kann?
Xcoder
Dies stürzt ab, wenn Sie versuchen, auf eine der Ansichten im Controller zu verweisen.
James Jordan Taylor
@ JamesJordanTaylor Könnten Sie bitte erklären, warum es abstürzen wird?
Orium
Es war vor 6 Monaten, als ich einen Kommentar abgegeben habe, aber ich denke, der Grund, den Xcode beim Versuch angegeben hat, war eine Null-Zeiger-Ausnahme, da die Ansicht selbst noch nicht instanziiert wurde, sondern nur der viewController. Ich könnte mich jedoch falsch erinnern.
James Jordan Taylor
@Xcoder Dies ist eine Funktion in UIViewController, nicht in UIView. Deshalb können Sie sie nicht überschreiben.
LightningStryk
22

⚠️Geräteausrichtung! = Schnittstellenausrichtung⚠️

Swift 5. * iOS13 und niedriger

Sie sollten wirklich einen Unterschied machen zwischen:

  • Geräteorientierung => Zeigt die Ausrichtung des physischen Geräts an
  • Schnittstellenausrichtung => Zeigt die Ausrichtung der auf dem Bildschirm angezeigten Schnittstelle an

Es gibt viele Szenarien, in denen diese beiden Werte nicht übereinstimmen, z.

  • Wenn Sie Ihre Bildschirmausrichtung sperren
  • Wenn Sie Ihr Gerät flach haben

In den meisten Fällen möchten Sie die Schnittstellenausrichtung verwenden und können diese über das Fenster abrufen:

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

Wenn Sie auch <iOS 13 (z. B. iOS 12) unterstützen möchten, gehen Sie wie folgt vor:

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

Jetzt müssen Sie definieren, wo auf die Ausrichtung der Fensterschnittstellenausrichtung reagiert werden soll. Es gibt mehrere Möglichkeiten, dies zu tun, aber die optimale Lösung besteht darin, dies innerhalb zu tun willTransition(to newCollection: UITraitCollection.

Diese geerbte UIViewController-Methode, die überschrieben werden kann, wird jedes Mal ausgelöst, wenn die Ausrichtung der Benutzeroberfläche geändert wird. Folglich können Sie alle Ihre Änderungen in letzterem vornehmen.

Hier ist ein Lösungsbeispiel :

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

Durch die Implementierung dieser Methode können Sie dann auf jede Änderung der Ausrichtung Ihrer Benutzeroberfläche reagieren. Beachten Sie jedoch, dass es beim Öffnen der App nicht ausgelöst wird, sodass Sie Ihre Benutzeroberfläche auch manuell aktualisieren müssen viewWillAppear().

Ich habe ein Beispielprojekt erstellt, das den Unterschied zwischen Geräteorientierung und Schnittstellenorientierung unterstreicht. Darüber hinaus hilft es Ihnen, das unterschiedliche Verhalten zu verstehen, je nachdem, für welchen Lebenszyklusschritt Sie Ihre Benutzeroberfläche aktualisieren möchten.

Sie können das folgende Repository klonen und ausführen: https://github.com/wjosset/ReactToOrientation

Wilfried Josset
quelle
Wie geht das vor iOS 13?
Daniel Springer
1
@ DanielSpringer Vielen Dank für Ihren Kommentar. Ich habe den Beitrag bearbeitet, um iOS 12 und niedriger zu unterstützen
Wilfried Josset
1
Ich finde diese Antwort am besten und informativsten. Das Testen der vorgeschlagenen Verwendung auf iPadOS13.5 func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)funktionierte jedoch nicht. Ich habe es mit funktionieren lassen func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
Andrej
10

Swift 4+: Ich habe dies für das Design von Softtastaturen verwendet, und aus irgendeinem Grund hat UIDevice.current.orientation.isLandscapemir die Methode immer wieder gesagt Portrait, dass dies der Fall ist. Deshalb habe ich stattdessen Folgendes verwendet:

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
quelle
Nun, ich habe ein ähnliches Problem in meiner iMessage-App. Ist es nicht wirklich komisch? Und sogar Ihre Lösung funktioniert umgekehrt !! ¯_ (ツ) _ / ¯
Shyam
Verwenden Sie UIScreen.main.bounds nicht, da dies möglicherweise zu Bildschirmgrenzen vor dem Übergang führt (beachten Sie den Willen in der Methode), insbesondere bei mehreren schnellen Rotationen. Sie sollten den Parameter 'Größe' verwenden ...
Dan Bodnar
@ dan-bodnar Meinst du den nativeBounds-Parameter?
Asetniop
3
@asetniop, nein. Der sizeParameter, der in die oben beschriebene Methode viewWillTransitionToSize: withCoordinator: eingefügt wird. Dies gibt die genaue Größe wieder, die Ihre Ansicht nach dem Übergang haben wird.
Dan Bodnar
Dies ist keine gute Lösung, wenn Sie untergeordnete Ansichtssteuerungen verwenden. Die Größe des Containers kann unabhängig von der Ausrichtung immer quadratisch sein.
Bojan
7

Swift 4.2, RxSwift

Wenn wir collectionView neu laden müssen.

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

Swift 4, RxSwift

Wenn wir collectionView neu laden müssen.

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

Wenn Sie die Swift-Version> = 3.0 verwenden, müssen Sie einige Code-Updates anwenden, wie andere bereits gesagt haben. Vergiss nur nicht, super anzurufen:

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

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

   // YOUR CODE OR FUNCTIONS CALL HERE

}

Wenn Sie eine StackView für Ihre Bilder verwenden möchten, können Sie Folgendes tun:

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

}

Wenn Sie Interface Builder verwenden, vergessen Sie nicht, die benutzerdefinierte Klasse für dieses UIStackView-Objekt im Abschnitt Identity Inspector im rechten Bereich auszuwählen. Erstellen Sie dann einfach (auch über Interface Builder) den IBOutlet-Verweis auf die benutzerdefinierte UIStackView-Instanz:

@IBOutlet weak var stackView: MyStackView!

Nehmen Sie die Idee und passen Sie sie an Ihre Bedürfnisse an. Hoffe das kann dir helfen!

WiseRatel
quelle
Guter Punkt, um super anzurufen. Es ist wirklich schlampig, die Super-Methode nicht in einer überschriebenen Funktion aufzurufen, und es kann zu unvorhersehbarem Verhalten in Apps führen. Und möglicherweise Fehler, die wirklich schwer zu finden sind!
Adam Freeman
Wissen Sie, warum ich viewWillTransition beim Unterklassen von UIView nicht überschreiben kann?
Xcoder
@Xcoder Der Grund (normalerweise) für ein Objekt, das keine Überschreibung anwendet, liegt in der Laufzeitinstanz, die nicht mit der benutzerdefinierten Klasse, sondern mit der Standardeinstellung erstellt wurde. Wenn Sie Interface Builder verwenden, müssen Sie die benutzerdefinierte Klasse für dieses UIStackView-Objekt im Abschnitt Identity Inspector im rechten Bereich auswählen.
WiseRatel
5

Swift 4

Ich hatte einige kleinere Probleme beim Aktualisieren der ViewController-Ansicht mit UIDevice.current.orientation , z. B. das Aktualisieren von Einschränkungen von Tabellenansichtszellen während der Drehung oder das Animieren von Unteransichten.

Anstelle der oben genannten Methoden vergleiche ich derzeit die Übergangsgröße mit der Ansichtsgröße des Ansichtscontrollers. Dies scheint der richtige Weg zu sein, da man an dieser Stelle im Code Zugriff auf beide hat:

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


}

Ausgabe auf einem 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
quelle
Sollte ich die Orientierungsunterstützung auf Projektebene aktivieren müssen?
Ericpoon
4

Ich glaube, die richtige Antwort ist tatsächlich eine Kombination beider Ansätze: viewWIllTransition(toSize:)und NotificationCenter's UIDeviceOrientationDidChange.

viewWillTransition(toSize:)benachrichtigt Sie vor dem Übergang.

NotificationCenter UIDeviceOrientationDidChangebenachrichtigt Sie nach .

Du musst sehr vorsichtig sein. Beispielsweise in , UISplitViewControllerwenn die Vorrichtung rotiert in bestimmte Orientierungen, die DetailViewControllerbekommen den tauchte off UISplitViewController‚s - viewcontrollersArray und Aufschieben auf den Master UINavigationController. Wenn Sie vor Abschluss der Drehung nach dem Detailansichts-Controller suchen, ist dieser möglicherweise nicht vorhanden und stürzt ab.

MH175
quelle
3

Alle vorherigen Beiträge sind in Ordnung, aber ein kleiner Hinweis:

a) Wenn die Ausrichtung in plist festgelegt ist, nur im Hochformat oder Beispiel, werden Sie nicht über viewWillTransition benachrichtigt

b) Wenn wir trotzdem wissen müssen, ob der Benutzer das Gerät gedreht hat (zum Beispiel ein Spiel oder ähnliches), können wir nur Folgendes verwenden:

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

getestet auf Xcode8, iOS11

ingconti
quelle
1

Um die richtige Ausrichtung beim Start der App zu erhalten , müssen Sie diese einchecken viewDidLayoutSubviews(). Andere hier beschriebene Methoden funktionieren nicht.

Hier ist ein Beispiel, wie es geht:

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

Dies funktioniert immer beim ersten Start der App und beim Drehen, während die App ausgeführt wird .

lenooh
quelle
1

Sie können verwenden viewWillTransition(to:with:)und tippen animate(alongsideTransition:completion:), um die Ausrichtung der Benutzeroberfläche zu erhalten, nachdem der Übergang abgeschlossen ist. Sie müssen lediglich ein ähnliches Protokoll definieren und implementieren, um das Ereignis zu nutzen. Beachten Sie, dass dieser Code für ein SpriteKit-Spiel verwendet wurde und Ihre spezifische Implementierung abweichen kann.

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

Sie können in diesen Funktionen Haltepunkte setzen und beobachten, dass diese interfaceOrientationChanged(to orientation: UIInterfaceOrientation)immer viewWillTransition(to size: CGSize)mit der aktualisierten Ausrichtung aufgerufen werden .

Der Rossinator
quelle
0

Eine andere Möglichkeit, Geräteorientierungen zu erkennen, ist die Funktion traitCollectionDidChange (_ :). Das System ruft diese Methode auf, wenn sich die iOS-Schnittstellenumgebung ändert.

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

Darüber hinaus können Sie die Funktion willTransition (to: with :) (die vor traitCollectionDidChange (_ :) aufgerufen wird) verwenden, um Informationen unmittelbar vor dem Anwenden der Ausrichtung abzurufen.

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}
ckc
quelle