Swift - Bagaimana mendeteksi perubahan orientasi

96

Saya ingin menambahkan dua gambar ke tampilan gambar tunggal (yaitu untuk gambar lanskap satu dan untuk potret gambar lain) tetapi saya tidak tahu bagaimana mendeteksi perubahan orientasi menggunakan bahasa cepat.

Saya mencoba jawaban ini tetapi hanya membutuhkan satu gambar

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

Saya baru dalam pengembangan iOS, saran apa pun akan sangat dihargai!

Raghuram
sumber
1
Bisakah Anda mengedit pertanyaan Anda dan memformat kode Anda? Anda dapat melakukannya dengan cmd + k setelah kode Anda dipilih.
jbehrens94
Apakah ini mencetak lanskap / potret dengan benar?
Daniel
ya itu mencetak dengan benar @simpleBob
Raghuram
Anda harus menggunakan orientasi antarmuka daripada orientasi perangkat => stackoverflow.com/a/60577486/8780127
Wilfried Josset

Jawaban:

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
sumber
Terima kasih ini berfungsi untuk imageView, bagaimana jika saya ingin menambahkan gambar latar belakang ke viewController ??
Raghuram
1
Letakkan imageView di background. Berikan batasan pada imageView untuk atas, bawah, memimpin, mengikuti tampilan utama ViewController untuk menutupi semua latar belakang.
Rutvik Kanbargi
4
Jangan lupa untuk memanggil super.viewWillTransitionToSize dalam metode override Anda
Brian Sachetta
Tahukah Anda mengapa saya tidak bisa mengganti viewWillTransitionsaat membuat subclass UIView?
Xcoder
2
Ada jenis orientasi lain: .isFlat. Jika Anda hanya ingin menangkap portraitsaya sarankan Anda mengubah klausa else menjadi else if UIDevice.current.orientation.isPortrait. Catatan: .isFlatbisa terjadi baik dalam potret maupun lanskap, dan dengan cara lain {} akan selalu ada default (meskipun lanskap datar).
PMT
78

Menggunakan NotificationCenter dan 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")
    }
}

Cepat 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.dll
sumber
1
Pastinya - pendekatan saya membutuhkan beberapa kode tambahan. Tapi itu benar-benar "mendeteksi perubahan orientasi".
maslovsa
3
@Michael Karena viewWillTransition:mengantisipasi perubahan akan terjadi saat menggunakan UIDeviceOrientationDidChangedipanggil setelah perubahan selesai.
Lyndsey Scott
1
@LyndseyScott Saya masih percaya bahwa perilaku yang dijelaskan dapat dicapai tanpa menggunakan NSNotificationCenter yang berpotensi berbahaya. Harap tinjau jawaban berikut, dan beri tahu saya pendapat Anda: stackoverflow.com/a/26944087/1306884
Michael
4
Yang menarik dalam jawaban ini adalah bahwa ini akan memberikan orientasi saat ini meskipun pengontrol tampilan shouldAutorotatedisetel ke false. Ini berguna untuk layar seperti layar utama aplikasi Kamera - orientasinya terkunci tetapi tombolnya dapat berputar.
yoninja
1
Mulai iOS 9, Anda bahkan tidak perlu memanggil deinit secara eksplisit. Anda dapat membaca lebih lanjut tentang itu di sini: useyourloaf.com/blog/…
James Jordan Taylor
63

Kode Swift 3 Di Atas diperbarui:

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
sumber
1
Apakah Anda tahu mengapa saya tidak dapat mengganti viewWillTransition saat membuat subclass UIView?
Xcoder
Ini akan macet jika Anda mencoba mereferensikan salah satu tampilan di controller.
James Jordan Taylor
@JamesJordanTaylor bisakah Anda menjelaskan mengapa itu akan crash?
orium
Itu 6 bulan yang lalu ketika saya berkomentar, tapi saya pikir alasan yang diberikan Xcode ketika saya mencobanya adalah pengecualian pointer nihil karena tampilan itu sendiri belum dibuat, hanya viewController. Saya bisa saja salah mengingat.
James Jordan Taylor
@Xcoder ini adalah fungsi pada UIViewController bukan UIView - itulah mengapa Anda tidak dapat menimpanya.
LightningStryk
25

⚠️ Orientasi Perangkat! = Orientasi Antarmuka⚠️

Swift 5. * iOS14 dan yang lebih lama

Anda harus benar-benar membuat perbedaan antara:

  • Device Orientation => Menunjukkan orientasi perangkat fisik
  • Orientasi Antarmuka => Menunjukkan orientasi antarmuka yang ditampilkan di layar

