Есть ли встроенный способ добраться от него UIViewдо его UIViewController? Я знаю, что вы можете получить от UIViewControllerнего UIViewчерез, [self view]но мне было интересно, если есть обратная ссылка?
Есть ли встроенный способ добраться от него UIViewдо его UIViewController? Я знаю, что вы можете получить от UIViewControllerнего UIViewчерез, [self view]но мне было интересно, если есть обратная ссылка?
Ответы:
Так как это был принятый ответ в течение долгого времени, я чувствую, что должен исправить это с лучшим ответом.
Некоторые комментарии о необходимости:
Пример того, как это реализовать:
@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, и создать протокол, который будет использоваться в качестве делегата.
Используя пример, опубликованный 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];
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есть рабочий метод для возврата контроллера представления.
UIViewи UIViewController. Ответ Фила М с рекурсией - это путь.
Я хотел бы предложить более легкий подход для обхода всей цепочки респондентов без добавления категории в UIView:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
Объединив несколько уже предоставленных ответов, я добавлю и мою реализацию:
@implementation UIView (AppNameAdditions)
- (UIViewController *)appName_viewController {
/// Finds the view's 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's 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, которую я поставляю в каждом приложении, которое создаю. Это было проверено несколько раз, и я не нашел никаких проблем или утечек.
PS: Вам не нужно использовать категорию, как я, если рассматриваемый вид является вашим подклассом. В последнем случае просто поместите метод в свой подкласс, и все готово.
Хотя технически это можно решить, как рекомендует pgb , ИМХО, это недостаток дизайна. Представление не должно знать о контроллере.
Я изменил de answer, чтобы я мог передать любое представление, кнопку, метку и т. Д., Чтобы получить его родитель 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)!
}
}
Не забывайте, что вы можете получить доступ к корневому контроллеру представления для окна, представлением которого является подпредставление. Оттуда, если вы, например, используете контроллер вида навигации и хотите добавить на него новый вид:
[[[[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];
}
[[self navigationController] view]как это «основной» (под) вид окна, необходимо установить rootViewControllerсвойство окна, которое немедленно navigationControllerконтролирует «основной» вид.
Хотя эти ответы являются технически правильными, включая Ushox, я думаю, что одобренный способ состоит в том, чтобы внедрить новый протокол или повторно использовать существующий. Протокол изолирует наблюдателя от наблюдаемого, что-то вроде вставки почтового отсека между ними. По сути, это то, что делает Габриэль через вызов метода pushViewController; представление «знает», что это правильный протокол, чтобы вежливо попросить ваш navigationController выдвинуть представление, поскольку viewController соответствует протоколу navigationController. Хотя вы можете создать свой собственный протокол, просто используйте пример Габриэля и повторно используйте протокол UINavigationController.
Я наткнулся на ситуацию, когда у меня есть небольшой компонент, который я хочу использовать повторно, и добавил некоторый код в само повторно используемое представление (это на самом деле не намного больше, чем кнопка, открывающая a 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
}
}
Я не думаю, что это «плохая» идея выяснить, кто является контроллером представления в некоторых случаях. Что может быть плохой идеей, так это сохранить ссылку на этот контроллер, поскольку он может меняться так же, как меняются суперпредставления. В моем случае у меня есть геттер, который пересекает цепочку респондента.
//.час
@property (nonatomic, readonly) UIViewController * viewController;
//.m
- (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;
}
Простейший цикл do while для поиска viewController.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(более кратко, чем другие ответы)
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«S UIViewController.
Это не дает прямого ответа на вопрос, а скорее делает предположение о цели вопроса.
Если у вас есть представление и в этом представлении вам нужно вызвать метод для другого объекта, например, скажем, контроллера представления, вы можете вместо этого использовать 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, чтобы я мог что-то сделать с данными.
Это, конечно, плохая идея и неправильный дизайн, но я уверен, что нам всем понравится решение Swift с лучшим ответом, предложенным @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
Может быть, я опоздал сюда. Но в этой ситуации мне не нравится категория (загрязнение). Я люблю так:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Более быстрое решение
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
sequenceнемного). Так что, если «swifty» означает «более функциональный», то я думаю, что это более «swifty».
Обновленная версия для swift 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)
}
Версия 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{
}
returnТеперь не нужно ключевое слово nowРешение 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Решение 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
Мое решение, вероятно, было бы сочтено фальшивым, но у меня была похожая ситуация с mayoneez (я хотел переключать представления в ответ на жест в EAGLView), и я получил контроллер представления EAGL следующим образом:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];.
ClassName *object- со звездочкой.
Я думаю, что есть случай, когда наблюдателю необходимо информировать наблюдателя.
Я вижу аналогичную проблему, когда UIView в UIViewController реагирует на ситуацию, и ему необходимо сначала сказать своему родительскому контроллеру представления скрыть кнопку возврата, а затем по завершении сказать родительскому контроллеру представления, что ему нужно выскочить из стека.
Я пытался это с делегатами без успеха.
Я не понимаю, почему это должно быть плохой идеей?
Другой простой способ - иметь свой собственный класс представления и добавить свойство контроллера представления в класс представления. Обычно контроллер представления создает представление, и именно здесь контроллер может установить для себя свойство. По сути, это вместо того, чтобы искать (с небольшим взломом) контроллер, иметь контроллер, чтобы настроить себя на представление - это просто, но имеет смысл, потому что именно контроллер «контролирует» представление.
Если вы не собираетесь загружать это в App Store, вы также можете использовать приватный метод UIView.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
Если ваш 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]];
Выхода нет.
Что я делаю, это передаю указатель UIViewController на UIView (или соответствующее наследование). Извините, я не могу помочь с подходом IB к проблеме, потому что я не верю в IB.
Чтобы ответить первому комментатору: иногда вам нужно знать, кто вам звонил, потому что он определяет, что вы можете сделать. Например, с базой данных вы можете иметь только права на чтение или чтение / запись ...