Як переглянути подання, як я можу отримати його перегляд Контролер?


141

У мене вказівник на a UIView. Як я можу отримати доступ до нього UIViewController? [self superview]інший UIView, але не той UIViewController, правда?


Я думаю, що ця тема має відповіді: Перейти до UIViewController з UIView на iPhone?
Ushox

В основному, я намагаюся викликати погляд мого виду контролераWillAppear, оскільки мій погляд відхиляється. Погляд відхиляється самим представленням, яке виявляє дотик та викликає [self removeFromSuperview]; ViewController не викликає viewWillAppear / WillDisappear / DidAppear / DidDisappear.
mahboudz

Я мав на увазі, що я намагаюся викликати viewWillDisappear, оскільки мій погляд відхиляється.
mahboudz

1
Можливий дублікат Get to UIViewController від UIView?
Ефрен

Відповіді:


42

Так, superviewце вид, який містить ваш погляд. Ваш погляд не повинен знати, що саме є його контролером перегляду, оскільки це порушило б принципи MVC.

Контролер, з іншого боку, знає, за який погляд він відповідає ( self.view = myView), і зазвичай цей погляд делегує методи / події для обробки з контролером.

Як правило, замість вказівника на ваш погляд, у вас повинен бути вказівник на ваш контролер, який, в свою чергу, може або виконувати якусь керуючу логіку, або передавати щось його перегляду.


24
Я не впевнений, чи це порушить принципи MVC. У будь-якій точці перегляду є лише один контролер перегляду. Можливість дістатися до нього, щоб повернути йому повідомлення, має бути автоматичною функцією, а не тією, де вам доведеться працювати (додаючи властивість для відстеження). Можна сказати те саме про погляди: навіщо вам знати, хто ви така дитина? Або є інші погляди братів та сестер. Але є способи отримати ці об'єкти.
mahboudz

Ви дещо праві щодо перегляду, знаючи про його батьків, це не надто чітке дизайнерське рішення, але вже встановлено, щоб виконати деякі дії, використовуючи безпосередньо змінну члена перегляду (перевірити тип батьків, видалити з батьківського і т.д.). Нещодавно працюючи з PureMVC, я став трохи більш прискіпливим щодо абстрагування дизайну :) Я би зробив паралель між класами UIView та UIViewController від iPhone та класами View та Mediator PureMVC - більшість часу класу View не потрібно знати про його обробник / інтерфейс MVC (UIViewController / Mediator).
Димитар Димитров

9
Ключове слово: "найбільше".
Гленн Мейнард

278

З 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; \
})

Тільки одне: якщо ви турбуєтесь про забруднення категорії, просто визначте це як статичну функцію, а не як макрос. Також жорстокий набір тексту небезпечний, і крім того, макрос може бути невірним, але я не впевнений.
моджуба

Макрос працює, я використовую лише версію макроса особисто. Я запускаю безліч проектів і маю заголовок, який я просто запускаю скрізь, з купою цих функцій утиліти макросу. Економить мені час. Якщо вам не подобаються макроси, ви можете адаптувати їх до функції, але статичні функції здаються стомлюючими, оскільки вам доведеться вводити їх у кожен файл, який ви хочете ним використовувати. Здається, замість цього ви хочете, щоб нестатична функція була оголошена в заголовку і десь визначена в .m?
mxcl

Приємно уникати делегатів чи повідомлень. Дякую!
Ферран Мейлінч

Ви повинні зробити це продовженням UIResponder;). Дуже повчальний пост.
ScottyBlades

32

Відповідь @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, воно компілюється, але воно не працює! 😐

Це прекрасно працює. Я використовував це для дії кнопки "назад" у загальному файлі .xib заголовка.
McDonal_11

23

Лише для налагодження, ви можете зателефонувати _viewDelegateна представлення даних, щоб отримати їх контролери перегляду. Це приватний API, тому не безпечний для App Store, але для налагодження він корисний.

Інші корисні методи:

  • _viewControllerForAncestor- отримати перший контролер, який керує поданням у ланцюзі нагляду. (спасибі n00neimp0rtant)
  • _rootAncestorViewController - отримати контролер пращура, ієрархія перегляду якого встановлена ​​у вікні в даний час.

Саме тому я прийшов до цього питання. Мабуть, "nextResponder" робить те саме, але я ціную розуміння, яке надає ця відповідь. Я розумію і люблю MVC, але налагодження - це інша тварина!
mbm29414

4
Здається, це працює лише на головному огляді диспетчера перегляду, а не на його підзагляді. _viewControllerForAncestorбуде обробляти нагляди, поки не знайде перший, який належить контролеру перегляду.
n00neimp0rtant

Дякую @ n00neimp0rtant! Я підтримую цю відповідь, щоб люди бачили ваш коментар.
eyuelt

Оновіть відповідь додатковими методами.
Лев Натан

1
Це корисно при налагодженні.
evanchin

10

Для отримання посилання на 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

4

Швидкий та загальний шлях у 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)
}

2

Якщо ви не знайомі з кодом і хочете знайти ViewController, який відповідає даному представленню, ви можете спробувати:

  1. Запустити додаток у налагодженні
  2. Перейдіть на екран
  3. Початок роботи інспектора
  4. Візьміть вигляд, який ви хочете знайти (або ще кращий вигляд дитини)
  5. З правої області отримайте адресу (наприклад, 0x7fe523bd3000)
  6. У консолі налагодження починайте писати команди:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 nextResponder]
    po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

У більшості випадків ви отримаєте UIView, але час від часу буде клас на базі UIViewController.


1

Я думаю, ви можете розповсюдити кран до контролера перегляду і дозволити йому це обробляти. Це більш прийнятний підхід. Щодо доступу до контролера перегляду з його подання, вам слід підтримувати посилання на контролер перегляду, оскільки іншого способу немає. Дивіться цю тему, це може допомогти: Доступ до контролера перегляду з подання


Якщо у вас є декілька поглядів, і один закриває всі представлення даних, і вам потрібно зателефонувати viewWillDisappear, хіба не було б легше для цього виду виявити кран, ніж передати його до контролера перегляду та перевірити контролер перегляду з усіма переглядами, щоб побачити, на який з них постукали?
mahboudz

0

Більш безпечний код для 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
    }
}

0

На жаль, це неможливо, якщо ви не підкласируєте подання і не надаєте йому властивість екземпляра або подібне, яке зберігає посилання контролера перегляду всередині нього, як тільки вигляд буде доданий на сцену ...

У більшості випадків - дуже просто подолати оригінальну проблему цієї посади, оскільки більшість контролерів перегляду є відомими особами програміста, який відповідав за додавання будь-яких підвидів перегляду ViewController's ;-) Тому я думаю, що Apple ніколи не намагався додати цю властивість.


0

Трохи запізнюємось, але ось розширення, яке дозволяє знайти відповідь будь-якого типу, включаючи 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
  }                       
}

-1

Якщо встановити точку розриву, ви можете вставити це у відладчик, щоб надрукувати ієрархію перегляду:

po [[UIWindow keyWindow] recursiveDescription]

Ви повинні мати можливість знайти батьківського погляду десь у тому безладі :)


recursiveDescriptionдрукує лише ієрархію перегляду , а не контролери перегляду.
Алан Зейно

1
від ви можете визначити ViewController @AlanZeino
Акшай
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.