Swift - Comment détecter les changements d'orientation

93

Je veux ajouter deux images à une seule image (c'est-à-dire pour une image en paysage et une autre image en portrait) mais je ne sais pas comment détecter les changements d'orientation en utilisant des langues rapides.

J'ai essayé cette réponse mais cela ne prend qu'une seule image

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

Je suis nouveau dans le développement iOS, tout conseil serait grandement apprécié!

Raghuram
la source
1
Pourriez-vous s'il vous plaît modifier votre question et formater votre code? Vous pouvez le faire avec cmd + k lorsque votre code est sélectionné.
jbehrens94
Est-ce qu'il imprime correctement le paysage / portrait?
Daniel
oui il s'imprime correctement @simpleBob
Raghuram
Vous devez utiliser l'orientation de l'interface au lieu de l'orientation de l'appareil => stackoverflow.com/a/60577486/8780127
Wilfried Josset

Réponses:

120
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
la source
Merci, cela fonctionne pour imageView, que faire si je veux ajouter une image d'arrière-plan à viewController ??
Raghuram
1
Mettez une imageView en arrière-plan. Donnez une contrainte à l'imageView pour le haut, le bas, le début et la fin de la vue principale de ViewController pour couvrir tout l'arrière-plan.
Rutvik Kanbargi
4
N'oubliez pas d'appeler super.viewWillTransitionToSize dans votre méthode de substitution
Brian Sachetta
Savez-vous pourquoi je ne peux pas remplacer viewWillTransitionlors du sous UIView- classement ?
Xcoder
2
Il existe un autre type d'orientation: .isFlat. Si vous voulez seulement attraper, portraitje vous suggère de changer la clause else en else if UIDevice.current.orientation.isPortrait. Remarque: .isFlatcela peut se produire à la fois en portrait et en paysage, et avec just else {} il y aura toujours par défaut (même si c'est un paysage plat).
PMT du
76

L' utilisation NotificationCenter et UIDevice d »beginGeneratingDeviceOrientationNotifications

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
la source
1
Certainement - mon approche a besoin d'un code supplémentaire. Mais il "détecte vraiment les changements d'orientation".
maslovsa
3
@Michael Car viewWillTransition:prévoit que le changement se produira lors de l'utilisation UIDeviceOrientationDidChangeest appelé une fois le changement terminé.
Lyndsey Scott
1
@LyndseyScott Je crois toujours que le comportement décrit peut être obtenu sans utiliser le NSNotificationCenter potentiellement dangereux. Veuillez examiner la réponse suivante et laissez-moi savoir ce que vous en pensez: stackoverflow.com/a/26944087/1306884
Michael
4
Ce qui est intéressant dans cette réponse, c'est que cela donnera l'orientation actuelle même lorsque le contrôleur de vue shouldAutorotateest défini sur false. Ceci est pratique pour les écrans tels que l'écran principal de l'application Appareil photo - l'orientation est verrouillée mais les boutons peuvent pivoter.
yoninja
1
Depuis iOS 9, vous n'avez même pas besoin d'appeler explicitement deinit. Vous pouvez en savoir plus ici: useyourloaf.com/blog/…
James Jordan Taylor
63

Code Swift 3 ci- dessus mis à jour:

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
la source
1
Savez-vous pourquoi je ne peux pas remplacer viewWillTransition lorsque je sous-classe UIView?
Xcoder
Cela plantera si vous essayez de référencer l'une des vues dans le contrôleur.
James Jordan Taylor
@JamesJordanTaylor pourriez-vous s'il vous plaît expliquer pourquoi il va planter?
orium
C'était il y a 6 mois quand j'ai commenté, mais je pense que la raison que Xcode a donnée quand j'ai essayé était une exception de pointeur nul parce que la vue elle-même n'avait pas encore été instanciée, juste le viewController. Je pourrais me souvenir cependant.
James Jordan Taylor
@Xcoder c'est une fonction sur UIViewController et non UIView - c'est pourquoi vous ne pouvez pas la remplacer.
LightningStryk
23

⚠️ Orientation de l'appareil! = Orientation de l'interface⚠️

Swift 5. * iOS13 et inférieur

