Ta linia:
[self dismissViewControllerAnimated:YES completion:nil];
nie wysyła wiadomości do siebie, w rzeczywistości wysyła wiadomość do prezentującego VC, prosząc go o odrzucenie. Przedstawiając VC, tworzysz relację między prezentującym VC a prezentowanym. Dlatego nie powinieneś niszczyć prezentującego VC podczas prezentacji (przedstawiony VC nie może odesłać tej wiadomości odrzucającej…). Ponieważ tak naprawdę nie bierzesz tego pod uwagę, pozostawiasz aplikację w stanie zdezorientowania. Zobacz moją odpowiedź Dismissing a Presented View Controller,
w którym polecam tę metodę, jest jaśniej napisana:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
W Twoim przypadku musisz upewnić się, że cała kontrola została wykonana mainVC
. Należy użyć delegata, aby wysłać poprawny komunikat z powrotem do MainViewController z ViewController1, aby mainVC mógł odrzucić VC1, a następnie przedstawić VC2.
W VC2 VC1 dodaj protokół w swoim pliku .h powyżej @interface:
@protocol ViewController1Protocol <NSObject>
- (void)dismissAndPresentVC2;
@end
i niżej w tym samym pliku w sekcji @interface zadeklaruj właściwość do przechowywania wskaźnika delegata:
@property (nonatomic,weak) id <ViewController1Protocol> delegate;
W pliku .m VC1 metoda przycisku odrzucenia powinna wywołać metodę delegata
- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
[self.delegate dissmissAndPresentVC2]
}
Teraz w mainVC ustaw go jako delegata VC1 podczas tworzenia VC1:
- (IBAction)present1:(id)sender {
ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
vc.delegate = self;
[self present:vc];
}
i zaimplementuj metodę delegata:
- (void)dismissAndPresent2 {
[self dismissViewControllerAnimated:NO completion:^{
[self present2:nil];
}];
}
present2:
może być tą samą metodą, co metoda VC2Pressed:
przycisku IBAction. Zauważ, że jest on wywoływany z bloku uzupełniania, aby zapewnić, że VC2 nie zostanie zaprezentowany, dopóki VC1 nie zostanie całkowicie odrzucony.
Przechodzisz teraz z VC1-> VCMain-> VC2, więc prawdopodobnie będziesz chciał animować tylko jedno przejście.
aktualizacja
W swoich komentarzach wyrażasz zdziwienie złożonością wymaganą do osiągnięcia pozornie prostej rzeczy. Zapewniam cię, że ten wzorzec delegowania jest tak centralny dla większości Objective-C i Cocoa, a ten przykład jest najprostszym, jaki możesz uzyskać, że naprawdę powinieneś dołożyć starań, aby przyzwyczaić się do niego.
W Apple View Controller Programming Guide mają do powiedzenia :
Odrzucanie kontrolera widoku prezentacji
Kiedy przychodzi czas na odrzucenie kontrolera widoku prezentowanego, preferowanym podejściem jest pozwolenie kontrolerowi widoku prezentacji na odrzucenie go. Innymi słowy, gdy tylko jest to możliwe, ten sam kontroler widoku, który przedstawił kontrolera widoku, powinien również wziąć odpowiedzialność za odwołanie go. Chociaż istnieje kilka technik powiadamiania kontrolera widoku prezentującego, że jego kontroler widoku prezentowanego powinien zostać odrzucony, preferowaną techniką jest delegowanie. Aby uzyskać więcej informacji, zobacz „Używanie delegowania do komunikacji z innymi kontrolerami”.
Jeśli naprawdę zastanowisz się, co chcesz osiągnąć i jak sobie z tym poradzisz, zdasz sobie sprawę, że wysyłanie wiadomości do MainViewController w celu wykonania całej pracy jest jedynym logicznym wyjściem, biorąc pod uwagę, że nie chcesz używać NavigationController. Jeśli zrobić użyć NavController w efekcie jesteś „delegowanie”, nawet jeśli nie wprost, do navController zrobić wszystkie prace. Musi istnieć jakiś obiekt, który centralnie śledzi to, co dzieje się z Twoją nawigacją VC, i potrzebujesz jakiejś metody komunikacji z nim, cokolwiek robisz.
W praktyce rada Apple jest trochę ekstremalna ... w normalnych przypadkach nie musisz wyznaczać dedykowanego delegata i metody, na której możesz polegać [self presentingViewController] dismissViewControllerAnimated:
- to w przypadkach takich jak twój chcesz, aby twoje zwolnienie miało inny wpływ na pilota przedmioty, na które musisz uważać.
Oto coś, co możesz sobie wyobrazić, aby pracować bez kłopotów ze strony delegata ...
- (IBAction)dismiss:(id)sender {
[[self presentingViewController] dismissViewControllerAnimated:YES
completion:^{
[self.presentingViewController performSelector:@selector(presentVC2:)
withObject:nil];
}];
}
Po poproszeniu kontrolera Presentation o zwolnienie nas, mamy blok uzupełniania, który wywołuje metodę w PresentViewController w celu wywołania VC2. Nie potrzeba żadnego delegata. (Dużą zaletą bloków jest to, że zmniejszają potrzebę delegowania w takich okolicznościach). Jednak w tym przypadku kilka rzeczy staje na przeszkodzie ...
- w VC1 nie wiesz, że mainVC implementuje metodę
present2
- możesz skończyć z trudnymi do debugowania błędami lub awariami. Delegaci pomogą ci tego uniknąć.
- po odrzuceniu VC1 tak naprawdę nie ma go w pobliżu, aby wykonać blok uzupełniania ... czy tak jest? Czy self.presentingViewController już coś znaczy? Nie wiesz (ja też nie) ... z delegatem nie masz tej niepewności.
- Kiedy próbuję uruchomić tę metodę, po prostu zawiesza się bez ostrzeżenia i błędów.
Więc proszę ... poświęć trochę czasu na naukę delegacji!
aktualizacja2
W swoim komentarzu udało Ci się to zadziałać, używając tego w programie obsługi przycisku VC2:
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
Jest to z pewnością znacznie prostsze, ale pozostawia wiele problemów.
Mocne sprzężenie
Twórz ze sobą sztywne okablowanie struktury viewController. Na przykład, jeśli chcesz wstawić nowy kontroler viewController przed mainVC, Twoje wymagane zachowanie ulegnie awarii (przejdziesz do poprzedniego). W VC1 musiałeś również #importować VC2. Dlatego masz sporo współzależności, co łamie cele OOP / MVC.
Używając delegatów, ani VC1, ani VC2 nie muszą nic wiedzieć o mainVC ani o jego poprzednikach, więc zachowujemy wszystko luźno powiązane i modułowe.
Pamięć
VC1 nie zniknęła, nadal masz do niej dwie wskazówki:
presentedViewController
właściwość mainVC
presentingViewController
Własność VC2
Możesz to przetestować, logując się, a także robiąc to z VC2
[self dismissViewControllerAnimated:YES completion:nil];
Nadal działa, nadal przenosi Cię z powrotem do VC1.
Wydaje mi się, że to wyciek pamięci.
Wskazówka do tego znajduje się w ostrzeżeniu, które otrzymujesz:
[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
Logika się psuje, ponieważ próbujesz odrzucić prezentujący VC, którego VC2 jest prezentowanym VC. Druga wiadomość tak naprawdę nie jest wykonywana - cóż, być może coś się dzieje, ale nadal pozostają dwa wskaźniki do obiektu, o którym myślałeś, że się pozbyłeś. ( edytuj - sprawdziłem to i nie jest tak źle, oba obiekty znikają, gdy wrócisz do mainVC )
To dość rozwlekły sposób powiedzenia - proszę, skorzystajcie z delegatów. Jeśli to pomaga, przedstawiłem tutaj kolejny krótki opis wzoru:
Czy przekazanie kontrolera u konstruktora jest zawsze złą praktyką?
aktualizacja 3
Jeśli naprawdę chcesz uniknąć delegatów, może to być najlepsze wyjście:
W VC1:
[self presentViewController:VC2
animated:YES
completion:nil];
Ale nie odrzucaj niczego ... jak ustaliliśmy, tak naprawdę to się nie zdarza.
W VC2:
[self.presentingViewController.presentingViewController
dismissViewControllerAnimated:YES
completion:nil];
Ponieważ (wiemy) nie odrzuciliśmy VC1, możemy wrócić przez VC1 do MainVC. MainVC odrzuca VC1. Ponieważ VC1 zniknął, przedstawiono VC2 razem z nim, więc jesteś z powrotem w MainVC w stanie czystym.
Nadal jest silnie sprzężony, ponieważ VC1 musi wiedzieć o VC2, a VC2 musi wiedzieć, że został osiągnięty przez MainVC-> VC1, ale jest to najlepsze, co uzyskasz bez odrobiny wyraźnej delegacji.
Przykład w języku Swift , ilustrujący powyższe wyjaśnienie odlewni i dokumentację Apple:
ViewController.swift
import UIKit protocol ViewControllerProtocol { func dismissViewController1AndPresentViewController2() } class ViewController: UIViewController, ViewControllerProtocol { @IBAction func goToViewController1BtnPressed(sender: UIButton) { let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1 vc1.delegate = self vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal self.presentViewController(vc1, animated: true, completion: nil) } func dismissViewController1AndPresentViewController2() { self.dismissViewControllerAnimated(false, completion: { () -> Void in let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2 self.presentViewController(vc2, animated: true, completion: nil) }) } }
ViewController1.swift
import UIKit class ViewController1: UIViewController { var delegate: protocol<ViewControllerProtocol>! @IBAction func goToViewController2(sender: UIButton) { self.delegate.dismissViewController1AndPresentViewController2() } }
ViewController2.swift
import UIKit class ViewController2: UIViewController { }
ViewController.swift
import UIKit protocol ViewControllerProtocol { func popViewController1AndPushViewController2() } class ViewController: UIViewController, ViewControllerProtocol { @IBAction func goToViewController1BtnPressed(sender: UIButton) { let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1 vc1.delegate = self self.navigationController?.pushViewController(vc1, animated: true) } func popViewController1AndPushViewController2() { self.navigationController?.popViewControllerAnimated(false) let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2 self.navigationController?.pushViewController(vc2, animated: true) } }
ViewController1.swift
import UIKit class ViewController1: UIViewController { var delegate: protocol<ViewControllerProtocol>! @IBAction func goToViewController2(sender: UIButton) { self.delegate.popViewController1AndPushViewController2() } }
ViewController2.swift
import UIKit class ViewController2: UIViewController { }
źródło
ViewController
klasa to mainVC, prawda?Myślę, że źle zrozumiałeś niektóre podstawowe koncepcje dotyczące kontrolerów widoku modalnego iOS. Kiedy odrzucasz VC1, wszystkie prezentowane kontrolery widoku przez VC1 są również odrzucane. Apple przeznaczone dla kontrolerów widoku modalnego do przepływu w sposób skumulowany - w twoim przypadku VC2 jest prezentowane przez VC1. Odrzucasz VC1, gdy tylko prezentujesz VC2 z VC1, więc jest to totalny bałagan. Aby osiągnąć to, co chcesz, buttonPressedFromVC1 powinien mieć mainVC obecny VC2 natychmiast po odrzuceniu VC1. Myślę, że można to osiągnąć bez delegatów. Coś w stylu:
UIViewController presentingVC = [self presentingViewController]; [self dismissViewControllerAnimated:YES completion: ^{ [presentingVC presentViewController:vc2 animated:YES completion:nil]; }];
Zauważ, że self.presentingViewController jest przechowywany w jakiejś innej zmiennej, ponieważ po odrzuceniu vc1 nie należy tworzyć żadnych odniesień do niego.
źródło
[self dismiss...]
dzieje się po[self present...]
zakończeniu? To nie jest coś asynchronicznegoRadu Simionescu - świetna robota! a poniżej Twoje rozwiązanie dla miłośników Swifta:
@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) { let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it var presentingVC = self.presentingViewController self.dismissViewControllerAnimated(false, completion: { () -> Void in presentingVC!.presentViewController(secondViewController, animated: true, completion: nil) }) }
źródło
Chciałem tego:
MapVC to mapa na pełnym ekranie.
Kiedy naciskam przycisk, otwiera się PopupVC (nie na pełnym ekranie) nad mapą.
Kiedy naciskam przycisk w PopupVC, wraca do MapVC, a następnie chcę wykonać viewDidAppear.
Ja to zrobiłem:
MapVC.m: w akcji przycisku, segue programowo i ustaw delegata
- (void) buttonMapAction{ PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"]; popvc.delegate = self; [self presentViewController:popvc animated:YES completion:nil]; } - (void)dismissAndPresentMap { [self dismissViewControllerAnimated:NO completion:^{ NSLog(@"dismissAndPresentMap"); //When returns of the other view I call viewDidAppear but you can call to other functions [self viewDidAppear:YES]; }]; }
PopupVC.h: przed @interface dodaj protokół
@protocol PopupVCProtocol <NSObject> - (void)dismissAndPresentMap; @end
po @interface: nowa właściwość
@property (nonatomic,weak) id <PopupVCProtocol> delegate;
PopupVC.m:
- (void) buttonPopupAction{ //jump to dismissAndPresentMap on Map view [self.delegate dismissAndPresentMap]; }
źródło
Rozwiązałem ten problem za pomocą UINavigationController podczas prezentacji. W MainVC, prezentując VC1
let vc1 = VC1() let navigationVC = UINavigationController(rootViewController: vc1) self.present(navigationVC, animated: true, completion: nil)
W VC1, gdy chciałbym pokazać VC2 i odrzucić VC1 w tym samym czasie (tylko jedna animacja), mogę mieć animację wypychania przez
let vc2 = VC2() self.navigationController?.setViewControllers([vc2], animated: true)
A w VC2 po zamknięciu kontrolera widoku, jak zwykle, możemy użyć:
self.dismiss(animated: true, completion: nil)
źródło