Czy istnieje wbudowany sposób na przejście od a UIView
do jego UIViewController
? Wiem, że możesz dostać się UIViewController
do jego UIView
poprzez, [self view]
ale zastanawiałem się, czy istnieje odwrotne odniesienie?
Czy istnieje wbudowany sposób na przejście od a UIView
do jego UIViewController
? Wiem, że możesz dostać się UIViewController
do jego UIView
poprzez, [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 UITableView
na 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 IBOutlet
na UIView
i łą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];
UIView
jest podklasą UIResponder
. UIResponder
określa metodę -nextResponder
z implementacją, która zwraca nil
. UIView
zastę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 UIView
ma działającą metodę zwracania kontrolera widoku.
UIView
a 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, rootViewController
należy ustawić właściwość okna, która natychmiast navigationController
kontroluje 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 ( UIPopoverController
same prezenty, więc nie wymaga odniesienia do a UIViewController
), uzyskanie tego samego kodu do działania oznacza nagle odwołanie się do twojego presentViewController
z 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
/ AVPlayerViewController
i chcę, aby zapewnić prostą show(in view: UIView)
metodę, która osadzi AVPlayerViewController
się 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
}
}
sequence
nieco). 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{
}
return
Teraz 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 ...