У мене вказівник на a UIView
. Як я можу отримати доступ до нього UIViewController
? [self superview]
інший UIView
, але не той UIViewController
, правда?
У мене вказівник на a UIView
. Як я можу отримати доступ до нього UIViewController
? [self superview]
інший UIView
, але не той UIViewController
, правда?
Відповіді:
Так, superview
це вид, який містить ваш погляд. Ваш погляд не повинен знати, що саме є його контролером перегляду, оскільки це порушило б принципи MVC.
Контролер, з іншого боку, знає, за який погляд він відповідає ( self.view = myView
), і зазвичай цей погляд делегує методи / події для обробки з контролером.
Як правило, замість вказівника на ваш погляд, у вас повинен бути вказівник на ваш контролер, який, в свою чергу, може або виконувати якусь керуючу логіку, або передавати щось його перегляду.
З UIResponder
документації для nextResponder
:
Клас UIResponder не зберігає і не встановлює наступного відповіді автоматично, замість цього повертає нуль за замовчуванням. Підкласи повинні замінити цей метод, щоб встановити наступного відповіді. UIView реалізує цей метод, повертаючи об'єкт UIViewController, який ним керує (якщо у нього є) або його супервізор (якщо його немає) ; UIViewController реалізує метод, повертаючи перегляд його подання; UIWindow повертає об’єкт програми, а UIApplication повертає нуль.
Отже, якщо ви повторюєте перегляд, nextResponder
доки він не має типу UIViewController
, тоді у вас є батьківський вигляд будь-якого видуController.
Зверніть увагу, що він все ще не може мати контролер перегляду батьків. Але лише якщо представлення не є частиною ієрархії подання viewController.
Розширення Swift 3 та 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
}
}
Розширення 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
}
}
Об'єктивна категорія:
@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
Цей макрос дозволяє уникнути забруднення категорії:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
UIResponder
;). Дуже повчальний пост.
Відповідь @andrey в одному рядку (перевірено в Swift 4.1 ):
extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}
використання:
let vc: UIViewController = view.parentViewController
parentViewController
не можна визначити, public
якщо розширення знаходиться в одному файлі з вашим, UIView
ви можете встановити його fileprivate
, воно компілюється, але воно не працює! 😐
Лише для налагодження, ви можете зателефонувати _viewDelegate
на представлення даних, щоб отримати їх контролери перегляду. Це приватний API, тому не безпечний для App Store, але для налагодження він корисний.
Інші корисні методи:
_viewControllerForAncestor
- отримати перший контролер, який керує поданням у ланцюзі нагляду. (спасибі n00neimp0rtant)_rootAncestorViewController
- отримати контролер пращура, ієрархія перегляду якого встановлена у вікні в даний час._viewControllerForAncestor
буде обробляти нагляди, поки не знайде перший, який належить контролеру перегляду.
Для отримання посилання на UIViewController, що має UIView, ви можете зробити розширення UIResponder (що є суперкласом для UIView та UIViewController), що дозволяє пройти вгору по ланцюгу відповідей і, таким чином, дійти до UIViewController (інакше повертається нуль).
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
Швидкий та загальний шлях у 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)
}
Якщо ви не знайомі з кодом і хочете знайти ViewController, який відповідає даному представленню, ви можете спробувати:
po (UIView *) 0x7fe523bd3000 po [(UIView *) 0x7fe523bd3000 nextResponder] po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder] ...
У більшості випадків ви отримаєте UIView, але час від часу буде клас на базі UIViewController.
Я думаю, ви можете розповсюдити кран до контролера перегляду і дозволити йому це обробляти. Це більш прийнятний підхід. Щодо доступу до контролера перегляду з його подання, вам слід підтримувати посилання на контролер перегляду, оскільки іншого способу немає. Дивіться цю тему, це може допомогти: Доступ до контролера перегляду з подання
Більш безпечний код для 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
}
}
На жаль, це неможливо, якщо ви не підкласируєте подання і не надаєте йому властивість екземпляра або подібне, яке зберігає посилання контролера перегляду всередині нього, як тільки вигляд буде доданий на сцену ...
У більшості випадків - дуже просто подолати оригінальну проблему цієї посади, оскільки більшість контролерів перегляду є відомими особами програміста, який відповідав за додавання будь-яких підвидів перегляду ViewController's ;-) Тому я думаю, що Apple ніколи не намагався додати цю властивість.
Трохи запізнюємось, але ось розширення, яке дозволяє знайти відповідь будь-якого типу, включаючи 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
}
}
Якщо встановити точку розриву, ви можете вставити це у відладчик, щоб надрукувати ієрархію перегляду:
po [[UIWindow keyWindow] recursiveDescription]
Ви повинні мати можливість знайти батьківського погляду десь у тому безладі :)
recursiveDescription
друкує лише ієрархію перегляду , а не контролери перегляду.