Bir görünüm verildiğinde, onun viewController'ı nasıl edinebilirim?


141

İçin bir işaretçim var UIView. Nasıl erişebilirim UIViewController? [self superview]başka UIView, ama değil UIViewController, değil mi?


Sanırım bu iş parçacığının cevapları var: iPhone'da UIView'den UIViewController'a mı?
Ushox

Temelde, benim görüşüm reddedilir gibi benim viewController'ın viewWillAppear çağırmak çalışıyorum. Görünüm, bir musluğu algılayıp [self removeFromSuperview] çağrısı yapan görünümün kendisi tarafından reddediliyor; ViewController viewWillAppear / WillDisappear / DidAppear / DidDisappear kendisini çağırmıyor.
mahboudz

Görüşümün reddedildiği için viewWillDisappear'ı çağırmaya çalışıyordum.
mahboudz

Yanıtlar:


42

Evet, superviewgörünümünüzü içeren görünümdür. Görüşünüz tam olarak hangisinin görünüm denetleyicisi olduğunu bilmemelidir, çünkü bu MVC ilkelerini ihlal edecektir.

Öte yandan, denetleyici hangi görünümden sorumlu olduğunu ( self.view = myView) bilir ve genellikle bu görünüm denetleyiciye yönelik yöntemleri / olayları temsil eder.

Genellikle, görünümünüze bir işaretçi yerine, denetleyicinize bir işaretçi olmalıdır, bu da kontrol eden bir mantık yürütebilir veya görünümüne bir şey iletebilir.


24
MVC ilkelerini ihlal edip etmeyeceğinden emin değilim. Herhangi bir noktada, bir görünümün yalnızca bir görünüm denetleyicisi vardır. Bir iletiyi ona geri iletebilmek için ona ulaşabilmek, ulaşmak için çalışmanız gereken bir yer değil (izleyecek bir özellik ekleyerek) otomatik bir özellik olmalıdır. Görüşler hakkında da aynı şey söylenebilir: neden çocuk olduğunuzu bilmeniz gerekiyor? Ya da başka kardeş görüşler olup olmadığı. Yine de bu nesneleri elde etmenin yolları var.
mahboudz

Görünüm konusunda biraz haklısınız, ebeveynini biliyorsunuz, bu süper net tasarım kararı değil, ancak doğrudan bir denetim üyesi değişkeni (ebeveyn türünü kontrol edin, ebeveynten kaldır, vb.) Son zamanlarda PureMVC ile çalıştıktan sonra, tasarım soyutlaması konusunda biraz daha az seçici oldum :) iPhone'un UIView ve UIViewController sınıfları ile PureMVC'nin View and Mediator sınıfları arasında paralellik yapardım - çoğu zaman View sınıfının MVC işleyicisi / arayüzü hakkında bilgi sahibi olur (UIViewController / Arabulucu).
Dimitar Dimitrov

9
Anahtar kelime: "çoğu".
Glenn Maynard

278

İçin UIResponderbelgelerden nextResponder:

UIResponder sınıfı, bir sonraki yanıtlayıcıyı otomatik olarak kaydetmez veya ayarlamaz, bunun yerine varsayılan olarak sıfır döndürür. Bir sonraki yanıtlayıcıyı ayarlamak için alt sınıfların bu yöntemi geçersiz kılması gerekir. UIView bu yöntemi, onu yöneten UIViewController nesnesini (varsa) veya denetimini (yoksa) döndürerek uygular ; UIViewController yöntemi bakış açısının denetimine döndürerek uygular; UIWindow uygulama nesnesini ve UIApplication nil değerini döndürür.

Bu nedenle, bir görünümün nextRespondertürüne ulaşıncaya kadar UIViewControllerokuyorsanız, herhangi bir görünümün üst görünümü var.

Yine de üst görünüm denetleyicisine sahip olmayabileceğini unutmayın . Ancak yalnızca görünüm bir viewController görünümünün görünüm hiyerarşisinin bir parçası değilse.

Swift 3 ve Swift 4.1 uzantısı:

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

Swift 2 uzantısı:

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

Objective-C kategorisi:

@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

Bu makro kategori kirliliğini önler:

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})

Tek bir şey: kategori kirliliği hakkında endişeleniyorsanız, makrodan ziyade statik bir işlev olarak tanımlayın. Ayrıca acımasız yazım tehlikelidir ve bunun üzerine makro doğru olmayabilir ama emin değilim.
mojuba

Makro çalışıyor, ben sadece makro sürümünü şahsen kullanıyorum. Bir çok projeye başlıyorum ve bu makro yardımcı fonksiyonlarının bir demetiyle her yere bıraktığım bir başlık var. Zaman kazandırır. Makrolardan hoşlanmıyorsanız, bir işleve uyarlayabilirsiniz, ancak statik işlevler sıkıcı görünebilir, çünkü daha sonra kullanmak istediğiniz her dosyaya bir tane koymanız gerekir. Bunun yerine, başlıkta bildirilen ve .m bir yerde tanımlanmış statik olmayan bir işlev ister misiniz?
mxcl

Bazı delegelerden veya bildirimlerden kaçınmak güzel. Teşekkürler!
Ferran Maylinch

Bunu UIResponder;) ' nin bir uzantısı yapmalısınız . Çok düzenleme sonrası.
ScottyBlades

