Dostać się do UIViewController z UIView?

Czy istnieje wbudowany sposób na przejście z UIView do jego UIViewController? Wiem, że można dostać się z UIViewController do UIView przez [self view], ale zastanawiałem się, czy jest odwrotne odniesienie?

Author: vikingosegundo, 2009-08-27

24 answers

Ponieważ jest to akceptowana odpowiedź przez długi czas, czuję, że muszę ją poprawić lepszą odpowiedzią.

Kilka uwag na temat potrzeby:

  • twój widok nie powinien mieć bezpośredniego dostępu do kontrolera widoku.
  • widok powinien być niezależny od kontrolera widoku i działać w różnych kontekstach.
  • Jeśli potrzebujesz widoku do interfejsu w sposób z kontrolerem widoku, zalecany sposób i to, co Apple robi w kakao, to użyj wzorca delegowania.

Przykład jak to zaimplementować:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

Widok łączy się ze swoim delegatem (tak jak robi to na przykład UITableView) i nie obchodzi go, czy jest zaimplementowany w kontrolerze widoku, czy w innej klasie, której używasz.

Moja oryginalna odpowiedź brzmi następująco: nie polecam tego, ani reszty odpowiedzi, w których uzyskuje się bezpośredni dostęp do kontrolera widoku

Nie ma na to wbudowanego sposobu. Podczas gdy ty można obejść go dodając {[2] } na UIView i łącząc je w Kreatorze interfejsów, nie jest to zalecane. Widok nie powinien wiedzieć o kontrolerze widoku. Zamiast tego powinieneś zrobić tak, jak sugeruje @Phil M i utworzyć protokół, który będzie używany jako delegat.
 41
Author: pgb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-18 17:32:28

Używając przykładu opublikowanego przez Brocka, zmodyfikowałem go tak, aby był kategorią UIView zamiast UIViewController i uczynił go rekurencyjnym, aby każdy subview mógł (miejmy nadzieję) znaleźć macierzysty 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

Aby użyć tego kodu, dodaj go do nowego pliku klasy (nazwałem mój" UIKitCategories") i usuń dane klasy... skopiuj interfejs @do nagłówka, a implementację @do .plik M. Następnie w projekcie# import " UIKitCategories.h " i używać w ramach UIView kod:

// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
 198
Author: Phil M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-26 07:19:33

UIView jest podklasą UIResponder. UIResponder określa metodę {[4] } z implementacją, która zwraca nil. UIView nadpisuje tę metodę, jak opisano w UIResponder (z jakiegoś powodu zamiast w UIView) w następujący sposób: jeśli Widok ma kontroler widoku, jest zwracany przez -nextResponder. Jeśli nie ma kontrolera widoku, metoda zwróci superview.

Dodaj to do swojego projektu i jesteś gotowy do pracy.

@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

Teraz UIView ma roboczą metodę zwracania widoku kontroler.

 111
Author: Brock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-01 16:27:14

Sugerowałbym bardziej lekkie podejście do przechodzenia całego łańcucha odpowiedzi bez konieczności dodawania kategorii na UIView:

@implementation MyUIViewSubclass

- (UIViewController *)viewController {
    UIResponder *responder = self;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

@end
 30
Author: de.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-26 22:56:51

Łącząc kilka już udzielonych odpowiedzi, wysyłam na nią również swoją implementację:

@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

Kategoria jest częścią mojej biblioteki statycznejz obsługą ARC , którą wysyłam w każdej tworzonej aplikacji. Był testowany kilka razy i nie znalazłem żadnych problemów ani wycieków.

P. S.: nie musisz używać kategorii jak ja, jeśli dany widok jest twoją podklasą. W tym drugim przypadku po prostu umieść metodę w swojej podklasie i możesz iść.

 22
Author: Randy Marsh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-28 14:33:26

Mimo, że technicznie można to rozwiązać jako pgb zaleca, IMHO, jest to wada projektowa. Widok nie powinien być świadomy kontrolera.

 12
Author: Ushox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-27 15:56:18

Zmodyfikowałem de odpowiedź, więc mogę przekazać dowolny widok, przycisk, Etykietę itp. aby uzyskać to rodzic UIViewController. Oto Mój kod.

+(UIViewController *)viewController:(id)view {
    UIResponder *responder = view;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

Edit Swift 3 Version

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)!
    }

