Czy istnieje wbudowany sposób na przejście od a UIViewdo jego UIViewController? Wiem, że możesz dostać się UIViewControllerdo jego UIViewpoprzez, [self view]ale zastanawiałem się, czy istnieje odwrotne odniesienie?
Czy istnieje wbudowany sposób na przejście od a UIViewdo jego UIViewController? Wiem, że możesz dostać się UIViewControllerdo jego UIViewpoprzez, [self view]ale zastanawiałem się, czy istnieje odwrotne odniesienie?
Odpowiedzi:
Ponieważ od dawna jest to odpowiedź zaakceptowana, uważam, że muszę ją poprawić, podając lepszą odpowiedź.
Kilka uwag na temat potrzeby:
Oto przykład jego wdrożenia:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
Widok łączy się z jego delegatem (jak UITableViewna przykład) i nie obchodzi go, czy został zaimplementowany w kontrolerze widoku, czy w jakiejkolwiek innej klasie, której używasz.
Moja pierwotna odpowiedź brzmi: nie polecam tego, ani pozostałych odpowiedzi, w których uzyskano bezpośredni dostęp do kontrolera widoku
Nie ma wbudowanego sposobu, aby to zrobić. Chociaż można obejść przez dodanie IBOutletna UIViewi łączenia ich w konstruktorze Interface, nie jest to zalecane. Widok nie powinien wiedzieć o kontrolerze widoku. Zamiast tego należy postępować zgodnie z sugestią @Phil M. i utworzyć protokół, który będzie używany jako delegat.
Korzystając z przykładu opublikowanego przez Brocka, zmodyfikowałem go tak, aby był to kategoria UIView zamiast UIViewController i sprawiłem, że będzie rekurencyjny, aby każdy podview mógł (mam nadzieję) znaleźć nadrzędny 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 @implementation do pliku .m. Następnie w swoim projekcie #import „UIKitCategories.h” i użyj w kodzie UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIViewjest podklasą UIResponder. UIResponderokreśla metodę -nextResponderz implementacją, która zwraca nil. UIViewzastępuje tę metodę, jak udokumentowano 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 podgląd.
Dodaj to do swojego projektu i możesz rozpocząć.
@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 UIViewma działającą metodę zwracania kontrolera widoku.
UIViewa odbiorcą UIViewController. Odpowiedź Phila M. obejmująca rekurencję to droga.
Sugerowałbym bardziej lekkie podejście do przejścia przez cały łańcuch odpowiedzi bez konieczności dodawania kategorii w UIView:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
Łącząc kilka już udzielonych odpowiedzi, wysyłam je również z moją 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 statycznej z obsługą ARC , którą wysyłam do każdej tworzonej aplikacji. Został przetestowany kilka razy i nie znalazłem żadnych problemów ani wycieków.
PS: Nie musisz używać kategorii takiej jak ja, jeśli dany widok jest twoją podklasą. W tym drugim przypadku po prostu umieść metodę w swojej podklasie i możesz zacząć.
Chociaż technicznie można to rozwiązać zgodnie z zaleceniami pgb , IMHO, jest to jednak wada projektowa. Widok nie powinien być świadomy kontrolera.
I zmodyfikowane de odpowiedź więc mogę przekazać wszelkie widok, przycisk, etykiety itd., Aby uzyskać jej jednostki 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;
}
Edytuj wersję 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)!
}
Edycja 2: - Szybkie rozszerzenie
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)!
}
}
Nie zapominaj, że możesz uzyskać dostęp do kontrolera widoku głównego dla okna, którego widok jest widokiem podrzędnym. Stamtąd, jeśli np. Używasz kontrolera widoku nawigacji i chcesz na niego nałożyć nowy widok:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Najpierw jednak musisz poprawnie ustawić właściwość rootViewController okna. Zrób to, gdy tworzysz kontroler po raz pierwszy, np. W aplikacji delegowanej:
-(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]jest to „główny” (pod) widok okna, rootViewControllernależy ustawić właściwość okna, która natychmiast navigationControllerkontroluje widok „główny”.
Chociaż odpowiedzi te są technicznie poprawne, w tym Ushox, myślę, że zatwierdzonym sposobem jest wdrożenie nowego protokołu lub ponowne użycie istniejącego. Protokół izoluje obserwatora od obserwowanego, podobnie jak umieszczenie między nimi szczeliny na listy. W efekcie to właśnie robi Gabriel poprzez wywołanie metody pushViewController; widok „wie”, że właściwym protokołem jest grzeczne poproszenie twojego NavigationController o wypchnięcie widoku, ponieważ viewController jest zgodny z protokołem navigationController. Chociaż możesz stworzyć własny protokół, wystarczy skorzystać z przykładu Gabriela i ponownie użyć protokołu UINavigationController.
Natknąłem się na sytuację, w której mam mały komponent, którego chcę ponownie użyć, i dodałem trochę kodu w widoku wielokrotnego użytku (tak naprawdę to niewiele więcej niż przycisk otwierający a PopoverController).
Chociaż działa to dobrze na iPadzie ( UIPopoverControllersame prezenty, więc nie wymaga odniesienia do a UIViewController), uzyskanie tego samego kodu do działania oznacza nagle odwołanie się do twojego presentViewControllerz twojego UIViewController. Trochę niespójne, prawda?
Jak wspomniano wcześniej, nie jest to najlepsze podejście do posiadania logiki w UIView. Ale nie było sensu pakować kilku wierszy kodu potrzebnych w osobny kontroler.
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
}
}
Nie wydaje mi się, aby „złym” pomysłem było ustalenie, kto jest kontrolerem widoku w niektórych przypadkach. Nieodpowiednim pomysłem może być zapisanie odwołania do tego kontrolera, ponieważ może on ulec zmianie, podobnie jak zmiany superviews. W moim przypadku mam moduł pobierający, który przechodzi przez ł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;
}
Najprostsza pętla do while, aby znaleźć viewController.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(bardziej zwięzłe niż inne odpowiedzi)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Mój przypadek użycia, dla których trzeba uzyskać dostęp do widoku pierwszy UIViewController: Mam obiekt, który owija się wokół AVPlayer/ AVPlayerViewControlleri chcę, aby zapewnić prostą show(in view: UIView)metodę, która osadzi AVPlayerViewControllersię view. Do tego muszę dostępu view„s UIViewController.
To nie odpowiada bezpośrednio na pytanie, ale raczej zakłada założenie dotyczące pytania.
Jeśli masz widok i w tym widoku musisz wywołać metodę na innym obiekcie, np. Kontrolerze widoku, możesz zamiast tego użyć NSNotificationCenter.
Najpierw utwórz ciąg powiadomień w pliku nagłówkowym
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
Twoim zdaniem zadzwoń do 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 (także w tym samym kontrolerze widoku) zaimplementuj metodę copyString: jak pokazano na @selector powyżej.
- (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 jest to właściwy sposób, aby to zrobić, wydaje się po prostu czystsze niż uruchamianie pierwszego łańcucha odpowiedzi. Użyłem tego kodu, aby zaimplementować UIMenuController na UITableView i przekazać zdarzenie z powrotem do UIViewController, aby móc coś zrobić z danymi.
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 masz zamiar robić proste rzeczy, takie jak wyświetlanie modalnego okna dialogowego lub danych śledzenia, nie uzasadnia to użycia protokołu. Osobiście przechowuję tę funkcję w obiekcie użytkowym, możesz użyć jej ze wszystkiego, co implementuje protokół UIResponder jako:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
Wszystkie podziękowania dla @Phil_M
Może się tu spóźniam. Ale w tej sytuacji nie lubię kategorii (zanieczyszczenia). Kocham w ten sposób:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Szybsze rozwiązanie
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
sequencenieco). Więc jeśli „szybki” oznacza „bardziej funkcjonalny”, to myślę, że jest bardziej szybki.
Zaktualizowana wersja dla swift 4: Dzięki za @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)
}
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{
}
returnTeraz słowo kluczowe nie jest potrzebne 🤓Rozwiązanie 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Rozwiązanie 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
Moje rozwiązanie prawdopodobnie byłoby uważane za fałszywe, ale miałem podobną sytuację jak majonez (chciałem przełączać widoki w odpowiedzi na gest w EAGLView) i dostałem w ten sposób kontroler widoku EAGL:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];.
ClassName *object- gwiazdką.
Myślę, że zdarza się, że obserwowany musi poinformować obserwatora.
Widzę podobny problem, w którym UIView w UIViewController reaguje na sytuację i musi najpierw powiedzieć swojemu kontrolerowi widoku nadrzędnego, aby ukrył przycisk Wstecz, a następnie po zakończeniu powiedzieć nadrzędnemu kontrolerowi widoku, że musi wyskoczyć ze stosu.
Próbowałem tego z delegatami bez powodzenia.
Nie rozumiem, dlaczego to zły pomysł?
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 tam kontroler może ustawić się na właściwość. Zasadniczo jest to zamiast przeszukiwania kontrolera (przy odrobinie włamania), zmuszania kontrolera do ustawienia się na widok - jest to proste, ale ma sens, ponieważ to kontroler „kontroluje” widok.
Jeśli nie zamierzasz przesłać tego do App Store, możesz również użyć prywatnej metody UIView.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
Jeśli Twoim rootViewController jest UINavigationViewController, który został skonfigurowany 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ło obejrzenia klasy kontrolerów.
STOSOWANIE:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Nie ma mowy.
To, co robię, to przekazanie wskaźnika UIViewController do UIView (lub odpowiedniego dziedziczenia). Przepraszam, że nie mogę pomóc w podejściu IB do problemu, ponieważ nie wierzę w IB.
Aby odpowiedzieć na pierwszy komentator: czasami musisz wiedzieć, kto do ciebie zadzwonił, ponieważ określa, co możesz zrobić. Na przykład z bazą danych możesz mieć dostęp tylko do odczytu lub odczyt / zapis ...