Diberikan tampilan, bagaimana cara mendapatkan viewController?

141

Saya memiliki pointer ke a UIView. Bagaimana cara saya mengaksesnya UIViewController? [self superview]ada yang lain UIView, tapi bukan UIViewController, kan?

mahboudz
sumber
Saya pikir utas ini memiliki jawaban: Dapatkan ke UIViewController dari UIView di iPhone?
Ushox
Pada dasarnya, saya mencoba untuk memanggil viewController's viewWillAppear karena pandangan saya ditolak. Tampilan ditolak oleh tampilan itu sendiri yang mendeteksi keran dan memanggil [self removeFromSuperview]; ViewController tidak memanggil viewWillAppear / WillDisappear / DidAppear / DidDisappear itu sendiri.
mahboudz
Maksud saya, saya mencoba memanggil viewWillDisappear karena pandangan saya ditolak.
mahboudz
1
Kemungkinan duplikat dari Get to UIViewController dari UIView?
Efren

Jawaban:

42

Ya, superviewini adalah tampilan yang berisi tampilan Anda. Pandangan Anda seharusnya tidak tahu yang mana persisnya pengontrol tampilan, karena itu akan melanggar prinsip MVC.

Kontroler, di sisi lain, tahu tampilan mana yang bertanggung jawab untuk ( self.view = myView), dan biasanya, tampilan ini mendelegasikan metode / kejadian untuk penanganan ke controller.

Biasanya, alih-alih sebuah penunjuk ke tampilan Anda, Anda harus memiliki penunjuk ke pengontrol Anda, yang pada gilirannya dapat menjalankan beberapa logika kontrol, atau meneruskan sesuatu ke pandangannya.

Dimitar Dimitrov
sumber
24
Saya tidak yakin apakah itu akan melanggar prinsip-prinsip MVC. Pada satu titik, tampilan hanya memiliki satu pengontrol tampilan. Mampu sampai ke sana untuk meneruskan pesan kembali ke itu, harus menjadi fitur otomatis, bukan di mana Anda harus bekerja untuk mencapainya (dengan menambahkan properti untuk melacak). Orang dapat mengatakan hal yang sama tentang pandangan: mengapa Anda perlu tahu siapa anak Anda? Atau apakah ada pandangan saudara lain. Namun ada cara untuk mendapatkan benda-benda itu.
mahboudz
Anda agak benar tentang tampilan, mengetahui tentang induknya, itu bukan keputusan desain super-jelas, tetapi sudah ditetapkan untuk melakukan beberapa tindakan, menggunakan langsung variabel anggota superview (periksa tipe induk, hapus dari induk, dll). Setelah bekerja dengan PureMVC baru-baru ini, saya menjadi sedikit lebih rewel tentang abstraksi desain :) Saya akan membuat paralel antara kelas UIView dan UIViewController iPhone dan kelas Tampilan dan Mediator PureMVC - sebagian besar waktu, kelas tampilan tidak perlu tahu tentang MVC handler / interface-nya (UIViewController / Mediator).
Dimitar Dimitrov
9
Kata kunci: "kebanyakan".
Glenn Maynard
278

Dari UIResponderdokumentasi untuk nextResponder:

Kelas UIResponder tidak menyimpan atau mengatur responden berikutnya secara otomatis, sebaliknya mengembalikan nol secara default. Subclass harus mengganti metode ini untuk mengatur responden berikutnya. UIView mengimplementasikan metode ini dengan mengembalikan objek UIViewController yang mengaturnya (jika ada) atau superview-nya (jika tidak) ; UIViewController mengimplementasikan metode dengan mengembalikan tampilan pandangannya; UIWindow mengembalikan objek aplikasi, dan aplikasi UIA mengembalikan nihil.

Jadi, jika Anda mengulang tampilan nextRespondersampai tipe UIViewController, maka Anda memiliki viewController induk view.

Perhatikan bahwa masih mungkin tidak memiliki pengontrol tampilan induk. Tetapi hanya jika tampilan bukan bagian dari hierarki tampilan viewController.

Ekstensi Swift 3 dan Swift 4.1 :

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder?.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Ekstensi Swift 2:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.nextResponder()
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Kategori Objective-C:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    return (UIViewController *)responder;
}
@end

Makro ini menghindari polusi kategori:

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})
mxcl
sumber
Hanya satu hal: jika Anda khawatir tentang polusi kategori, cukup definisikan sebagai fungsi statis daripada makro. Juga typecast brutal itu berbahaya, dan di atas semua itu makro mungkin tidak benar tetapi tidak yakin.
mojuba
Makro berfungsi, saya hanya menggunakan versi makro secara pribadi. Saya memulai banyak proyek dan memiliki header saya hanya mampir di mana-mana dengan sekelompok fungsi utilitas makro ini. Menghemat waktu saya. Jika Anda tidak suka makro, Anda bisa mengadaptasinya ke dalam suatu fungsi, tetapi fungsi statis sepertinya membosankan, karena Anda harus meletakkannya di setiap file yang ingin Anda gunakan. Sepertinya Anda ingin fungsi non-statis dideklarasikan di header dan didefinisikan dalam .m di suatu tempat?
mxcl
Senang menghindari beberapa delegasi atau pemberitahuan. Terima kasih!
Ferran Maylinch
Anda harus membuatnya menjadi ekstensi UIResponder;). Posting yang sangat meneguhkan.
ScottyBlades
32

@andrey menjawab dalam satu baris (diuji dalam Swift 4.1 ):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

