Учитывая представление, как мне получить его viewController?
144
У меня есть указатель на UIView. Как мне получить к нему доступ UIViewController? [self superview]это другое UIView, но не то UIViewController, правда ?
По сути, я пытаюсь вызвать viewWillAppear моего viewController, поскольку мое представление отклоняется. Представление закрывается самим представлением, которое обнаруживает касание и вызывает [self removeFromSuperview]; ViewController сам не вызывает viewWillAppear / WillDisappear / DidAppear / DidDisappear.
mahboudz 03
Я имел в виду, что пытаюсь вызвать viewWillDisappear, поскольку мое представление отклоняется.
Да, superviewэто представление, которое содержит ваше представление. Ваше представление не должно знать, какой именно контроллер представления является его контроллером представления, потому что это нарушит принципы MVC.
С другой стороны, контроллер знает, за какое представление он отвечает ( self.view = myView), и обычно это представление делегирует методы / события для обработки контроллеру.
Как правило, вместо указателя на ваше представление у вас должен быть указатель на ваш контроллер, который, в свою очередь, может либо выполнять некоторую управляющую логику, либо передавать что-то своему представлению.
Я не уверен, что это нарушит принципы MVC. В любой момент у представления есть только один контроллер представления. Возможность добраться до него, чтобы передать ему сообщение, должна быть автоматической функцией, а не той, для достижения которой вам нужно работать (добавляя свойство для отслеживания). То же самое можно сказать и о взглядах: зачем вам знать, кто вы такой? Или есть ли другие взгляды братьев и сестер. Но есть способы получить эти предметы.
mahboudz
Вы в некоторой степени правы насчет представления, зная о его родительском элементе, это не очень четкое дизайнерское решение, но оно уже установлено для выполнения некоторых действий, напрямую используя переменную-член суперпредставления (проверить родительский тип, удалить из родительского и т. Д.). Недавно поработав с PureMVC, я стал немного более придирчив к абстракции дизайна :) Я бы провел параллель между классами iPhone UIView и UIViewController и классами PureMVC View и Mediator - в большинстве случаев класс View не нуждается в знать о его обработчике / интерфейсе MVC (UIViewController / Mediator).
Димитар Димитров
9
Ключевое слово: «самый».
Гленн Мейнард
284
Из UIResponderдокументации для nextResponder:
Класс UIResponder не сохраняет и не устанавливает следующего ответчика автоматически, вместо этого по умолчанию возвращает nil. Подклассы должны переопределить этот метод, чтобы установить следующего респондента. UIView реализует этот метод, возвращая объект UIViewController, который им управляет (если он есть) или его супервизор (если нет) ; UIViewController реализует метод, возвращая супервизор своего представления; UIWindow возвращает объект приложения, а UIApplication возвращает ноль.
Итак, если вы рекурсивно повторяете представление nextResponderдо тех пор , пока оно не станет типом UIViewController, тогда у вас будет родительский viewController любого представления.
Обратите внимание, что у него все еще может не быть родительского контроллера представления. Но только если представление не является частью иерархии представлений viewController.
Только одно: если вы беспокоитесь о загрязнении категорий, просто определите его как статическую функцию, а не как макрос. Также опасно грубое приведение типов, и, кроме того, макрос может быть некорректным, но я не уверен.
Mojuba 05
Макрос работает, лично использую только макроверсию. Я начинаю много проектов и получаю заголовок, который я просто добавляю повсюду с кучей этих вспомогательных макросов. Экономит время. Если вам не нравятся макросы, вы можете адаптировать их к функциям, но статические функции кажутся утомительными, поскольку вы должны затем помещать их в каждый файл, который хотите использовать. Похоже, вместо этого вы хотите, чтобы нестатическая функция была объявлена в заголовке и определена где-нибудь в .m?
mxcl 06
Приятно избегать некоторых делегатов или уведомлений. Благодарность!
Ферран Мэйлинч 01
Вы должны сделать его продолжением UIResponder;). Очень поучительный пост.
ScottyBlades
35
@andrey ответ в одну строку (проверено в Swift 4.1 ):
parentViewControllerне может быть определено, publicесли расширение находится в одном файле с вашим, UIViewвы можете установить его fileprivate, оно компилируется, но не работает! 😐
Работает нормально. Я использовал это для действия кнопки «Назад» в общем файле заголовка .xib.
McDonal_11
В Xamarin: public static class Extensions {public static UIViewController ParentViewController (это представление UIResponder) => (view.NextResponder as UIViewController) ?? view.NextResponder? .ParentViewController (); }
Softlion
23
Только для целей отладки вы можете вызывать _viewDelegateпредставления, чтобы получить их контроллеры представлений. Это частный API, поэтому он небезопасен для App Store, но полезен для отладки.
Другие полезные методы:
_viewControllerForAncestor- получить первый контроллер, который управляет представлением в цепочке супервизора. (спасибо n00neimp0rtant)
_rootAncestorViewController - получить родительский контроллер, иерархия представлений которого установлена в текущем окне.
Именно поэтому я пришел к этому вопросу. По-видимому, nextResponder делает то же самое, но я ценю понимание, которое дает этот ответ. Я понимаю и люблю MVC, но отладка - другое дело!
mbm29414 07
4
Похоже, это работает только в основном представлении контроллера представления, а не на каких-либо его подчиненных представлениях. _viewControllerForAncestorбудет проходить по супервизорам, пока не найдет первый, принадлежащий контроллеру представления.
n00neimp0rtant
Спасибо @ n00neimp0rtant! Я поддерживаю этот ответ, чтобы люди видели ваш комментарий.
eyuelt
Обновите ответ с помощью дополнительных методов.
Лео Натан,
1
Это полезно при отладке.
evanchin
10
Чтобы получить ссылку на UIViewController, имеющий UIView, вы можете сделать расширение UIResponder (который является суперклассом для UIView и UIViewController), который позволяет подниматься по цепочке респондентов и, таким образом, достигать UIViewController (в противном случае возвращается ноль).
Я думаю, вы можете передать касание контроллеру представления и позволить ему обрабатывать его. Это более приемлемый подход. Что касается доступа к контроллеру представления из его представления, вы должны поддерживать ссылку на контроллер представления, поскольку другого пути нет. См. Эту ветку, это может помочь:
Доступ к контроллеру представления из представления
Если у вас есть несколько представлений, и одно закрывает все представления, и вам нужно вызвать viewWillDisappear, не было бы проще для этого представления обнаружить касание, чем передать касание контроллеру представления и проверить контроллер представления со всеми видами, чтобы увидеть, какой из них был выбран?
mahboudz
1
В UIView есть переменная под названием window?
Итак, это вернет контроллер представления
вы можете получить к нему доступ, как это self.window? .rootViewController
Увы, это невозможно, если вы не подклассифицируете представление и не предоставите ему свойство экземпляра или что-то подобное, которое хранит ссылку на контроллер представления внутри него, когда представление добавлено в сцену ...
В большинстве случаев - очень легко обойти исходную проблему этого поста, поскольку большинство контроллеров представлений являются хорошо известными сущностями программисту, который отвечал за добавление любых подпредставлений в представление ViewController ;-) Вот почему я предполагаю, что Apple никогда не удосужился добавить это свойство.
Ответы:
Да,
superview
это представление, которое содержит ваше представление. Ваше представление не должно знать, какой именно контроллер представления является его контроллером представления, потому что это нарушит принципы MVC.С другой стороны, контроллер знает, за какое представление он отвечает (
self.view = myView
), и обычно это представление делегирует методы / события для обработки контроллеру.Как правило, вместо указателя на ваше представление у вас должен быть указатель на ваш контроллер, который, в свою очередь, может либо выполнять некоторую управляющую логику, либо передавать что-то своему представлению.
источник
Из
UIResponder
документации дляnextResponder
:Итак, если вы рекурсивно повторяете представление
nextResponder
до тех пор , пока оно не станет типомUIViewController
, тогда у вас будет родительский viewController любого представления.Обратите внимание, что у него все еще может не быть родительского контроллера представления. Но только если представление не является частью иерархии представлений 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 } }
Категория 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
Этот макрос позволяет избежать загрязнения категорий:
#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, соответствующий данному представлению, вы можете попробовать:
В большинстве случаев вы получите UIView, но время от времени будет класс на основе UIViewController.
источник
Я думаю, вы можете передать касание контроллеру представления и позволить ему обрабатывать его. Это более приемлемый подход. Что касается доступа к контроллеру представления из его представления, вы должны поддерживать ссылку на контроллер представления, поскольку другого пути нет. См. Эту ветку, это может помочь: Доступ к контроллеру представления из представления
источник
В UIView есть переменная под названием window?
Итак, это вернет контроллер представления
вы можете получить к нему доступ, как это self.window? .rootViewController
источник
Больше безопасного кода для 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 ;-) Вот почему я предполагаю, что 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
печатает только вид иерархии, а не смотреть контроллеров.