Есть ли встроенный способ получить от UIView
до UIViewController
? Я знаю, что вы можете получить от UIViewController
до UIView
через [self view]
, но мне было интересно, есть ли обратная ссылка?
Получить UIViewController из UIView?
Ответ 1
Поскольку это был принятый ответ в течение длительного времени, я чувствую, что мне нужно исправить его с лучшим ответом.
Некоторые комментарии о необходимости:
- Ваше представление не должно иметь прямого доступа к контроллеру представления.
- Вместо этого представление должно быть независимым от контроллера представления и иметь возможность работать в разных контекстах.
- Если вам нужен интерфейс для взаимодействия с контроллером представления, рекомендуемый способ и то, что Apple делает через Cocoa, использовать шаблон делегата.
Пример реализации:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
Интерфейсы представления с его делегатом (например, UITableView
), и его не волнует, реализовано ли это в контроллере представления или в любом другом классе, который вы используете.
Мой первоначальный ответ: я не рекомендую это, ни оставшиеся ответы, где достигается прямой доступ к контроллеру представления.
Нет встроенного способа сделать это. Хотя вы можете обойти это, добавив IBOutlet
в UIView
и подключив их в Interface Builder, это не рекомендуется. Представление не должно знать о контроллере представления. Вместо этого вы должны сделать, как @Phil M предлагает и создать протокол, который будет использоваться в качестве делегата.
Ответ 2
Используя пример, опубликованный Brock, я изменил его так, что это категория UIView, а не UIViewController, и сделала его рекурсивным, чтобы любое подзапрос мог (надеюсь) найти родительский UIViewController.
@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
- (id) traverseResponderChainForUIViewController;
@end
@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
// convenience function for casting and to "mask" the recursive function
return (UIViewController *)[self traverseResponderChainForUIViewController];
}
- (id) traverseResponderChainForUIViewController {
id nextResponder = [self nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return nextResponder;
} else if ([nextResponder isKindOfClass:[UIView class]]) {
return [nextResponder traverseResponderChainForUIViewController];
} else {
return nil;
}
}
@end
Чтобы использовать этот код, добавьте его в новый файл класса (я назвал мой "UIKitCategories" ) и удалю данные класса... скопируйте @interface в заголовок и @implementation в файл .m. Затем в вашем проекте #import "UIKitCategories.h" и используйте код UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
Ответ 3
UIView
является подклассом UIResponder
. UIResponder
излагает метод -nextResponder
с реализацией, которая возвращает nil
. UIView
переопределяет этот метод, как описано в UIResponder
(по какой-либо причине вместо UIView
) следующим образом: если в представлении есть контроллер вида, он возвращается -nextResponder
. Если нет контроллера просмотра, метод вернет супервизор.
Добавьте это в свой проект, и вы готовы к рулону.
@interface UIView (APIFix)
- (UIViewController *)viewController;
@end
@implementation UIView (APIFix)
- (UIViewController *)viewController {
if ([self.nextResponder isKindOfClass:UIViewController.class])
return (UIViewController *)self.nextResponder;
else
return nil;
}
@end
Теперь UIView
имеет рабочий метод для возврата контроллера вида.
Ответ 4
Я бы предложил более легкий подход для прохождения всей цепочки ответов, не добавляя категорию в UIView:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
Ответ 5
Объединяя несколько уже предоставленных ответов, я отправляю на него также с моей реализацией:
@implementation UIView (AppNameAdditions)
- (UIViewController *)appName_viewController {
/// Finds the view view controller.
// Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
Class vcc = [UIViewController class];
// Traverse responder chain. Return first found view controller, which will be the view view controller.
UIResponder *responder = self;
while ((responder = [responder nextResponder]))
if ([responder isKindOfClass: vcc])
return (UIViewController *)responder;
// If the view controller isn't found, return nil.
return nil;
}
@end
Категория является частью моей статической библиотеки ARC-enabled, которую я отправляю на каждое созданное мной приложение. Он был протестирован несколько раз, и я не обнаружил никаких проблем или утечек.
P.S.: Вам не нужно использовать такую категорию, как я, если соответствующее представление является вашим подклассом. В последнем случае просто поставьте этот метод в свой подкласс, и вы хорошо пойдете.
Ответ 6
Даже если это технически можно решить, как рекомендует pgb, IMHO, это недостаток дизайна. В представлении не должно быть необходимости знать контроллер.
Ответ 7
Я изменил de, чтобы передать любой вид, кнопку, ярлык и т.д., чтобы получить его родительский UIViewController
. Вот мой код.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Изменить версию Swift 3
class func viewController(_ view: UIView) -> UIViewController {
var responder: UIResponder? = view
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
Изменить 2: - Быстрое расширение
extension UIView
{
//Get Parent View Controller from any view
func parentViewController() -> UIViewController {
var responder: UIResponder? = self
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
}
Ответ 8
Не забывайте, что вы можете получить доступ к контроллеру корневого представления для окна, в котором рассматривается представление. Оттуда, если вы, например, используя контроллер навигационного представления и хотите нажать на него новый вид:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Сначала вам нужно правильно настроить свойство rootViewController для окна. Сделайте это, когда вы сначала создаете контроллер, например. в делетете приложения:
-(void) applicationDidFinishLaunching:(UIApplication *)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RootViewController *controller = [[YourRootViewController] alloc] init];
[window setRootViewController: controller];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[controller release];
[window addSubview:[[self navigationController] view]];
[window makeKeyAndVisible];
}
Ответ 9
Хотя эти ответы являются технически правильными, включая Ushox, я думаю, что одобренный способ - реализовать новый протокол или повторно использовать существующий. Протокол изолирует наблюдателя от наблюдаемого, вроде как помещает почтовый слот между ними. По сути, это то, что делает Габриэль с помощью вызова метода pushViewController; представление "знает", что это правильный протокол, чтобы вежливо попросить вашего навигационного контроллера нажать вид, поскольку viewController соответствует протоколу navigationController. Хотя вы можете создать свой собственный протокол, просто используйте пример Габриэля и повторное использование протокола UINavigationController.
Ответ 10
Я наткнулся на ситуацию, когда у меня есть небольшой компонент, который я хочу повторно использовать, и добавил некоторый код в многократно используемое представление (это действительно не намного больше, чем кнопка, которая открывает PopoverController
).
Пока это хорошо работает в iPad (представлен UIPopoverController
, поэтому не требуется ссылка на UIViewController
), получение того же кода для работы означает внезапное обращение к вашему presentViewController
с вашего UIViewController
. Какое-то непоследовательное право?
Как упоминалось ранее, это не лучший подход к логике в вашем UIView. Но было бесполезно обернуть несколько строк кода, необходимых в отдельном контроллере.
В любом случае, здесь быстрое решение, которое добавляет новое свойство в любой UIView:
extension UIView {
var viewController: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let responder = responder as? UIViewController {
return responder
}
responder = responder?.nextResponder()
}
return nil
}
}
Ответ 11
Я не думаю, что это "плохая" идея, чтобы выяснить, кто является контроллером представления для некоторых случаев. Что может быть плохой идеей, так это сохранить ссылку на этот контроллер, поскольку он может меняться так же, как изменения супервизора. В моем случае у меня есть геттер, который пересекает цепочку ответчиков.
//. Ч
@property (nonatomic, readonly) UIViewController * viewController;
//м.
- (UIViewController *)viewController
{
for (UIResponder * nextResponder = self.nextResponder;
nextResponder;
nextResponder = nextResponder.nextResponder)
{
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController *)nextResponder;
}
// Not found
NSLog(@"%@ doesn't seem to have a viewController". self);
return nil;
}
Ответ 12
Простейший цикл while для нахождения viewController.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
Ответ 13
Это не отвечает на вопрос напрямую, а скорее делает предположение о намерении вопроса.
Если у вас есть представление и в этом представлении вам нужно вызвать метод на другом объекте, например, например, контроллер вида, вы можете использовать NSNotificationCenter.
Сначала создайте свою строку уведомлений в файле заголовка
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
В вашем представлении вызывается postNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Затем в вашем контроллере просмотра вы добавляете наблюдателя. Я делаю это в viewDidLoad
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
Теперь (также в том же контроллере представления) реализуйте свой метод copyString: как показано в элементе @selector выше.
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
Я не говорю, что это правильный способ сделать это, это просто кажется более чистым, чем запуск первой цепи ответчиков. Я использовал этот код для реализации UIMenuController в UITableView и передал событие обратно в UIViewController, чтобы я мог что-то сделать с данными.
Ответ 14
Это, безусловно, плохая идея и неправильный дизайн, но я уверен, что мы все можем наслаждаться быстрым решением лучшего ответа, предложенного @Phil_M:
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.nextResponder() {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder)
}
Если вы намерены делать простые вещи, показывая модальный диалог или данные отслеживания, это не оправдывает использование протокола. Я лично храню эту функцию в объекте утилиты, вы можете использовать ее из всего, что реализует протокол UIResponder, как:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
Все кредиты @Phil_M
Ответ 15
Может, я опаздываю здесь. Но в этой ситуации мне не нравится категория (загрязнение). Мне так нравится:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Ответ 16
Быстрое решение
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
Ответ 17
Swift 4
(более краткими, чем другие ответы)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Мой прецедент, для которого мне нужно получить доступ к представлению сначала UIViewController
: у меня есть объект, который обертывает AVPlayer
/AVPlayerViewController
и я хочу предоставить простой метод show(in view: UIView)
который будет внедрять AVPlayerViewController
в view
. Для этого мне нужно открыть view
UIViewController
.
Ответ 18
Версия Swift 4
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
}
Пример использования
if let parent = self.view.parentViewController{
}
Ответ 19
Ответ Фила:
В строке: id nextResponder = [self nextResponder];
, если self (UIView) не является подвидью представления ViewController, если вы знаете иерархию self (UIView), вы также можете использовать: id nextResponder = [[self superview] nextResponder];
...
Ответ 20
Обновленная версия для быстрого 4: Спасибо за @Phil_M и @paul-slm
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.next {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(responder: nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder: responder)
}
Ответ 21
Мое решение, вероятно, считалось бы фиктивным, но у меня была аналогичная ситуация, как mayoneez (я хотел переключать представления в ответ на жест в EAGLView), и я получил контроллер представления EAGL следующим образом:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
Ответ 22
Я думаю, что есть случай, когда наблюдаемое должно информировать наблюдателя.
Я вижу аналогичную проблему, когда UIView в UIViewController реагирует на ситуацию, и ему нужно сначала сказать родительскому контроллеру представления скрыть кнопку "Назад", а затем по завершении сообщить контроллеру родительского представления, что ему нужно всплывать стек.
Я пробовал это с делегатами без успеха.
Я не понимаю, почему это должна быть плохая идея?
Ответ 23
Другим простым способом является создание собственного класса представления и добавление свойства контроллера представления в класс представления. Обычно контроллер просмотра создает представление, и именно там контроллер может установить себя на свойство. В основном это вместо того, чтобы искать (с небольшим взломом) для контроллера, имея контроллер, чтобы настроить себя на представление - это просто, но имеет смысл, потому что контроллер контролирует представление.
Ответ 24
Если вы не собираетесь загружать это в App Store, вы также можете использовать закрытый метод UIView.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
Ответ 25
Если ваш rootViewController - это UINavigationViewController, который был настроен в классе AppDelegate, тогда
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
Где c требуется класс контроллеров представления.
ПРИМЕНЕНИЕ:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Ответ 26
Нет способа.
То, что я делаю, это передать указатель UIViewController на UIView (или соответствующее наследование). Извините, я не могу помочь с подходом IB к проблеме, потому что я не верю в IB.
Чтобы ответить первому комментатору: иногда вам нужно знать, кто звонил вам, потому что он определяет, что вы можете сделать. Например, с базой данных вы можете иметь доступ только для чтения или читать/писать...