Edit 2: - Swift Extention

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)!
    }
}
 10
Author: Varun Naharia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-12 13:01:05

Nie zapomnij, że możesz uzyskać dostęp do kontrolera widoku głównego dla okna, którego widok jest podglądem podrzędnym. Stamtąd, jeśli np. używasz kontrolera widoku nawigacyjnego i chcesz wypchnąć na niego nowy Widok:

    [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];

Najpierw musisz jednak poprawnie skonfigurować właściwość rootviewcontroller okna. Zrób to, gdy po raz pierwszy utworzysz kontroler, np. w delegacie aplikacji:

-(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];
}
 7
Author: Gabriel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-01 03:30:31

Chociaż te odpowiedzi są poprawne technicznie, w tym Ushox, myślę, że zatwierdzony Sposób jest wdrożenie nowego protokołu lub ponowne wykorzystanie istniejącego. Protokół izoluje obserwatora od obserwowanego, coś w rodzaju umieszczania gniazda poczty między nimi. W efekcie Gabriel robi to za pomocą wywołania metody pushViewController; widok "wie", że właściwym protokołem jest grzeczne poproszenie navigationController o wypchnięcie widoku, ponieważ viewController jest zgodny z protokół navigationController. Chociaż możesz utworzyć własny protokół, wystarczy użyć przykładu Gabriela i ponownie użyć protokołu UINavigationController.

 6
Author: PapaSmurf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-05 21:00:14

Nie sądzę, aby to był" zły " pomysł, aby dowiedzieć się, kto jest kontrolerem widoku w niektórych przypadkach. Co może być złym pomysłem jest, aby zapisać odniesienie do tego kontrolera, ponieważ może się zmienić tak jak superviews zmienić. W moim przypadku mam gettera, który przemierza łańcuch odpowiedzi.

//.h

@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;
}
 5
Author: Rivera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-24 00:10:10

Najprostsza pętla do while do znalezienia kontrolera viewcontrollera.

-(UIViewController*)viewController
{
    UIResponder *nextResponder =  self;

    do
    {
        nextResponder = [nextResponder nextResponder];

        if ([nextResponder isKindOfClass:[UIViewController class]])
            return (UIViewController*)nextResponder;

    } while (nextResponder != nil);

    return nil;
}
 5
Author: Mohd Iftekhar Qurashi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-07 06:31:34

Natknąłem się na sytuację, w której mam mały komponent, który chcę ponownie wykorzystać, i dodałem trochę kodu w widoku wielokrotnego użytku (to naprawdę niewiele więcej niż przycisk otwierający PopoverController).

Chociaż działa to dobrze na iPadzie (UIPopoverController prezentuje się sam, dlatego nie wymaga odniesienia do UIViewController), uzyskanie tego samego kodu do pracy oznacza nagle odwołanie się do twojego presentViewController z twojego UIViewController. Trochę niekonsekwentne, prawda?

Jak wspomniano wcześniej, nie jest to najlepsze podejście, aby mieć logikę w swoim UIView. Ale zawieranie kilku linii kodu w osobnym kontrolerze wydawało się naprawdę bezużyteczne.

Tak czy inaczej, oto szybkie rozwiązanie, które dodaje nową właściwość do dowolnego 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
    }
}
 4
Author: Kevin R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-05 12:13:06

To nie odpowiada bezpośrednio na pytanie, ale raczej zakłada intencję pytania.

Jeśli masz widok i w tym widoku musisz wywołać metodę na innym obiekcie, jak np. kontroler widoku, możesz zamiast tego użyć NSNotificationCenter.

Najpierw Utwórz łańcuch powiadomień w pliku nagłówkowym

#define SLCopyStringNotification @"ShaoloCopyStringNotification"

W Twoim odczuciu call postNotificationName:

- (IBAction) copyString:(id)sender
{
    [[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}

Następnie w kontrolerze widoku dodajesz obserwatora. Robię to w viewDidLoad

- (void)viewDidLoad
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(copyString:)
                                                 name:SLCopyStringNotification
                                               object:nil];
}

Teraz (również w tym samym kontrolerze widoku) zaimplementuj swoją metodę copyString: jak pokazano na powyższym selektorze@.

