Mam wskaźnik do UIView
. Jak uzyskać dostęp do jego UIViewController
? [self superview]
jest inny UIView
, ale nie UIViewController
, prawda?
Mam wskaźnik do UIView
. Jak uzyskać dostęp do jego UIViewController
? [self superview]
jest inny UIView
, ale nie UIViewController
, prawda?
Odpowiedzi:
Tak, superview
jest to widok, który zawiera Twój widok. Twój widok nie powinien wiedzieć, który dokładnie jest jego kontrolerem widoku, ponieważ złamałoby to zasady MVC.
Z drugiej strony kontroler wie, za który widok jest odpowiedzialny ( self.view = myView
) i zwykle ten widok deleguje metody / zdarzenia do obsługi do kontrolera.
Zazwyczaj zamiast wskaźnika do widoku powinieneś mieć wskaźnik do kontrolera, który z kolei może albo wykonać jakąś logikę sterującą, albo przekazać coś do swojego widoku.
Z UIResponder
dokumentacji dla nextResponder
:
Klasa UIResponder nie przechowuje ani nie ustawia automatycznie następnego respondera, zamiast tego domyślnie zwraca nil. Podklasy muszą przesłonić tę metodę, aby ustawić następny responder. UIView implementuje tę metodę, zwracając obiekt UIViewController, który nim zarządza (jeśli taki posiada) lub jego superview (jeśli nie) ; UIViewController implementuje metodę, zwracając superview swojego widoku; UIWindow zwraca obiekt aplikacji, a UIApplication zwraca nil.
Tak więc, jeśli powtarzasz widok, nextResponder
dopóki nie stanie się on typu UIViewController
, masz nadrzędny atrybut viewController dowolnego widoku.
Zauważ, że nadal może nie mieć nadrzędnego kontrolera widoku. Ale tylko wtedy, gdy widok nie jest częścią hierarchii widoków viewControllera.
Rozszerzenie Swift 3 i 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
}
}
Rozszerzenie 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
}
}
Kategoria celu-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
To makro pozwala uniknąć zanieczyszczenia kategorii:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
UIResponder
;). Bardzo budujący post.
@andrey odpowiedź w jednej linii (testowane w Swift 4.1 ):
extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}
stosowanie:
let vc: UIViewController = view.parentViewController
parentViewController
nie można zdefiniować, public
jeśli rozszerzenie znajduje się w tym samym pliku, co Twoje, UIView
możesz je ustawić fileprivate
, kompiluje się, ale nie działa! 😐
Tylko do celów debugowania można wywoływać _viewDelegate
widoki, aby uzyskać ich kontrolery widoku. To jest prywatny interfejs API, więc nie jest bezpieczny dla App Store, ale do debugowania jest przydatny.
Inne przydatne metody:
_viewControllerForAncestor
- pobierz pierwszy kontroler, który zarządza widokiem w łańcuchu superview. (dzięki n00neimp0rtant)_rootAncestorViewController
- pobierz kontroler nadrzędny, którego hierarchia widoków jest aktualnie ustawiona w oknie._viewControllerForAncestor
przejdzie w górę nadzorów, aż znajdzie pierwszy, który należy do kontrolera widoku.
Aby uzyskać odniesienie do UIViewController mającego UIView, możesz utworzyć rozszerzenie UIResponder (która jest superklasą dla UIView i UIViewController), które pozwala przejść w górę przez łańcuch responder i w ten sposób dotrzeć do UIViewController (w przeciwnym razie zwraca nil).
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
Szybki i ogólny sposób w 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)
}
Jeśli nie znasz kodu i chcesz znaleźć ViewController odpowiadający danemu widokowi, możesz spróbować:
po (UIView *) 0x7fe523bd3000 po [(UIView *) 0x7fe523bd3000 nextResponder] po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder] ...
W większości przypadków otrzymasz UIView, ale od czasu do czasu będzie tam klasa oparta na UIViewController.
Myślę, że możesz przekazać kran do kontrolera widoku i pozwolić mu się tym zająć. To bardziej akceptowalne podejście. Jeśli chodzi o dostęp do kontrolera widoku z jego widoku, powinieneś zachować odwołanie do kontrolera widoku, ponieważ nie ma innego sposobu. Zobacz ten wątek, może pomóc: Dostęp do kontrolera widoku z widoku
Bardziej bezpieczny kod dla 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
}
}
Niestety, jest to niemożliwe, chyba że podklasujesz widok i nie udostępnisz mu właściwości instancji lub podobnej, która przechowuje odwołanie do kontrolera widoku w nim po dodaniu widoku do sceny ...
W większości przypadków - bardzo łatwo jest obejść pierwotny problem tego postu, ponieważ większość kontrolerów widoku to jednostki dobrze znane programiście, który był odpowiedzialny za dodanie jakichkolwiek podwidoków do widoku ViewControllera ;-) Dlatego domyślam się, że Apple nigdy nie zawracał sobie głowy dodaniem tej własności
Trochę późno, ale tutaj jest rozszerzenie, które umożliwia znalezienie respondera dowolnego typu, w tym 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
}
}
Jeśli ustawisz punkt przerwania, możesz wkleić to do debugera, aby wydrukować hierarchię widoków:
po [[UIWindow keyWindow] recursiveDescription]
Gdzieś w tym bałaganie powinieneś znaleźć rodzica swojego widoku :)
recursiveDescription
drukuje tylko hierarchię widoków , a nie kontrolery widoku.