Powiedzmy, że mam instancję klasy kontrolera widoku o nazwie VC2. W VC2 jest przycisk „Anuluj”, który sam się zamyka. Ale nie mogę wykryć ani odebrać żadnego oddzwonienia, gdy przycisk „anuluj” został wyzwolony. VC2 to czarna skrzynka.
Kontroler widoku (zwany VC1) przedstawi VC2 przy użyciu presentViewController:animated:completion:
metody.
Jakie opcje musi wykryć VC1, kiedy VC2 został odrzucony?
Edycja: Z komentarza @rory mckinnel i odpowiedzi @NicolasMiari próbowałem następujących rzeczy:
W VC2:
-(void)cancelButton:(id)sender
{
[self dismissViewControllerAnimated:YES completion:^{
}];
// [super dismissViewControllerAnimated:YES completion:^{
//
// }];
}
W VC1:
//-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
- (void)dismissViewControllerAnimated:(BOOL)flag
completion:(void (^ _Nullable)(void))completion
{
NSLog(@"%s ", __PRETTY_FUNCTION__);
[super dismissViewControllerAnimated:flag completion:completion];
// [self dismissViewControllerAnimated:YES completion:^{
//
// }];
}
Ale dismissViewControllerAnimated
w VC1 nie dzwoniono.
ios
uiviewcontroller
user523234
źródło
źródło
dismissViewControllerAnimated
w kontrolerze VC1, wierzę, że zostanie on wywołany, gdy naciśniesz anuluj na VC2. Wykryj odrzucenie, a następnie wywołaj wersję superklas, która wykona faktyczne odrzucenie.[self.presentingViewController dismissViewControllerAnimated]
. Może się zdarzyć, że kod wewnętrzny ma inny mechanizm poproszenia prezentera o zwolnienie.Odpowiedzi:
Zgodnie z dokumentacją za faktyczne zwolnienie odpowiedzialny jest kontroler przedstawiający. Kiedy przedstawiony kontroler zwolni się, poprosi prezentera, aby zrobił to za niego. Więc jeśli zastąpisz dismissViewControllerAnimated w kontrolerze VC1, wierzę, że zostanie wywołany po naciśnięciu przycisku Cancel na VC2. Wykryj odrzucenie, a następnie wywołaj wersję superklas, która wykona faktyczne odrzucenie.
Jak wynika z dyskusji, nie wydaje się to działać. Zamiast polegać na mechanizmu bazowego, zamiast dzwonić
dismissViewControllerAnimated:completion
na samej VC2, zadzwońdismissViewControllerAnimated:completion
naself.presentingViewController
w VC2. Spowoduje to bezpośrednie wywołanie nadpisania.Całkowicie lepszym podejściem byłoby posiadanie VC2 zapewniającego blok, który jest wywoływany po zakończeniu działania kontrolera modalnego.
Tak więc w VC2 podaj właściwość bloku powiedz z nazwą
onDoneBlock
.W VC1 prezentujesz w następujący sposób:
W VC1 utwórz VC2
Ustaw gotową obsługę dla VC2 jako:
VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};
Przedstaw kontroler VC2 w normalny sposób, używając [self presentViewController: VC2 animated: YES complete: nil];
W VC2 w wywołaniu akcji Cancel target
self.onDoneBlock();
W rezultacie VC2 mówi każdemu, kto go podniesie, że jest zrobione. Możesz rozszerzyć,
onDoneBlock
aby mieć argumenty, które wskazują, czy modalne zostało zakończone, anulowane, powiodło się itp.źródło
Wewnątrz znajduje się specjalna właściwość logiczna o
UIViewController
nazwieisBeingDismissed
, której możesz użyć do tego celu:override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isBeingDismissed { // TODO: Do your stuff here. } }
źródło
viewDidAppear
.Użyj właściwości bloku
Zadeklaruj w VC2
var onDoneBlock : ((Bool) -> Void)?
Konfiguracja w VC1
VC2.onDoneBlock = { result in // Do something }
Zadzwoń do VC2, kiedy masz zamiar zwolnić
onDoneBlock!(true)
źródło
Może wywoływać zarówno kontroler prezentujący, jak i prezentowany widok
dismissViewController:animated:
w celu odrzucenia kontrolera widoku prezentowanego.Pierwsza opcja jest (prawdopodobnie) „poprawna”, z punktu widzenia projektu: ten sam „nadrzędny” kontroler widoku jest odpowiedzialny zarówno za prezentowanie, jak i odrzucanie modalnego („podrzędnego”) kontrolera widoku.
Jednak to drugie jest wygodniejsze: zazwyczaj przycisk „odrzuć” jest dołączony do widoku kontrolera widoku prezentowanego i ma wspomniany kontroler widoku ustawiony jako cel akcji.
Jeśli stosujesz poprzednie podejście, znasz już wiersz kodu w kontrolerze widoku prezentacji, w którym występuje odrzucenie: albo uruchom kod zaraz po
dismissViewControllerAnimated:completion:
, albo w bloku uzupełniania.Jeśli przyjmujesz to drugie podejście (kontroler widoku prezentowanego odrzuca się), pamiętaj, że wywołanie
dismissViewControllerAnimated:completion:
kontrolera widoku prezentowanego powoduje, że UIKit z kolei wywołuje tę metodę na kontrolerze widoku prezentacyjnego:( źródło: odwołanie do klasy UIViewController )
Aby więc przechwycić takie zdarzenie, możesz nadpisać tę metodę w kontrolerze widoku prezentującego :
override func dismiss(animated flag: Bool, completion: (() -> Void)?) { super.dismiss(animated: flag, completion: completion) // Your custom code here... }
źródło
extension Foo: UIAdaptivePresentationControllerDelegate { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { //call whatever you want } } vc.presentationController?.delegate = foo
źródło
iOS 13.0+
tylkoAby wykonać to zadanie, możesz użyć odprężania segue, bez konieczności używania discissModalViewController. Zdefiniuj metodę odwijania płynności w swoim VC1.
Zobacz ten link, aby dowiedzieć się, jak utworzyć odprężającą ścieżkę, https://stackoverflow.com/a/15839298/5647055 .
Zakładając, że Twoja odprężająca ścieżka jest skonfigurowana, w metodzie działania zdefiniowanej dla przycisku „Anuluj”, możesz wykonać przejście jako -
[self performSegueWithIdentifier:@"YourUnwindSegueName" sender:nil];
Teraz, gdy naciśniesz przycisk „Anuluj” w VC2, zostanie on odrzucony i pojawi się VC1. Wywoła również metodę odwijania, zdefiniowaną w VC1. Teraz wiesz, kiedy przedstawiony kontroler widoku zostanie odrzucony.
źródło
Używam następujących znaków, aby zasygnalizować koordynatorowi, że kontroler widoku jest „gotowy”. Jest to używane w
AVPlayerViewController
podklasie w aplikacji tvOS i zostanie wywołane po zakończeniu przejścia od zwolnienia playerVC:class PlayerViewController: AVPlayerViewController { var onDismissal: (() -> Void)? override func beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) { super.beginAppearanceTransition(isAppearing, animated: animated) transitionCoordinator?.animate(alongsideTransition: nil, completion: { [weak self] _ in if !isAppearing { self?.onDismissal?() } }) } }
źródło
Korzystanie z
willMove(toParent: UIViewController?)
poniższego sposobu wydawało mi się działać. (Testowane na iOS12).override func willMove(toParent parent: UIViewController?) { super.willMove(toParent: parent); if parent == nil { // View controller is being removed. // Perform onDismiss action } }
źródło
@ user523234 - "Ale nie wywoływano pliku dismissViewControllerAnimated w VC1."
Nie możesz założyć, że VC1 faktycznie przeprowadza prezentację - może to być główny kontroler widoku, powiedzmy VC0. W grę wchodzą 3 kontrolery widoku:
W przykładzie
VC1 = sourceViewController
,VC2 = presentedViewController
,?? = presentingViewController
- może VC1, a może nie.Jednak zawsze możesz polegać na wywołaniu VC1.animationControllerForDismissedController (jeśli zaimplementowałeś metody delegata) podczas odrzucania VC2 iw tej metodzie możesz robić, co chcesz z VC1
źródło
Widziałem ten post tyle razy, gdy zajmowałem się tym problemem, pomyślałem, że w końcu mogę rzucić trochę światła na możliwą odpowiedź.
Jeśli potrzebujesz wiedzieć, czy akcje inicjowane przez użytkownika (takie jak gesty na ekranie) spowodowały zwolnienie kontrolera UIActionController i nie chcesz poświęcać czasu na tworzenie podklas, rozszerzeń lub czegokolwiek w kodzie, istnieje alternatywa.
Jak się okazuje, właściwość popoverPresentationController elementu UIActionController (lub raczej dowolnego UIViewController w tym celu) ma delegata, który można ustawić w dowolnym momencie w kodzie, który jest typu UIPopoverPresentationControllerDelegate i ma następujące metody:
Przypisz delegata ze swojego kontrolera akcji, zaimplementuj wybrane metody w klasie delegatów (widok, kontroler widoku lub cokolwiek) i voila!
Mam nadzieję że to pomoże.
źródło
Inną opcją jest odsłuchanie zwolnieniaTransitionDidEnd () niestandardowego kontrolera UIPresentationController
źródło
Wybierz podklasę: UIStoryboardSegue
Przejdź do pliku DismissSegue.m i zapisz następujący kod:
- (void)perform { UIViewController *sourceViewController = self.sourceViewController; [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; }
Otwórz scenorys, a następnie Ctrl + przeciągnij z przycisku anulowania do VC1 i wybierz Segment akcji jako Odrzuć i gotowe.
źródło
Jeśli zastąpisz ukrywanie kontrolera widoku:
override func removeFromParentViewController() { super.removeFromParentViewController() // your code here }
Przynajmniej to zadziałało dla mnie.
źródło
Możesz obsłużyć zamknięcie uiviewcontroller za pomocą Unwind Segues.
https://developer.apple.com/library/content/technotes/tn2298/_index.html
https://spin.atomicobject.com/2014/12/01/program-ios-unwind-segue/
źródło
override
ingviewDidAppear
załatwił sprawę za mnie. Użyłem Singletona w moim modalu i jestem teraz w stanie ustawić i pobrać z tego w wywołującym VC, modalnym i wszędzie indziej.źródło
viewDidAppear
?viewWillDisappear
Funkcja nadpisania w kontrolerze widoku prezentowanego.override func viewWillDisappear(_ animated: Bool) { //Your code here }
źródło
viewDidAppear
.Jak już wspomniano, rozwiązaniem jest użycie
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)
.Dla tych, którzy zastanawiają się, dlaczego
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)
nie zawsze wydaje się działać, może się okazać, że połączenie jest przechwytywane przez a,UINavigationController
jeśli jest zarządzane. Napisałem podklasę, która powinna pomóc:class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }
źródło
Jeśli chcesz obsłużyć odrzucanie kontrolera widoku, powinieneś użyć poniższego kodu.
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (self.isBeingDismissed && self.completion != NULL) { self.completion(); } }
Niestety nie możemy wywołać zakończenia w metodzie przesłoniętej -
(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;
ponieważ ta metoda jest wywoływana tylko wtedy, gdy wywołujesz metodę odrzucenia tego kontrolera widoku.źródło
viewWillDisappear
też nie działa poprawnie bez sparowania zviewDidAppear
.Użyłem deinit dla ViewController
deinit { dataSource.stopUpdates() }
źródło