Pytanie modal Zobacz kontrolery - jak wyświetlić i zamknąć


Przez ostatni tydzień łamie mi głowę, jak rozwiązać problem z pokazywaniem i zwalnianiem wielu kontrolerów widoku. Stworzyłem przykładowy projekt i wkleiłem kod bezpośrednio z projektu. Mam 3 kontrolery widoku z odpowiadającymi im plikami .xib. MainViewController, VC1 i VC2. Mam dwa przyciski na głównym kontrolerze widoku.

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

To otwiera VC1 bez problemów. W VC1 mam inny przycisk, który powinien otworzyć VC2, jednocześnie odrzucając VC1.

- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
    VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
    [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc2 animated:YES completion:nil];
    [self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!


- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1. 

Chcę, aby powrócił do głównego kontrolera widoku, podczas gdy w tym samym czasie VC1 powinien był zostać na stałe usunięty z pamięci. VC1 powinien pojawić się tylko wtedy, gdy kliknę przycisk VC1 na głównym kontrolerze.

Drugi przycisk kontrolera widoku głównego powinien również być w stanie wyświetlić VC2 bezpośrednio z pominięciem VC1 i powrócić do głównego kontrolera po kliknięciu przycisku VC2. Nie ma długiego kodu, pętli ani żadnych timerów. Tylko nagłe wywołania kości, aby wyświetlić kontrolerów.


76
2018-02-16 06:06


pochodzenie




Odpowiedzi:


Ta linia:

[self dismissViewControllerAnimated:YES completion:nil];

nie wysyła wiadomości do siebie, wysyła wiadomość do swojego VC z prośbą o zwolnienie. Przedstawiając VC, tworzysz relację pomiędzy prezentującym VC a prezentowanym. Nie powinieneś więc niszczyć prezentowanego VC podczas prezentacji (przedstawiony VC nie może wysłać tej wiadomości z powrotem ...). Ponieważ tak naprawdę nie bierzesz tego pod uwagę, opuszczasz aplikację w stanie zdezorientowania. Zobacz moją odpowiedź Odrzucenie przedstawionego kontrolera widoku w którym polecam tę metodę jest jaśniej napisany:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

W twoim przypadku musisz upewnić się, że wszystkie czynności kontrolne są wykonywane mainVC. Powinieneś użyć delegata, aby wysłać prawidłową wiadomość z powrotem do MainViewController z ViewController1, tak aby mainVC mógł odrzucić VC1, a następnie zaprezentować VC2.

W VC2 VC1 dodaj protokół do pliku .h powyżej interfejsu @:

@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 VC1 .m metoda przycisku odrzucania powinna wywoływać metodę delegata

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}

Teraz w mainVC, ustaw 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ć taka sama jak twoja VC2Pressed: przycisk Metoda IBAction. Zauważ, że jest wywoływany z bloku zakończenia, aby upewnić się, że VC2 nie zostanie zaprezentowany, dopóki VC1 nie zostanie w pełni odrzucony.

Przechodzisz teraz z VC1-> VCMain-> VC2, więc prawdopodobnie będziesz chciał, aby tylko jeden z przejść był animowany.

aktualizacja 

W swoich komentarzach wyrażasz zdziwienie złożonością wymaganą do osiągnięcia pozornie prostej rzeczy. Zapewniam cię, że ten schemat delegacji jest tak ważny dla większości Celu C i Kakao, a ten przykład jest o najprostszym, jaki możesz zdobyć, że naprawdę powinieneś podjąć wysiłek, by mu to ułatwić.

W Apple's Zobacz Przewodnik programowania sterownika oni mają to znaczy:

Odrzucenie przedstawionego kontrolera widoku

Kiedy przychodzi czas na odrzucenie przedstawionego kontrolera widoku, preferowanym podejściem jest zwolnienie kontrolera przedstawiającego widok. Innymi słowy, ilekroć to możliwe, ten sam kontroler widoku, który przedstawiał kontroler widoku, powinien również wziąć odpowiedzialność za jego odrzucenie. Chociaż istnieje kilka technik powiadamiania kontrolera przedstawiającego widok, że jego przedstawiony kontroler widoku powinien zostać odrzucony, preferowaną techniką jest delegowanie. Aby uzyskać więcej informacji, zobacz "Korzystanie z delegowania do komunikacji z innymi kontrolerami."