Ada banyak skenario di mana 2 nilai tersebut tidak cocok seperti:

  • Saat Anda mengunci orientasi layar Anda
  • Saat perangkat Anda datar

Dalam kebanyakan kasus, Anda ingin menggunakan orientasi antarmuka dan Anda bisa mendapatkannya melalui jendela:

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

Jika Anda juga ingin mendukung <iOS 13 (seperti iOS 12), Anda harus melakukan hal berikut:

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

Sekarang Anda perlu menentukan di mana harus bereaksi terhadap perubahan orientasi antarmuka jendela. Ada banyak cara untuk melakukan itu tetapi solusi optimal adalah melakukannya di dalam willTransition(to newCollection: UITraitCollection.

Metode UIViewController yang diwariskan yang dapat diganti ini akan dipicu setiap kali orientasi antarmuka diubah. Akibatnya, Anda dapat melakukan semua modifikasi Anda dengan yang terakhir.

Berikut adalah contoh solusi :

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

Dengan menerapkan metode ini, Anda akan dapat bereaksi terhadap perubahan orientasi apa pun ke antarmuka Anda. Namun perlu diingat bahwa itu tidak akan dipicu pada pembukaan aplikasi sehingga Anda juga harus memperbarui antarmuka Anda secara manual viewWillAppear().

Saya telah membuat proyek contoh yang menggarisbawahi perbedaan antara orientasi perangkat dan orientasi antarmuka. Selain itu, ini akan membantu Anda memahami berbagai perilaku bergantung pada langkah siklus hidup mana yang Anda putuskan untuk memperbarui UI.

Jangan ragu untuk mengkloning dan menjalankan repositori berikut: https://github.com/wjosset/ReactToOrientation

Wilfried Josset
sumber
bagaimana melakukan ini sebelum iOS 13?
Daniel Springer
1
@DanielSpringer terima kasih atas komentar Anda, saya telah mengedit pos untuk mendukung iOS 12 dan yang lebih lama
Wilfried Josset
1
Saya menemukan jawaban ini yang terbaik dan paling informatif. Tetapi pengujian pada iPadOS13.5 penggunaan yang disarankan func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)tidak berhasil. Saya telah membuatnya bekerja menggunakan func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
Andrej
12

Swift 4+: Saya menggunakan ini untuk desain keyboard lembut, dan untuk beberapa alasan UIDevice.current.orientation.isLandscapemetode tersebut terus memberi tahu saya Portrait, jadi inilah yang saya gunakan sebagai gantinya:

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
sumber
baik, saya memiliki masalah serupa di aplikasi iMessage saya. Bukankah ini sangat aneh? Dan, bahkan solusi Anda justru sebaliknya !! ¯_ (ツ) _ / ¯
Shyam
Jangan gunakan UIScreen.main.bounds, karena ini mungkin memberi Anda batas layar sebelum transisi (perhatikan 'Will' dalam metode), terutama pada beberapa rotasi cepat. Anda harus menggunakan parameter 'size' ...
Dan Bodnar
@ dan-bodnar Maksud Anda parameter nativeBounds?
asetniop
3
@asetniop, tidak. The sizeparameter yang disuntikkan di viewWillTransitionToSize: withCoordinator: Metode Anda tulis di atas. Ini akan mencerminkan ukuran persis tampilan Anda setelah transisi.
Dan Bodnar
Ini bukan solusi yang baik jika Anda menggunakan pengontrol tampilan anak, ukuran wadah mungkin selalu persegi, terlepas dari orientasinya.
bojan
8

Swift 4.2, RxSwift

Jika kita perlu memuat ulang collectionView.

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

Cepat 4, RxSwift

Jika kita perlu memuat ulang collectionView.

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

Saya percaya Jawaban yang benar adalah kombinasi dari kedua pendekatan: viewWIllTransition(toSize:)dan NotificationCenter's UIDeviceOrientationDidChange.

viewWillTransition(toSize:)memberi tahu Anda sebelum transisi.

NotificationCenter UIDeviceOrientationDidChangememberi tahu Anda setelah .

Anda harus sangat berhati-hati. Misalnya, di UISplitViewControllersaat berputar perangkat ke orientasi tertentu, DetailViewControllerakan muncul dari UISplitViewController's viewcontrollersarray, dan didorong ke master UINavigationController. Jika Anda mencari pengontrol tampilan detail sebelum rotasi selesai, mungkin tidak ada dan crash.