pemakaian:

 let vc: UIViewController = view.parentViewController
Federico Zanetello
sumber
parentViewControllertidak dapat ditentukan publicjika ekstensi dalam file yang sama dengan Anda, UIViewAnda dapat mengaturnya fileprivate, itu mengkompilasi tetapi tidak bekerja! 😐
Ini bekerja dengan baik. Saya telah menggunakan ini untuk aksi tombol kembali di dalam file .xib header umum.
McDonal_11
23

Hanya untuk tujuan debug, Anda dapat memanggil _viewDelegatetampilan untuk mendapatkan pengontrol tampilan mereka. Ini adalah API pribadi, jadi tidak aman untuk App Store, tetapi untuk debugging itu berguna.

Metode berguna lainnya:

  • _viewControllerForAncestor- dapatkan pengontrol pertama yang mengelola tampilan dalam rantai superview. (terima kasih n00neimp0rtant)
  • _rootAncestorViewController - dapatkan pengendali leluhur yang hierarki pandangannya diatur di jendela saat ini.
Leo Natan
sumber
Inilah mengapa saya sampai pada pertanyaan ini. Rupanya, 'nextResponder' melakukan hal yang sama, tetapi saya menghargai wawasan yang diberikan jawaban ini. Saya mengerti dan menyukai MVC, tetapi debugging adalah hewan yang berbeda!
mbm29414
4
Tampaknya ini hanya berfungsi pada tampilan utama pengontrol tampilan, bukan pada subview apa pun. _viewControllerForAncestorakan melintasi superview hingga menemukan yang pertama yang dimiliki pengontrol tampilan.
n00neimp0rtant
Terima kasih @ n00neimp0rtant! Saya membatalkan jawaban ini sehingga orang melihat komentar Anda.
eyuelt
Perbarui jawaban dengan lebih banyak metode.
Leo Natan
1
Ini bermanfaat saat melakukan debugging.
evanchin
10

Untuk mendapatkan referensi ke UIViewController yang memiliki UIView, Anda dapat membuat ekstensi UIResponder (yang merupakan kelas super untuk UIView dan UIViewController), yang memungkinkan untuk naik melalui rantai responden dan dengan demikian mencapai UIViewController (jika tidak mengembalikan nol).

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc
Andrey
sumber
4

Cara cepat dan umum dalam Swift 3:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}
iTSangar
sumber
2

Jika Anda tidak terbiasa dengan kode dan Anda ingin menemukan ViewController sesuai dengan tampilan yang diberikan, maka Anda dapat mencoba:

  1. Jalankan aplikasi dalam debug
  2. Arahkan ke layar
  3. Mulai Lihat inspektur
  4. Raih Tampilan yang ingin Anda temukan (atau tampilan anak yang lebih baik)
  5. Dari panel kanan dapatkan alamat (mis. 0x7fe523bd3000)
  6. Di konsol debug mulai menulis perintah:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 nextResponder]
    po [[((UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

Dalam kebanyakan kasus, Anda akan mendapatkan UIView, tetapi dari waktu ke waktu akan ada kelas berbasis UIViewController.

m4js7er
sumber
1

Saya pikir Anda dapat menyebarkan keran ke controller tampilan dan membiarkannya menanganinya. Ini adalah pendekatan yang lebih dapat diterima. Adapun mengakses pengontrol tampilan dari pandangannya, Anda harus mempertahankan referensi ke pengontrol tampilan, karena tidak ada cara lain. Lihat utas ini, mungkin membantu: Mengakses pengontrol tampilan dari tampilan

Nava Carmon
sumber
Jika Anda memiliki beberapa tampilan, dan satu menutup semua tampilan, dan Anda perlu memanggil viewWillDisappear, bukankah lebih mudah bagi pandangan itu untuk mendeteksi keran daripada menyerahkan keran ke pengontrol tampilan dan meminta pengontrol tampilan memeriksa dengan semua tampilan untuk melihat mana yang disadap?
mahboudz
0

Jenis kode aman lainnya untuk Swift 3.0

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}
Denis Krivitski
sumber
0

Sayangnya, ini tidak mungkin kecuali Anda mensubclass view dan memberikannya dengan properti instance atau yang menyimpan referensi controller view di dalamnya begitu view ditambahkan ke scene ...

Dalam kebanyakan kasus - sangat mudah untuk mengatasi masalah asli posting ini karena sebagian besar pengontrol tampilan adalah entitas terkenal bagi programmer yang bertanggung jawab untuk menambahkan subview apa pun ke ViewController's View ;-) Itulah sebabnya saya menduga Apple tidak pernah repot untuk menambahkan properti itu.

Morten Carlsen
sumber
0

Sedikit terlambat, tetapi ini adalah ekstensi yang memungkinkan Anda menemukan responden jenis apa pun, termasuk ViewController.

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder

    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }

    return resp
  }                       
}
Hackbarth
sumber
-1

Jika Anda menetapkan breakpoint, Anda bisa menempelkan ini ke debugger untuk mencetak hierarki tampilan:

po [[UIWindow keyWindow] recursiveDescription]

Anda harus dapat menemukan orang tua pandangan Anda di suatu tempat di kekacauan itu :)

Scott McCoy
sumber
recursiveDescriptionhanya mencetak hierarki tampilan , bukan pengontrol tampilan.
Alan Zeino
1
dari sana Anda dapat mengidentifikasi viewcontroller @AlanZeino
Akshay