discissModalViewController I przekaż dane z powrotem

84

Mam dwa kontrolery widoku, firstViewController i secondViewController . Używam tego kodu, aby przełączyć się do mojego secondViewController (przekazuję również ciąg do niego):

secondViewController *second = [[secondViewController alloc] initWithNibName:nil bundle:nil];

second.myString = @"This text is passed from firstViewController!";

second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;

[self presentModalViewController:second animated:YES];

[second release];

Następnie używam tego kodu w secondViewController, aby przełączyć się z powrotem do firstViewController:

[self dismissModalViewControllerAnimated:YES];

Wszystko to działa dobrze. Moje pytanie brzmi, jak przekazać dane do firstViewController? Chciałbym przekazać inny ciąg do firstViewController z secondViewController.

Andrew Davis
źródło

Odpowiedzi:

142

Musisz użyć protokołów delegata ... Oto jak to zrobić:

Zadeklaruj protokół w pliku nagłówkowym secondViewControllera. To powinno wyglądać tak:

#import <UIKit/UIKit.h>

@protocol SecondDelegate <NSObject>
-(void)secondViewControllerDismissed:(NSString *)stringForFirst
@end


@interface SecondViewController : UIViewController
{
    id myDelegate;  
}

@property (nonatomic, assign) id<SecondDelegate>    myDelegate;

Nie zapomnij zsyntetyzować myDelegate w pliku implementacji (SecondViewController.m):

@synthesize myDelegate;

W pliku nagłówkowym FirstViewController zasubskrybuj protokół SecondDelegate, wykonując następujące czynności:

#import "SecondViewController.h"

@interface FirstViewController:UIViewController <SecondDelegate>

Teraz podczas tworzenia instancji SecondViewController w FirstViewController należy wykonać następujące czynności:

// If you're using a view controller built with Interface Builder.
SecondViewController *second = [[SecondViewController alloc] initWithNibName:"SecondViewController" bundle:[NSBundle mainBundle]];
// If you're using a view controller built programmatically.
SecondViewController *second = [SecondViewController new]; // Convenience initializer that uses alloc] init]
second.myString = @"This text is passed from firstViewController!";
second.myDelegate = self;
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];

Na koniec w pliku implementacji dla pierwszego kontrolera widoku (FirstViewController.m) zaimplementuj metodę SecondDelegate dla secondViewControllerDismissed:

- (void)secondViewControllerDismissed:(NSString *)stringForFirst
{
    NSString *thisIsTheDesiredString = stringForFirst; //And there you have it.....
}

Teraz, gdy zamierzasz odrzucić drugi kontroler widoku, chcesz wywołać metodę zaimplementowaną w pierwszym kontrolerze widoku. Ta część jest prosta. Wszystko, co musisz zrobić, to w drugim kontrolerze widoku dodać kod przed kodem odrzucenia:

if([self.myDelegate respondsToSelector:@selector(secondViewControllerDismissed:)])
{
    [self.myDelegate secondViewControllerDismissed:@"THIS IS THE STRING TO SEND!!!"];
}
[self dismissModalViewControllerAnimated:YES];

Protokoły delegatów są NIEZWYKLE, NIEZWYKLE, BARDZO przydatne. Dobrze by było, gdybyś się z nimi zapoznał :)

NSNotifications to inny sposób, aby to zrobić, ale jako najlepsze rozwiązanie wolę go używać, gdy chcę komunikować się między wieloma kontrolerami widoku lub obiektami. Oto odpowiedź, którą opublikowałem wcześniej, jeśli jesteś ciekawy korzystania z NSNotifications: Uruchamianie zdarzeń na wielu kontrolerach widoku z wątku w appdelegate

EDYTOWAĆ:

Jeśli chcesz przekazać wiele argumentów, kod przed odrzuceniem wygląda następująco:

if([self.myDelegate respondsToSelector:@selector(secondViewControllerDismissed:argument2:argument3:)])
{
    [self.myDelegate secondViewControllerDismissed:@"THIS IS THE STRING TO SEND!!!" argument2:someObject argument3:anotherObject];
}
[self dismissModalViewControllerAnimated:YES];

Oznacza to, że implementacja metody SecondDelegate wewnątrz elementu firstViewController będzie teraz wyglądać następująco:

- (void) secondViewControllerDismissed:(NSString*)stringForFirst argument2:(NSObject*)inObject1 argument3:(NSObject*)inObject2
{
    NSString thisIsTheDesiredString = stringForFirst;
    NSObject desiredObject1 = inObject1;
    //....and so on
}
Sid
źródło
Według Apple View Controller Programming Guide for iOS secondViewController powinien zostać odrzucony w kontrolerze widoku prezentacji, a nie w prezentowanym.
Michael,
Wygląda na to, że nie ustawiłeś delegata UITableView. Czy możesz zadać to pytanie jako pytanie z kodem, który masz i zakreślić z powrotem? Mogę ci pomóc.
Sid,
1
@Michael Dokumentacja mówi, że wywołanie disciss na siebie przekazuje wywołanie do kontrolera widoku prezentacji. Ponadto wywołanie self jest czystsze, ponieważ w ten sposób nie musisz się martwić o przełączanie między PresentViewController i parentViewController w zależności od docelowej wersji systemu iOS (5 lub starszej).
Sid
1
@Resty Zgadzam się; klocki są niezwykle przydatne. W pewnym momencie rozważałem zmianę tej odpowiedzi na bloki wsparcia. Jednak w tym przypadku na razie pozostawiłem widoczną odpowiedź delegata, ponieważ daje nam ona trochę więcej swobody w manipulowaniu obiektami, które mogą zostać przekazane do modalu. Jestem po prostu leniwy i wkrótce zaktualizuję tę odpowiedź, aby używać bloków :)
Sid
1
@sid dzięki, stary, to działa dla mnie, ale musisz trochę zmodyfikować. jak wiele rzeczy się zmieniło. edytuj go
ChenSmile
40