MH175
sumber
5

Jika Anda menggunakan versi Swift> = 3.0, ada beberapa pembaruan kode yang harus Anda terapkan seperti yang telah dikatakan orang lain. Jangan lupa untuk menelepon super:

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

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

   // YOUR CODE OR FUNCTIONS CALL HERE

}

Jika Anda berpikir untuk menggunakan StackView untuk gambar Anda, berhati-hatilah karena Anda dapat melakukan sesuatu seperti berikut:

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

}

Jika Anda menggunakan Interface Builder, jangan lupa untuk memilih kelas kustom untuk objek UIStackView ini, di bagian Identity Inspector di panel kanan. Kemudian buat saja (juga melalui Interface Builder) referensi IBOutlet ke instance UIStackView kustom:

@IBOutlet weak var stackView: MyStackView!

Ambil ide dan sesuaikan dengan kebutuhan Anda. Semoga ini bisa membantu Anda!

WeiseRatel
sumber
Poin bagus tentang menelepon super. Ini benar-benar pengkodean yang ceroboh untuk tidak memanggil metode super dalam fungsi yang diganti dan dapat menghasilkan perilaku yang tidak dapat diprediksi dalam aplikasi. Dan berpotensi bug yang sangat sulit dilacak!
Adam Freeman
Apakah Anda tahu mengapa saya tidak dapat mengganti viewWillTransition saat membuat subclass UIView?
Xcoder
@Xcoder Alasan (biasanya) objek tidak menerapkan override terletak pada instance runtime tidak dibuat dengan kelas kustom tetapi dengan default sebagai gantinya. Jika Anda menggunakan Interface Builder, pastikan memilih kelas kustom untuk objek UIStackView ini, di bagian Identity Inspector di panel kanan.
WeiseRatel
5

Cepat 4

Saya mengalami beberapa masalah kecil saat memperbarui tampilan ViewControllers menggunakan UIDevice.current.orientation, seperti memperbarui batasan sel tampilan tabel selama rotasi atau animasi subview.

Alih-alih metode di atas, saat ini saya membandingkan ukuran transisi dengan ukuran tampilan pengontrol tampilan. Ini sepertinya cara yang tepat untuk pergi karena seseorang memiliki akses ke keduanya pada titik ini dalam kode:

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


}

Output pada 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
sumber
Haruskah saya mengaktifkan dukungan orientasi dari tingkat proyek?
Ericpoon
3

Semua kontribusi sebelumnya baik-baik saja, tetapi sedikit catatan:

a) jika orientasi diatur dalam plist, hanya potret atau contoh, Anda akan tidak diberitahu melalui viewWillTransition

b) jika kita tetap perlu tahu apakah pengguna memiliki perangkat yang dirotasi, (misalnya permainan atau sejenisnya ..) kita hanya dapat menggunakan:

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

diuji di Xcode8, iOS11

ingconti
sumber
2

Anda dapat menggunakan viewWillTransition(to:with:)dan memanfaatkan animate(alongsideTransition:completion:)untuk mendapatkan orientasi antarmuka SETELAH transisi selesai. Anda hanya perlu mendefinisikan dan menerapkan protokol yang mirip dengan ini untuk memanfaatkan acara tersebut. Perhatikan bahwa kode ini digunakan untuk game SpriteKit dan penerapan spesifik Anda mungkin berbeda.

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

Anda bisa menyetel breakpoint dalam fungsi ini dan mengamati bahwa interfaceOrientationChanged(to orientation: UIInterfaceOrientation)selalu dipanggil setelah viewWillTransition(to size: CGSize)dengan orientasi yang diperbarui.

Rossinator
sumber
1

Untuk mendapatkan orientasi yang benar pada permulaan aplikasi, Anda harus memeriksanya viewDidLayoutSubviews(). Metode lain yang dijelaskan di sini tidak akan berfungsi.

Berikut ini contoh cara melakukannya:

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

Ini akan selalu berfungsi, saat aplikasi pertama kali dimulai , dan jika berputar saat aplikasi berjalan .

lenooh
sumber
0

Cara lain untuk mendeteksi orientasi perangkat adalah dengan fungsi traitCollectionDidChange (_ :). Sistem memanggil metode ini ketika lingkungan antarmuka iOS berubah.

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

Selanjutnya, Anda dapat menggunakan fungsi willTransition (to: with :) (yang dipanggil sebelum traitCollectionDidChange (_ :)), untuk mendapatkan informasi tepat sebelum orientasi diterapkan.

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