Swift-向きの変化を検出する方法


93

2つの画像を1つの画像ビューに追加したい(つまり、横向きの画像と縦向きの別の画像)が、迅速な言語を使用して向きの変化を検出する方法がわかりません。

私はこの答えを試しましたが、それは1つの画像しか撮りません

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
Wilfried Josset

回答:


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

imageViewで動作します。背景画像をviewControllerに追加するにはどうすればよいですか?
Raghuram 2016

1
背景にimageViewを配置します。すべての背景をカバーするために、トップ、ボトム、リーディング、トレーリングのViewViewメインビューに制約を与えます。
Rutvik Kanbargi

4
オーバーライドメソッド内でsuper.viewWillTransitionToSizeを呼び出すことを忘れないでください
ブライアンサシェッタ2017

viewWillTransitionサブクラス化するときにオーバーライドできない理由を知っていますUIViewか?
Xcoder

2
別の方向タイプがあります:.isFlat。キャッチするだけの場合はportrait、else句をに変更することをお勧めしますelse if UIDevice.current.orientation.isPortrait。注:.isFlatポートレートとランドスケープの両方で発生する可能性があります。それ以外の場合は{}常にデフォルトになります(フラットランスケープであっても)。
PMT

76

NotificationCenterUIDeviceの使用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")
    }
}

スウィフト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
間違いなく-私のアプローチには追加のコードが必要です。しかし、実際には「向きの変化を検出」します。
マスロフサ2016年

3
@Michael は、変更が完了するとviewWillTransition:、using UIDeviceOrientationDidChangeが呼び出されている間に変更が行われることを予期しているため
リンジースコット2017

1
@LyndseyScott記述されている動作は、潜在的に危険なNSNotificationCenterを使用しなくても達成できると私はまだ信じています。次の回答を確認し、ご意見をお聞かせください:stackoverflow.com/a/26944087/1306884
Michael

4
この回答で興味深いのshouldAutorotateは、ビューコントローラーがに設定されている場合でも、現在の向きがわかることfalseです。これは、カメラアプリのメイン画面などの画面に便利です。向きはロックされていますが、ボタンは回転できます。
yoninja 2017年

1
iOS 9以降、明示的にdeinitを呼び出す必要はありません。詳しくはこちらをご覧ください:useyourloaf.com/blog/…–
James Jordan Taylor

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
UIViewをサブクラス化するときにviewWillTransitionをオーバーライドできない理由を知っていますか?
Xcoder

これは、コントローラーのビューのいずれかを参照しようとするとクラッシュします。
ジェームズジョーダンテイラー

@JamesJordanTaylorクラッシュする理由を説明してください。
オリウム

コメントしたのは6か月前ですが、Xcodeを試したときにXcodeが示した理由は、ビュー自体がまだインスタンス化されておらず、viewControllerだけなので、nilポインタ例外だったと思います。でも、私は覚えていなかったかもしれません。
James Jordan Taylor

@XcoderこれはUIViewではなくUIViewControllerの関数です。そのため、これをオーバーライドすることはできません。
LightningStryk

23

⚠️デバイスの向き!=インターフェースの向き⚠️

Swift 5. * iOS13以下

あなたは本当に違いを生むべきです:

  • デバイスの向き=>物理デバイスの向きを示します
  • インターフェースの向き=>画面に表示されるインターフェースの向きを示します

これらの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()

デバイスの向きとインターフェイスの向きの違いに下線を引くサンプルプロジェクトを作成しました。さらに、UIを更新することにしたライフサイクルのステップに応じて、さまざまな動作を理解するのに役立ちます。

次のリポジトリを複製して実行してくださいhttps : //github.com/wjosset/ReactToOrientation


iOS 13より前にこれを行う方法
ダニエルスプリンガー


1
私はこの答えが最良で最も有益なものだと思います。しかし、iPadOS13.5でのテストでは、推奨の使用は機能しfunc willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)ませんでした。私はそれを使用してそれを動作させましたfunc viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
Andrej

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は使用しないでください。これは、特に複数の高速回転で、遷移の前に画面の境界を与える可能性があるためです(メソッドの「Will」に注意してください)。'size'パラメータを使用する必要があります
Dan Bodnar

@ dan-bodnar nativeBoundsパラメータを意味しますか?
asetniop

3
@asetniop、いいえ。上記でsize記述したviewWillTransitionToSize:withCoordinator:メソッドに注入されるパラメーター。これは、移行後のビューの正確なサイズを反映しています。
Dan Bodnar 2018

子ビューコントローラーを使用している場合、これは良い解決策ではありません。方向に関係なく、コンテナーのサイズは常に正方形になる場合があります。
ボジャン

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

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を使用している場合は、右パネルのIdentity Inspectorセクションで、このUIStackViewオブジェクトのカスタムクラスを選択することを忘れないでください。次に、インターフェイスビルダーを使用して、カスタムUIStackViewインスタンスへのIBOutlet参照を作成します。

@IBOutlet weak var stackView: MyStackView!

アイデアを取り入れて、ニーズに合わせてください。これがあなたを助けることを願っています!


スーパーの呼び出しについての良い点。オーバーライドされた関数でスーパーメソッドを呼び出さないのは実際にずさんなコーディングであり、アプリで予期しない動作を引き起こす可能性があります。そして、追跡するのが本当に難しい潜在的なバグ!
Adam Freeman

UIViewをサブクラス化するときにviewWillTransitionをオーバーライドできない理由を知っていますか?
Xcoder

@Xcoderオーバーライドを適用しないオブジェクトの理由(通常)は、ランタイムインスタンスにあるのは、カスタムクラスではなくデフォルトで作成されたためです。Interface Builderを使用している場合は、右パネルのIdentity Inspectorセクションで、このUIStackViewオブジェクトのカスタムクラスを必ず選択してください。
WiseRatel 2018

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

4

:私は、正しい答えは実際には両方の組み合わせが近づくと信じているviewWIllTransition(toSize:)NotificationCenterUIDeviceOrientationDidChange

viewWillTransition(toSize:)移行前に通知します。

NotificationCenter UIDeviceOrientationDidChange後に通知します。

あなたは非常に注意する必要があります。たとえばUISplitViewController、デバイスが特定の方向に回転すると、DetailViewControllerUISplitViewControllerviewcontrollers配列からポップされ、マスターのにプッシュされUINavigationControllerます。回転が完了する前に詳細ビューコントローラーを検索すると、存在せず、クラッシュする可能性があります。


3

以前の貢献はすべて問題ありませんが、少し注意してください:

a)向きがplistで設定されている場合、縦向きまたは例のみ、viewWillTransitionを介して通知されません

b)ユーザーがデバイスを回転させたかどうかを知る必要がある場合(たとえば、ゲームなど)、使用できるのは以下のみです。

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

Xcode8、iOS11でテスト済み


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

これは、アプリの最初の起動時、およびアプリの実行中に回転している場合は常に機能ます。


1

移行が完了した後、を使用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)


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)
    //...
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.