Jeśli naprawdę zastanowisz się, co chcesz osiągnąć i jak sobie z tym poradzisz, uświadomisz sobie, że przesyłanie wiadomości do kontrolera MainViewController w celu wykonania całej pracy jest jedynym logicznym wyjściem, biorąc pod uwagę, że nie chcesz używać kontrolera NavigationController. Jeśli ty zrobić użyj kontrolera NavController, co oznacza, że ​​"delegujesz", nawet jeśli nie jest to jawne, do navController, aby wykonać całą pracę. Musi być trochę obiekt, który utrzymuje centralny ślad tego, co dzieje się z nawigacją VC, i czego potrzebujesz trochę sposób komunikowania się z nim, cokolwiek robisz.

W praktyce porady Apple są trochę ekstremalne ... w normalnych przypadkach nie trzeba wykonywać dedykowanych delegatów i metod, na których można polegać [self presentingViewController] dismissViewControllerAnimated: - w przypadkach podobnych do twojego, które chcesz, aby twoje zwolnienie miało inne efekty na odległych obiektach, które musisz zachować.

Oto coś, co możesz wyobrażać sobie pracować bez przeszkód dla wszystkich delegatów ...

- (IBAction)dismiss:(id)sender {
    [[self presentingViewController] dismissViewControllerAnimated:YES 
                                                        completion:^{
        [self.presentingViewController performSelector:@selector(presentVC2:) 
                                            withObject:nil];
    }];

}

Po zwróceniu się do kontrolera prezentującego o oddalenie nas, mamy blok zakończenia, który wywołuje metodę w presentViewController, aby wywołać VC2. Żaden delegat nie jest potrzebny. (Wielką zaletą bloków jest to, że w takich okolicznościach zmniejszają potrzebę delegatów). Jednak w tym przypadku jest kilka przeszkód ...

  • w VC1 nie wiedzieć mainVC implementuje metodę present2 - możesz skończyć z trudnymi do debugowania błędami lub awariami. Delegaci pomogą ci tego uniknąć.
  • gdy VC1 zostanie odrzucony, tak naprawdę nie jest tak, aby wykonać blok ukończenia ... czy jest? Czy self.presentingViewController coś jeszcze 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żeń i błędów.

Proszę więc ... poświęć czas na naukę delegacji!

update2

W twoim komentarzu udało ci się sprawić, żeby działało, używając tego w procedurze obsługi przycisku zamykania VC2:

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

Jest to z pewnością znacznie prostsze, ale pozostawia wiele problemów.

Szczelne połączenie
Twarde okablowanie struktury viewController razem. Na przykład, jeśli wstawisz nowy viewController przed mainVC, wymagane zachowanie zostanie przerwane (możesz przejść do poprzedniej). W VC1 musiałeś również #importować VC2. W związku z tym masz dość wiele zależności, które łamią cele OOP / MVC.

Korzystając z delegatów, ani VC1, ani VC2 nie muszą wiedzieć nic o mainVC lub jego antecedentach, więc trzymamy wszystko luźno połączone i modułowe.

Pamięć
VC1 nie zniknął, wciąż masz do niego dwa wskaźniki:

  • mainVC's presentedViewController własność
  • VC2 presentingViewController własność

Możesz to sprawdzić, logując się, a także robiąc to z VC2

[self dismissViewControllerAnimated:YES completion:nil]; 

Nadal działa, nadal wraca do VC1.

Wydaje mi się, że to przeciek pamięci.

Klucz do tego znajduje się w ostrzeżeniu, które tu otrzymujesz:

[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
 // Attempt to dismiss from view controller <VC1: 0x715e460>
 // while a presentation or dismiss is in progress!

Logika załamuje się, gdy próbujesz odrzucić prezentujący VC z którego VC2 to przedstawiony VC. Druga wiadomość tak naprawdę nie zostaje wykonana - może coś się wydarzy, ale nadal masz dwa wskaźniki do obiektu, który myślałeś, że go pozbyłeś. (edit - sprawdziłem to i nie jest tak źle, oba obiekty znikają po powrocie do mainVC)

To dość rozwlekły sposób powiedzenia - proszę, skorzystajcie z delegatów. Jeśli to pomaga, przedstawiłem kolejny krótki opis tego wzoru:
Czy przekazywanie kontrolera w konduktorze 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 rób tego Odrzucić wszystko ... jak stwierdziliśmy, tak naprawdę tak się nie dzieje.

W VC2:

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];

Ponieważ (nie) zdymisjonowaliśmy VC1, możemy sięgnąć wstecz przez VC1 do MainVC. MainVC odrzuca VC1. Ponieważ VC1 odszedł, jest przedstawiony, jak VC2 idzie z nim, więc wracasz do MainVC w czystym stanie.

Nadal jest bardzo sprzężony, ponieważ VC1 musi wiedzieć o VC2, a VC2 musi wiedzieć, że został osiągnięty za pośrednictwem MainVC-> VC1, ale jest to najlepsze, co otrzymasz bez odrobiny jawnej delegacji.


184
2018-02-16 12:49



wydaje się być skomplikowany. Próbowałem za nim podążać i kopiować do kropki, ale zgubiłem się w środku. Czy istnieje inny sposób osiągnięcia tego? Chciałem również dodać, że w delegacie aplikacji główny kontroler jest ustawiony jako kontroler widoku głównego. Nie chcę używać kontrolerów nawigacyjnych, ale zastanawiam się, dlaczego to powinno być tak skomplikowane. Podsumowując, po uruchomieniu aplikacji wyświetlany jest kontroler widoku głównego z dwoma przyciskami. Kliknięcie na pierwszy przycisk powoduje załadowanie VC1. Jest przycisk na VC1 i po kliknięciu powinien załadować VC2 bez błędów i ostrzeżeń, jednocześnie odrzucając VC1 z pamięci. - Hema
W VC2 mam przycisk i kliknięcie go powinno zwolnić VC2 z pamięci, a sterowanie powinno wrócić do głównego kontrolera i nie iść do VC1. - Hema
@ Hema, doskonale zrozumiałem twoje wymagania i zapewniam cię o tym jest poprawny sposób na zrobienie tego. Zaktualizowałem swoją odpowiedź nieco więcej informacji, mam nadzieję, że to pomoże. Jeśli spróbowałeś mojego podejścia i utknąłeś, proszę postawić nowe pytanie pokazujące dokładnie, co nie działa, więc możemy pomóc. Możesz również połączyć się z tym pytaniem dla jasności. - foundry
Cześć, był: Dziękuję za twój wgląd. Rozmawiam też o innym wątku (oryginalny wątek) i właśnie opublikowałem fragment z sugestii tam wspomnianych. Próbuję wszystkich odpowiedzi ekspertów, aby ustalić ten problem. Adres URL znajduje się tutaj: stackoverflow.com/questions/14840318/... - Hema
@Honey - Być może, ale oświadczenie było retoryczną odpowiedzią na kawałek "wyimaginowanego" pseudokodu. Celem, który chciałem zrobić, nie jest zatrzymywanie pułapek cyklicznych, ale kształcenie pytającego, dlaczego delegacja jest cennym wzorcem projektowym (który przypadkowo unika tego problemu). Myślę, że jest to myląca sprzeczka - pytanie dotyczy modalnych VC, ale wartość odpowiedzi polega głównie na wyjaśnieniu schematu delegatów, wykorzystaniu pytania i oczywistych frustracji OP jako katalizatora. Dziękuję za zainteresowanie (i edycje) !! - foundry


Sądzę, że źle zrozumiałeś kilka podstawowych pojęć dotyczących kontrolerów widoku modalnych iOS. Kiedy odrzucisz VC1, wszelkie prezentowane kontrolery widoku przez VC1 również zostaną odrzucone. Apple przeznaczone dla kontrolerów widoku modalnego do przepływu w stosie - w twoim przypadku VC2 jest prezentowany przez VC1. Odrzucasz VC1, gdy tylko przedstawisz VC2 z VC1, więc jest to totalna nieporządek. Aby osiągnąć to, co chcesz, buttonPressedFromVC1 powinien mieć mainVC obecny VC2 natychmiast po tym, jak VC1 sam się odrzuci. I myślę, że można to osiągnąć bez delegatów. Coś w tym stylu:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