Mógłbym być tutaj nie na miejscu, ale zaczynam o wiele bardziej preferować składnię blokową niż bardzo rozwlekłe podejście delegata / protokołu. Jeśli utworzysz vc2 z vc1, masz właściwość na vc2, którą możesz ustawić z vc1, która jest blokiem!

@property (nonatomic, copy) void (^somethingHappenedInVC2)(NSString *response);

Następnie, gdy w vc2 wydarzy się coś, o czym chcesz powiedzieć vc1, po prostu wykonaj blok zdefiniowany w vc1!

self.somethingHappenedInVC2(@"Hello!");

Pozwala to na wysyłanie danych z vc2 z powrotem do vc1. Jak magia. IMO, jest to o wiele łatwiejsze / czystsze niż protokoły. Bloki są niesamowite i należy je ogarnąć jak najwięcej.

EDYCJA - ulepszony przykład

Powiedzmy, że mamy mainVC, na którym chcemy tymczasowo zaprezentować modalVC, aby uzyskać dane wejściowe od użytkownika. Aby zaprezentować ten modalVC z mainVC, musimy go zaalokować / zainicjować wewnątrz mainVC. Całkiem podstawowe rzeczy. Cóż, kiedy tworzymy ten obiekt modalVC, możemy również ustawić na nim właściwość bloku, która pozwala nam łatwo komunikować się między oboma obiektami vc. Weźmy więc powyższy przykład i umieść następującą właściwość w pliku .h modalVC:

 @property (nonatomic, copy) void (^somethingHappenedInModalVC)(NSString *response);  

Następnie w naszym mainVC, po przydzieleniu / zainicjowaniu nowego obiektu modalVC, ustawiasz właściwość bloku modalVC w następujący sposób:

ModalVC *modalVC = [[ModalVC alloc] init];
modalVC.somethingHappenedInModalVC = ^(NSString *response) {
     NSLog(@"Something was selected in the modalVC, and this is what it was:%@", response);
}

Więc po prostu ustawiamy właściwość bloku i definiujemy, co się stanie, gdy ten blok zostanie wykonany.

Wreszcie, w naszym modalVC możemy mieć tableViewController, który jest obsługiwany przez tablicę ciągów dataSource. Po dokonaniu wyboru wiersza możemy zrobić coś takiego:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
      NSString *selectedString = self.dataSource[indexPath.row];
      self.somethingHappenedInModalVC(selectedString);
 }

Oczywiście za każdym razem, gdy wybieramy wiersz w modalVC, otrzymamy wynik konsoli z naszej linii NSLog z powrotem w mainVC. Mam nadzieję, że to pomoże!

Lizza
źródło
1
Czy powinno to nadal działać podczas korzystania ze scenorysów? W tej chwili to nie działa dla mnie. Po prostu kończy pracę z błędem lldb. Główna różnica. Widzę, że alokacja vc jest teraz utworzonym przepływem scenorysu. EDYCJA A ja prezentowałem przed utworzeniem bloku. Naprawiony.
malaki1974
2
Zgadzam się z tobą :) Opublikowałem swoją odpowiedź jakiś czas temu. Teraz przełączam się między używaniem bloków / protokołów w zależności od zastosowania. Ponieważ ten wątek jest nadal dość aktywny do dziś, powinienem w pewnym momencie zmienić moją odpowiedź, aby zawierała bloki.
Sid
1
Tę odpowiedź należy zaakceptować, ponieważ jest to najbardziej intuicyjne rozwiązanie.
Sukitha Udugamasooriya
2
Z dwóch odpowiednich odpowiedzi ta jest najlepsza!
kygcoleman,
1
To zdecydowanie moja preferowana metoda. Dokonuje się tego szybko za pomocą zamknięć. Znacznie lepiej niż delegaci i powiadomienia, ponieważ nie musisz określać protokołów ani tych „brzydkich” stałych powiadomień. Jeśli nadasz nazwę zmiennej w przedstawionym vc, która dobrze trzyma zamknięcie, może to być bardzo intuicyjny kod np. Vc.didCancel, vc.didFinish ... Możesz ustawić te wartości w pliku PrzygotowanieForSegue f vc, który go przedstawia (jeśli używasz segues).
HixField
4

hmm, poszukaj centrum powiadomień i przekaż informacje w powiadomieniu. oto jabłka, które się tym zajmują - podchodzę do tego osobiście, chyba że ktoś ma inne sugestie

theiOSDude
źródło
Łącze w rzeczywistości komplikuje to zbytnio, wystarczy obserwator (pierwszy Kontroler widoku) i wysłać powiadomienie z drugiego. Możesz przypisać selektory do powiadomienia i otrzymać informacje odesłane za pośrednictwem powiadomienia.
theiOSDude
2

Zdefiniuj protokół delegata w drugim kontrolerze widoku i ustaw pierwszy z nich jako delegata drugiego.

cschwarz
źródło