- (IBAction) copyString:(id)sender
{
    CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
    [gpBoard setString:result.stringResult];
}
Nie twierdzę, że to dobry sposób na to, ale wydaje się czystszy niż bieganie w łańcuchu pierwszej pomocy. Użyłem tego kodu, aby zaimplementować UIMenuController na UITableView i przekazać Zdarzenie z powrotem do UIViewController, więc mogę zrobić coś z danymi.
 3
Author: Shaolo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-15 05:38:45

To z pewnością zły pomysł i zły projekt, ale jestem pewien, że wszyscy możemy cieszyć się szybkim rozwiązaniem najlepszej odpowiedzi zaproponowanej przez @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)
}

Jeśli twoim zamiarem jest robienie prostych rzeczy, takich jak Pokazywanie modalnego okna dialogowego lub śledzenie danych, nie uzasadnia to użycia protokołu. Osobiście przechowuję tę funkcję w obiekcie użyteczności, możesz jej używać z wszystkiego, co implementuje protokół UIResponder jako:

if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}

All credit to @ Phil_M

 3
Author: Paul Slm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-26 07:27:15

Do odpowiedzi Phila:

W linii: id nextResponder = [self nextResponder]; jeśli self(UIView) nie jest podglądem widoku Viewcontrollera, jeśli znasz hierarchię self (UIView) możesz użyć również: id nextResponder = [[self superview] nextResponder];...

 1
Author: sag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-04 14:35:30

Może się spóźniłem. Ale w tej sytuacji nie lubię kategorii (zanieczyszczenia). Kocham ten sposób:

#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
 1
Author: lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-03 09:07:33

Moje rozwiązanie prawdopodobnie byłoby uznane za fałszywe, ale miałem podobną sytuację jak mayoneez( chciałem zmienić widoki w odpowiedzi na gest w EAGLView), i dostałem kontroler widoku EAGL w ten sposób:

EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
 0
Author: gulchrider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-07 19:43:30

Myślę, że jest przypadek, kiedy obserwowany musi poinformować obserwatora.

Widzę podobny problem, gdy UIView w UIViewController reaguje na sytuację i musi najpierw powiedzieć swojemu nadrzędnemu kontrolerowi widoku, aby ukrył przycisk Wstecz, a następnie po zakończeniu powiedzieć nadrzędnemu kontrolerowi widoku, że musi sam wyskoczyć ze stosu.

Próbowałem tego z delegatami bez powodzenia.

Nie rozumiem dlaczego to ma być zły pomysł?

 0
Author: user7865437,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-08 08:16:28

Innym łatwym sposobem jest posiadanie własnej klasy widoku i dodanie właściwości kontrolera widoku do klasy widoku. Zwykle kontroler widoku tworzy widok i wtedy kontroler może ustawić się na właściwość. Zasadniczo zamiast szukać kontrolera (z odrobiną hakowania), kontroler ustawia się na widok - jest to proste, ale ma sens, ponieważ to kontroler "kontroluje" widok.

 0
Author: jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-25 01:08:16

Zaktualizowana wersja dla swift 4: dzięki dla @ Phil_M i @ 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)
}
 0
Author: Dicle Yilmaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-04 20:37:25

Wersja 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
}

}

Przykład użycia

 if let parent = self.view.parentViewController{

    }
 0
Author: levin varghese,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-22 05:12:56

Swiftier solution

extension UIView {
    var parentViewController: UIViewController? {
        for responder in sequence(first: self, next: { $0.next }) {
            if let viewController = responder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}
 0
Author: Igor Palaguta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-09 11:14:24

Jeśli twoim rootviewcontrollerem jest UINavigationViewController, który został ustawiony w klasie AppDelegate, to

    + (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];

for (UIViewController *v in arrVc)
{
    if ([v isKindOfClass:c])
    {
        return v;
    }
}

return nil;}

Gdzie C wymaga klasy kontrolerów widoku.

Użycie:

     RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
 -1
Author: Ansari Awais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-06 12:30:33

Nie ma mowy.

To, co robię, to przekazywanie wskaźnika UIViewController do UIView (lub odpowiedniego dziedziczenia). Przykro mi, że nie mogę pomóc w podejściu IB do problemu, ponieważ nie wierzę w IB.

Aby odpowiedzieć pierwszemu komentatorowi: czasami musisz wiedzieć, kto do ciebie zadzwonił, ponieważ to decyduje, co możesz zrobić. Na przykład w bazie danych możesz mieć tylko dostęp do odczytu lub odczyt/zapis ...

 -5
Author: John Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-27 11:32:49