Vous devriez vraiment faire la différence entre:

  • Orientation de l'appareil => Indique l'orientation de l'appareil physique
  • Orientation de l'interface => Indique l'orientation de l'interface affichée à l'écran

Il existe de nombreux scénarios dans lesquels ces 2 valeurs ne correspondent pas, par exemple:

  • Lorsque vous verrouillez l'orientation de votre écran
  • Lorsque votre appareil est à plat

Dans la plupart des cas, vous voudrez utiliser l'orientation de l'interface et vous pouvez l'obtenir via la fenêtre:

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

Si vous souhaitez également prendre en charge <iOS 13 (comme iOS 12), procédez comme suit:

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

Vous devez maintenant définir où réagir au changement d'orientation de l'interface de la fenêtre. Il existe plusieurs façons de le faire, mais la solution optimale est de le faire à l'intérieur willTransition(to newCollection: UITraitCollection.

Cette méthode UIViewController héritée qui peut être remplacée sera déclenchée chaque fois que l'orientation de l'interface sera modifiée. Par conséquent, vous pouvez effectuer toutes vos modifications dans ce dernier.

Voici un exemple de solution :

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

En implémentant cette méthode, vous serez alors en mesure de réagir à tout changement d'orientation de votre interface. Mais gardez à l'esprit qu'il ne sera pas déclenché à l'ouverture de l'application, vous devrez donc également mettre à jour manuellement votre interface dans viewWillAppear().

J'ai créé un exemple de projet qui souligne la différence entre l'orientation de l'appareil et l'orientation de l'interface. En outre, cela vous aidera à comprendre les différents comportements en fonction de l'étape du cycle de vie que vous décidez de mettre à jour votre interface utilisateur.

N'hésitez pas à cloner et exécuter le référentiel suivant: https://github.com/wjosset/ReactToOrientation

Wilfried Josset
la source
comment faire cela avant iOS 13?
Daniel Springer
1
@DanielSpringer merci pour votre commentaire, j'ai édité le post pour supporter iOS 12 et moins
Wilfried Josset
1
Je trouve cette réponse la meilleure et la plus informative. Mais tester sur iPadOS13.5 l'utilisation de suggéré func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)n'a pas fonctionné. Je l'ai fait fonctionner en utilisant func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
Andrej
12

Swift 4+: J'utilisais ceci pour la conception de clavier logiciel, et pour une raison quelconque, la UIDevice.current.orientation.isLandscapeméthode me disait que c'était le cas Portrait, alors voici ce que j'ai utilisé à la place:

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
la source
eh bien, j'ai un problème similaire dans mon application iMessage. N'est-ce pas vraiment bizarre? Et même votre solution fonctionne à l'opposé !! ¯_ (ツ) _ / ¯
Shyam
N'utilisez pas UIScreen.main.bounds, car cela pourrait vous donner les limites de l'écran avant la transition (faites attention au «Will» dans la méthode), en particulier sur plusieurs rotations rapides. Vous devriez utiliser le paramètre 'size' ...
Dan Bodnar
@ dan-bodnar Voulez-vous dire le paramètre nativeBounds?
asetniop
3
@asetniop, non. Le sizeparamètre qui est injecté dans viewWillTransitionToSize: withCoordinator: méthode que vous avez écrite ci-dessus. Cela reflétera la taille exacte de votre vue après la transition.
Dan Bodnar
Ce n'est pas une bonne solution si vous utilisez des contrôleurs de vue enfants, la taille du conteneur peut toujours être carrée, quelle que soit l'orientation.
bojan
8

Swift 4.2, RxSwift

Si nous avons besoin de recharger collectionView.

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

Swift 4, RxSwift

Si nous avons besoin de recharger collectionView.

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

Je crois que la bonne réponse est en fait une combinaison des deux approches: viewWIllTransition(toSize:)et NotificationCenter's UIDeviceOrientationDidChange.

viewWillTransition(toSize:)vous avertit avant la transition.

NotificationCenter UIDeviceOrientationDidChangevous avertit après .

Tu dois être très prudent. Par exemple, UISplitViewControllerlorsque le périphérique tourne dans certaines orientations, le DetailViewControllerest sorti du tableau du UISplitViewControllers viewcontrollerset poussé sur celui du maître UINavigationController. Si vous recherchez le contrôleur de vue détaillée avant la fin de la rotation, il peut ne pas exister et se bloquer.