Zauważ, że self.presentingViewController jest przechowywany w innej zmiennej, ponieważ po odrzuceniu vc1 nie powinieneś robić żadnych odniesień do niego.


9
2018-02-25 14:59



tak prosty! Chciałbym, żeby inni przewijali się do twojej odpowiedzi, zamiast zatrzymywać się w górnym poście. - Ryan Loggerythm
w kodzie PO, dlaczego tak nie jest [self dismiss...] zdarzyć po  [self present...] skończone? To nie jest coś asynchronicznego - Honey
@Honey faktycznie, jest coś asynchronicznego dzieje się podczas wywoływania presentViewController - dlatego ma obsługę zakończenia. Ale nawet używając tego, jeśli odrzucisz prezentujący kontroler widoku po tym, jak coś przedstawi, wszystko, co mu przedstawi, zostanie również odrzucone. Tak więc OP faktycznie chce przedstawić viewcontroller od innego prezentera, tak aby mógł odrzucić bieżący - Radu Simionescu
Ale nawet używając tego, jeśli odrzucisz prezentujący kontroler widoku po tym, jak coś przedstawi, wszystko, co mu przedstawi, zostanie również odrzucone... Aha, więc kompilator zasadniczo mówi "co robisz jest głupie." Po prostu cofnąłeś swoją poprzednią linię kodu (jako VC1 odpuszczę sobie i cokolwiek prezentuję). Nie rób tego " dobrze? - Honey
Kompilator nie "powie" nic na ten temat, a także może się zdarzyć, że nie ulegnie awarii podczas wykonywania tego, tylko że będzie się zachowywał w sposób, którego programista nie oczekuje - Radu Simionescu


Przykład w Szybki, ilustrując powyższe wyjaśnienie odlewni i dokumentację Apple'a:

  1. W oparciu o Dokumentacja Apple i odlewni wyjaśnienie powyżej (poprawianie niektórych błędów), presentViewController wersja wykorzystująca delikatesowy wzorzec projektowy:

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 {

}
  1. Na podstawie powyższego wyjaśnienia odlewni (korekta niektórych błędów), Wersja pushViewController za pomocą delegowanego wzorca projektowego:

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 {

}

8
2017-12-12 01:21



w twoim przykładzie ViewController klasa to mainVC prawda? - Honey


Radu Simionescu - niesamowita robota! i 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)
    })
}

4
2018-04-16 10:14



to w pewnym sensie denerwuje mnie, że to działa. Nie rozumiem, dlaczego blok nie przechwytuje "self.presentingViewController" i potrzebne jest mocne odwołanie, tj. "var prezentującVC". Tak czy inaczej to działa. dzięki - emdog4


Chciałem tego:

MapVC to mapa na pełnym ekranie.

Kiedy naciśniesz przycisk, otworzy się okno PopupVC (nie na pełnym ekranie) nad mapą.

Kiedy wciskam przycisk w PopupVC, to wraca do MapVC, a następnie chcę wykonać viewDidAppear.

Ja to zrobiłem:

MapVC.m: w akcji przycisku, programowo, i ustawić 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: before @interface, dodaj protokół

@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end

after @interface, nowa właściwość

@property (nonatomic,weak) id <PopupVCProtocol> delegate;

PopupVC.m:

- (void) buttonPopupAction{
  //jump to dismissAndPresentMap on Map view
  [self.delegate dismissAndPresentMap];
}

0
2017-08-31 11:47





Rozwiązałem problem za pomocą UINavigationController podczas prezentacji. W MainVC, przedstawiając VC1

let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)

W VC1, kiedy 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, gdy zamkniemy kontroler widoku, jak zwykle możemy użyć:

self.dismiss(animated: true, completion: nil)

0
2017-11-02 17:21