32

Bir satırda @andrey yanıtı ( Swift 4.1'de test edilmiştir ):

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

kullanımı:

 let vc: UIViewController = view.parentViewController

parentViewControllerpublicuzantısı ile aynı dosya ise tanımlanamaz UIViewonu ayarlayabilirsiniz fileprivate, derler ama çalışmıyor! 😐

İyi çalışıyor. Bunu ortak üstbilgi .xib dosyası içindeki geri düğmesi eylemi için kullandım.
McDonal_11

23

Yalnızca hata ayıklama amacıyla _viewDelegate, görünüm denetleyicilerini almak için görünümleri arayabilirsiniz . Bu özel API'dir, bu nedenle App Store için güvenli değildir, ancak hata ayıklama için kullanışlıdır.

Diğer faydalı yöntemler:

  • _viewControllerForAncestor- denetim zincirindeki bir görünümü yöneten ilk denetleyiciyi edinin. (teşekkürler n00neimp0rtant)
  • _rootAncestorViewController - görünüm hiyerarşisi şu anda pencerede ayarlanmış olan ata kontrolörünü alın.

Bu soruya tam olarak bu yüzden geldim. Görünüşe göre, 'nextResponder' aynı şeyi yapıyor, ancak bu cevabın sağladığı içgörüyü takdir ediyorum. MVC'yi anlıyorum ve seviyorum, ancak hata ayıklama farklı bir hayvandır!
mbm29414

4
Görünüşe göre bu yalnızca görünüm denetleyicisinin ana görünümünde çalışır, herhangi bir alt görünümünde değil. _viewControllerForAncestorbir görünüm denetleyicisine ait olan ilk tanesini bulana kadar denetimleri geçecektir.
n00neimp0rtant

Teşekkürler @ n00neimp0rtant! Bu yorumu destekliyorum, böylece insanlar yorumunuzu görüyor.
eyuelt

Cevabı daha fazla yöntemle güncelleyin.
Leo Natan

1
Bu hata ayıklama sırasında faydalıdır.
evanchin

10

UIView'e sahip UIViewController'a başvurmak için, yanıtlayıcı zincirinden yukarı çıkıp UIViewController'a (aksi halde sıfır) ulaşan UIResponder (UIView ve UIViewController için süper sınıftır) uzantısını yapabilirsiniz.

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

4

Swift 3'te hızlı ve genel yol:

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

2

Kodu bilmiyorsanız ve verilen görünüme karşılık gelen ViewController'ı bulmak istiyorsanız, deneyebilirsiniz:

  1. Uygulamayı hata ayıklamada çalıştır
  2. Ekrana gidin
  3. Görünümü Başlat denetçisi
  4. Bulmak istediğiniz Görünümü (veya alt görünümü daha da iyi) alın
  5. Sağ bölmeden adresi alın (örneğin 0x7fe523bd3000)
  6. Hata ayıklama konsolunda komutları yazmaya başlayın:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 nextResponder]
    po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

Çoğu durumda UIView alırsınız, ancak zaman zaman UIViewController tabanlı sınıf olur.


1

Musluğu görünüm denetleyicisine yayabilir ve işlemesine izin verebilirsiniz. Bu daha kabul edilebilir bir yaklaşımdır. Bir görünüm denetleyicisine kendi görünümünden erişmeye gelince, başka bir yol olmadığından bir görünüm denetleyicisine başvurmanız gerekir. Bu konuya bakın, yardımcı olabilir: Görünüm denetleyicisine bir görünümden erişme


Bir avuç görünümünüz varsa ve biri tüm görünümleri kapatıyorsa ve viewWillDisappear'ı aramanız gerekiyorsa, o görünümün musluğu algılaması, musluğu görünüm denetleyicisine teslim etmekten ve görünüm denetleyicisini kontrol ettirmekten daha kolay olmaz hangisinin üzerine dokunulduğunu görmek için tüm görünümlerle?
mahboudz

0

Swift 3.0 için daha fazla tür güvenlik kodu

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

0

Ne yazık ki, görünümü alt sınıfa ayırmadıkça ve görünüm sahneye eklendikten sonra görünüm denetleyicisi başvurusunu depolayan bir örnek özelliği veya benzerleri sağlamadığınız sürece bu mümkün değildir ...

Çoğu durumda - çoğu görünüm denetleyicisi, ViewController'ın Görünümüne herhangi bir alt görünüm eklemekten sorumlu olan programcıya iyi bilinen varlıklar olduğundan, bu yazının orijinal sorunu etrafında çalışmak çok kolaydır.Bu yüzden Apple'ın bu özelliği eklemek için hiç uğraşmadım.


0

Biraz geç, ancak işte ViewController dahil her türden bir yanıtlayıcı bulmanızı sağlayan bir uzantı.

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

-1

Bir kesme noktası ayarlarsanız, görünüm hiyerarşisini yazdırmak için bunu hata ayıklayıcıya yapıştırabilirsiniz:

po [[UIWindow keyWindow] recursiveDescription]

Bu karmaşa içinde görünümünüzün ebeveynini bir yerde bulabilmeniz gerekir :)


recursiveDescriptiongörünüm denetleyicilerini değil, yalnızca görünüm hiyerarşisini yazdırır .
Alan Zeino

1
ondan @AlanZeino
Akshay
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.