MH175
la source
5

Si vous utilisez la version Swift> = 3.0, vous devez appliquer certaines mises à jour de code, comme d'autres l'ont déjà dit. N'oubliez pas d'appeler super:

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

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

   // YOUR CODE OR FUNCTIONS CALL HERE

}

Si vous envisagez d'utiliser un StackView pour vos images, sachez que vous pouvez faire quelque chose comme ce qui suit:

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

}

Si vous utilisez Interface Builder, n'oubliez pas de sélectionner la classe personnalisée pour cet objet UIStackView, dans la section Inspecteur d'identité dans le panneau de droite. Ensuite, créez simplement (également via Interface Builder) la référence IBOutlet à l'instance UIStackView personnalisée:

@IBOutlet weak var stackView: MyStackView!

Prenez l'idée et adaptez-la à vos besoins. J'espère que cela peut vous aider!

WiseRatel
la source
Bon point d'appeler super. C'est vraiment un codage bâclé de ne pas appeler la super méthode dans une fonction remplacée et cela peut produire un comportement imprévisible dans les applications. Et potentiellement des bugs vraiment difficiles à dépister!
Adam Freeman
Savez-vous pourquoi je ne peux pas remplacer viewWillTransition lorsque je sous-classe UIView?
Xcoder
@Xcoder La raison (généralement) pour un objet n'appliquant pas de remplacement réside dans le fait que l'instance d'exécution n'a pas été créée avec la classe personnalisée mais avec la classe par défaut à la place. Si vous utilisez Interface Builder, assurez-vous de sélectionner la classe personnalisée pour cet objet UIStackView, dans la section Inspecteur d'identité du panneau de droite.
WiseRatel
5

Swift 4

J'ai eu quelques problèmes mineurs lors de la mise à jour de la vue ViewControllers à l'aide UIDevice.current.orientation, tels que la mise à jour des contraintes des cellules de vue de table pendant la rotation ou l'animation des sous-vues.

Au lieu des méthodes ci-dessus, je compare actuellement la taille de la transition à la taille de la vue des contrôleurs de vue. Cela semble être la bonne façon de procéder car on a accès aux deux à ce stade du code:

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


}

Sortie sur un 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
la source
Dois-je devoir activer le support d'orientation à partir du niveau du projet?
Ericpoon
3

Toutes les contributions précédentes sont bien, mais une petite note:

a) si l'orientation est définie dans plist, uniquement portrait ou exemple, vous ne serez pas averti via viewWillTransition

b) si nous avons de toute façon besoin de savoir si l'utilisateur a fait pivoter l'appareil, (par exemple un jeu ou similaire ..) nous ne pouvons utiliser que:

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

testé sur Xcode8, iOS11

ingconti
la source
1

Pour obtenir l'orientation correcte au démarrage de l'application, vous devez l'enregistrer viewDidLayoutSubviews(). Les autres méthodes décrites ici ne fonctionneront pas.

Voici un exemple comment procéder:

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

Cela fonctionnera toujours, au premier démarrage de l'application , et en cas de rotation pendant que l'application est en cours d'exécution .

lenooh
la source
1

Vous pouvez utiliser viewWillTransition(to:with:)et taper animate(alongsideTransition:completion:)pour obtenir l'orientation de l'interface APRÈS que la transition soit terminée. Il vous suffit de définir et de mettre en œuvre un protocole similaire à celui-ci afin de puiser dans l'événement. Notez que ce code a été utilisé pour un jeu SpriteKit et que votre implémentation spécifique peut différer.

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

Vous pouvez définir des points d'arrêt dans ces fonctions et observer qu'il interfaceOrientationChanged(to orientation: UIInterfaceOrientation)est toujours appelé après viewWillTransition(to size: CGSize)avec l'orientation mise à jour.

Le Rossinator
la source
0

Une autre façon de détecter les orientations des périphériques est d'utiliser la fonction traitCollectionDidChange (_ :). Le système appelle cette méthode lorsque l'environnement de l'interface iOS change.

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

De plus, vous pouvez utiliser la fonction willTransition (to: with :) (qui est appelée avant traitCollectionDidChange (_ :)), pour obtenir des informations juste avant l'application de l'orientation.

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