Wenn Sie einen Modal-View-Controller mit dismissViewController
schließen, gibt es die Option, einen Beendigungsblock bereitzustellen. Gibt es ein ähnliches Äquivalent für popViewController
?
Das Abschlussargument ist sehr praktisch. Ich kann es beispielsweise verwenden, um das Entfernen einer Zeile aus einer Tabellenansicht zu stoppen, bis der Modal vom Bildschirm abweicht, sodass der Benutzer die Zeilenanimation sehen kann. Bei der Rückkehr von einem Push-View-Controller hätte ich die gleiche Gelegenheit.
Ich habe versucht, popViewController
in einem UIView
-Animationsblock zu platzieren, wo ich Zugriff auf einen Beendigungsblock habe. Dies führt jedoch zu unerwünschten Nebeneffekten in der Ansicht, in die Sie springen.
Wenn keine solche Methode verfügbar ist, gibt es einige Problemumgehungen?
Ich weiß, dass eine Antwort vor mehr als zwei Jahren akzeptiert wurde, diese Antwort ist jedoch unvollständig.
Es gibt keine Möglichkeit, das zu tun, was Sie sofort wünschen
Dies ist technisch korrekt, da die UINavigationController
-API hierfür keine Optionen bietet. Wenn Sie jedoch das CoreAnimation-Framework verwenden, können Sie der darunter liegenden Animation einen Abschlussblock hinzufügen:
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// handle completion here
}];
[self.navigationController popViewControllerAnimated:YES];
[CATransaction commit];
Der Beendigungsblock wird aufgerufen, sobald die von popViewControllerAnimated:
verwendete Animation endet. Diese Funktionalität ist seit iOS 4 verfügbar.
Für iOS9 Swift-Version - funktioniert wie ein Zauber (hatte nicht für frühere Versionen getestet). Basierend auf dieser Antwort
extension UINavigationController {
func pushViewController(viewController: UIViewController, animated: Bool, completion: () -> ()) {
pushViewController(viewController, animated: animated)
if let coordinator = transitionCoordinator() where animated {
coordinator.animateAlongsideTransition(nil) { _ in
completion()
}
} else {
completion()
}
}
func popViewController(animated: Bool, completion: () -> ()) {
popViewControllerAnimated(animated)
if let coordinator = transitionCoordinator() where animated {
coordinator.animateAlongsideTransition(nil) { _ in
completion()
}
} else {
completion()
}
}
}
Ich habe eine Swift
-Version mit Erweiterungen mit @JorisKluivers answer erstellt.
Nach Abschluss der Animation wird sowohl für Push
als auch für pop
ein Abschluss abgeschlossen.
extension UINavigationController {
func popViewControllerWithHandler(completion: ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.popViewControllerAnimated(true)
CATransaction.commit()
}
func pushViewController(viewController: UIViewController, completion: ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.pushViewController(viewController, animated: true)
CATransaction.commit()
}
}
Ich hatte das gleiche Problem. Und weil ich sie mehrfach verwenden musste, und innerhalb von Ketten von Abschlussblöcken, erstellte ich diese generische Lösung in einer UINavigationController-Unterklasse:
- (void) navigationController:(UINavigationController *) navigationController didShowViewController:(UIViewController *) viewController animated:(BOOL) animated {
if (_completion) {
_completion();
_completion = nil;
}
}
- (UIViewController *) popViewControllerAnimated:(BOOL) animated completion:(void (^)()) completion {
_completion = completion;
return [super popViewControllerAnimated:animated];
}
Angenommen,
@interface NavigationController : UINavigationController <UINavigationControllerDelegate>
und
@implementation NavigationController {
void (^_completion)();
}
und
- (id) initWithRootViewController:(UIViewController *) rootViewController {
self = [super initWithRootViewController:rootViewController];
if (self) {
self.delegate = self;
}
return self;
}
Es gibt keine Möglichkeit, das zu tun, was Sie sofort wünschen. d.h. es gibt kein Verfahren mit einem Beendigungsblock, um eine Ansichtskontrolle aus einem Navigationsstapel zu entfernen.
Ich würde die Logik in viewDidAppear
setzen. Dies wird aufgerufen, wenn die Ansicht auf dem Bildschirm angezeigt wird. Es wird für alle verschiedenen Szenarien aufgerufen, in denen der View Controller erscheint, aber das sollte in Ordnung sein.
Oder Sie können die UINavigationControllerDelegate
-Methode navigationController:didShowViewController:animated:
verwenden, um eine ähnliche Aufgabe zu erfüllen. Dies wird aufgerufen, wenn der Navigationscontroller einen View-Controller gedrückt oder gedrückt hat.
Mit oder ohne Animation richtig arbeiten und enthält auch popToRootViewController
// updated for Swift 3.0
extension UINavigationController {
private func doAfterAnimatingTransition(animated: Bool, completion: @escaping (() -> Void)) {
if let coordinator = transitionCoordinator, animated {
coordinator.animate(alongsideTransition: nil, completion: { _ in
completion()
})
} else {
DispatchQueue.main.async {
completion()
}
}
}
func pushViewController(viewController: UIViewController, animated: Bool, completion: @escaping (() -> Void)) {
pushViewController(viewController, animated: animated)
doAfterAnimatingTransition(animated: animated, completion: completion)
}
func popViewController(animated: Bool, completion: @escaping (() -> Void)) {
popViewController(animated: animated)
doAfterAnimatingTransition(animated: animated, completion: completion)
}
func popToRootViewController(animated: Bool, completion: @escaping (() -> Void)) {
popToRootViewController(animated: animated)
doAfterAnimatingTransition(animated: animated, completion: completion)
}
}
Basierend auf der Antwort von @ HotJard, wenn Sie nur ein paar Zeilen Code benötigen. Schnell und einfach.
Swift 4 :
_ = self.navigationController?.popViewController(animated: true)
self.navigationController?.transitionCoordinator.animate(alongsideTransition: nil) { _ in
doWantIWantAfterContollerHasPopped()
}
Swift 3 Antwort dank dieser Antwort: https://stackoverflow.com/a/28232570/3412567
//MARK:UINavigationController Extension
extension UINavigationController {
//Same function as "popViewController", but allow us to know when this function ends
func popViewControllerWithHandler(completion: @escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.popViewController(animated: true)
CATransaction.commit()
}
func pushViewController(viewController: UIViewController, completion: @escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.pushViewController(viewController, animated: true)
CATransaction.commit()
}
}
Der Beendigungsblock wird aufgerufen, nachdem die viewDidDisappear-Methode auf dem präsentierten View-Controller aufgerufen wurde. Das Einfügen von Code in die viewDidDisappear-Methode des geöffneten View-Controllers sollte also wie ein Beendigungsblock funktionieren.
wenn du das hast ...
navigationController?.popViewController(animated: false)
NeXTSTEP()
und du möchtest einen Abschluss hinzufügen ...
CATransaction.begin()
navigationController?.popViewController(animated: true)
CATransaction.setCompletionBlock({ [weak self] in
self?.NeXTSTEP() })
CATransaction.commit()
so einfach ist das.
Swift 4.1
extension UINavigationController {
func pushToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.pushViewController(viewController, animated: animated)
CATransaction.commit()
}
func popViewController(animated:Bool = true, completion: @escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.popViewController(animated: true)
CATransaction.commit()
}
func popToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.popToViewController(viewController, animated: animated)
CATransaction.commit()
}
func popToRootViewController(animated:Bool = true, completion: @escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.popToRootViewController(animated: animated)
CATransaction.commit()
}
}
Es gibt einen Pod namens UINavigationControllerWithCompletionBlock , der Unterstützung für einen Beendigungsblock hinzufügt, wenn auf einem UINavigationController sowohl Push als auch Popping ausgeführt werden.
Genau das habe ich mit einem Block erreicht. Ich wollte, dass der abgerufene Ergebniscontroller die von der modalen Ansicht hinzugefügte Zeile nur dann anzeigt, wenn er den Bildschirm vollständig verlassen hat, damit der Benutzer die Änderung sehen kann. Bei der Vorbereitung des Segues, das für die Anzeige des Modal View Controllers zuständig ist, stelle ich den Block ein, den ich ausführen möchte, wenn der Modal nicht mehr angezeigt wird. Und im Modal-View-Controller überschreibe ich viewDidDissapear und rufe den Block auf. Ich fange einfach mit Aktualisierungen an, wenn der Modal erscheint, und beende die Aktualisierungen, wenn er verschwindet. Das liegt jedoch daran, dass ich einen NSFetchedResultsController verwende. Allerdings können Sie im Block alles tun, was Sie möchten.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:@"addPassword"]){
UINavigationController* nav = (UINavigationController*)segue.destinationViewController;
AddPasswordViewController* v = (AddPasswordViewController*)nav.topViewController;
...
// makes row appear after modal is away.
[self.tableView beginUpdates];
[v setViewDidDissapear:^(BOOL animated) {
[self.tableView endUpdates];
}];
}
}
@interface AddPasswordViewController : UITableViewController<UITextFieldDelegate>
...
@property (nonatomic, copy, nullable) void (^viewDidDissapear)(BOOL animated);
@end
@implementation AddPasswordViewController{
...
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
if(self.viewDidDissapear){
self.viewDidDissapear(animated);
}
}
@end
Verwenden Sie die nächste Erweiterung für Ihren Code: (Swift 4)
import UIKit
extension UINavigationController {
func popViewController(animated: Bool = true, completion: @escaping () -> Void) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
popViewController(animated: animated)
CATransaction.commit()
}
func pushViewController(_ viewController: UIViewController, animated: Bool = true, completion: @escaping () -> Void) {
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
pushViewController(viewController, animated: animated)
CATransaction.commit()
}
}
Der Vollständigkeit halber habe ich eine Objective-C-Kategorie erstellt:
// UINavigationController+CompletionBlock.h
#import <UIKit/UIKit.h>
@interface UINavigationController (CompletionBlock)
- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion;
@end
// UINavigationController+CompletionBlock.m
#import "UINavigationController+CompletionBlock.h"
@implementation UINavigationController (CompletionBlock)
- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
completion();
}];
UIViewController *vc = [self popViewControllerAnimated:animated];
[CATransaction commit];
return vc;
}
@end
Swift 4-Version mit optionalem viewController-Parameter zum Aufrufen eines bestimmten Parameters.
extension UINavigationController {
func pushViewController(viewController: UIViewController, animated:
Bool, completion: @escaping () -> ()) {
pushViewController(viewController, animated: animated)
if let coordinator = transitionCoordinator, animated {
coordinator.animate(alongsideTransition: nil) { _ in
completion()
}
} else {
completion()
}
}
func popViewController(viewController: UIViewController? = nil,
animated: Bool, completion: @escaping () -> ()) {
if let viewController = viewController {
popToViewController(viewController, animated: animated)
} else {
popViewController(animated: animated)
}
if let coordinator = transitionCoordinator, animated {
coordinator.animate(alongsideTransition: nil) { _ in
completion()
}
} else {
completion()
}
}
}
Aufgeräumte Swift 4 Version basierend auf dieser Antwort .
extension UINavigationController {
func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
self.pushViewController(viewController, animated: animated)
self.callCompletion(animated: animated, completion: completion)
}
func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController? {
let viewController = self.popViewController(animated: animated)
self.callCompletion(animated: animated, completion: completion)
return viewController
}
private func callCompletion(animated: Bool, completion: @escaping () -> Void) {
if animated, let coordinator = self.transitionCoordinator {
coordinator.animate(alongsideTransition: nil) { _ in
completion()
}
} else {
completion()
}
}
}