Есть ли встроенный способ добраться от него 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.
Чтобы ответить первому комментатору: иногда вам нужно знать, кто вам звонил, потому что он определяет, что вы можете сделать. Например, с базой данных вы можете иметь только права на чтение или чтение / запись ...