Digamos que tengo una instancia de una clase de controlador de vista llamada VC2. En VC2, hay un botón "cancelar" que se cerrará. Pero no puedo detectar ni recibir ninguna devolución de llamada cuando el botón "cancelar" se activó. VC2 es una caja negra.
Un controlador de vista (llamado VC1) presentará VC2 usando el presentViewController:animated:completion:
método.
¿Qué opciones tiene VC1 para detectar cuándo se descartó VC2?
Editar: desde el comentario de @rory mckinnel y la respuesta de @NicolasMiari, probé lo siguiente:
En VC2:
-(void)cancelButton:(id)sender
{
[self dismissViewControllerAnimated:YES completion:^{
}];
// [super dismissViewControllerAnimated:YES completion:^{
//
// }];
}
En 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:^{
//
// }];
}
Pero el dismissViewControllerAnimated
en el VC1 no fue llamado.
ios
uiviewcontroller
usuario523234
fuente
fuente
dismissViewControllerAnimated
en su controlador VC1, creo que se llamará cuando presione cancelar en VC2. Detecte el descarte y luego llame a la versión de superclases que hará el descarte real.[self.presentingViewController dismissViewControllerAnimated]
. Puede ser que el código interno tenga un mecanismo diferente para pedirle al presentador que haga el descarte.Respuestas:
Según los documentos, el controlador presentador es responsable del despido real. Cuando el controlador presentado se descarta, le pedirá al presentador que lo haga por él. Entonces, si anula descartarViewControllerAnimated en su controlador VC1, creo que se llamará cuando presione cancelar en VC2. Detecte el descarte y luego llame a la versión de superclases que hará el descarte real.
Como se encontró en la discusión, esto no parece funcionar. En lugar de confiar en el mecanismo subyacente, en lugar de llamar
dismissViewControllerAnimated:completion
a sí mismo VC2, llamadadismissViewControllerAnimated:completion
enself.presentingViewController
en VC2. Esto luego llamará a su anulación directamente.Un enfoque mejor en conjunto sería hacer que VC2 proporcione un bloque que se llama cuando se completa el controlador modal.
Entonces, en VC2, proporcione una propiedad de bloque, por ejemplo, con el nombre
onDoneBlock
.En VC1 presentas lo siguiente:
En VC1, cree VC2
Establezca el controlador hecho para VC2 como:
VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};
Presente el controlador VC2 como normal usando [self presentViewController: VC2 animado: SÍ completado: nulo];
En VC2, en la llamada de acción de cancelación de destino
self.onDoneBlock();
El resultado es que VC2 le dice a quien lo plantea que está hecho. Puede extender el
onDoneBlock
para tener argumentos que indiquen si el modal se completó, canceló, tuvo éxito, etc.fuente
Hay una propiedad booleana especial dentro
UIViewController
llamadaisBeingDismissed
que puede usar para este propósito:override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isBeingDismissed { // TODO: Do your stuff here. } }
fuente
viewDidAppear
.Usar una propiedad de bloque
Declarar en VC2
var onDoneBlock : ((Bool) -> Void)?
Configuración en VC1
VC2.onDoneBlock = { result in // Do something }
Llame a VC2 cuando esté a punto de despedir
onDoneBlock!(true)
fuente
Tanto el controlador de vista presentado como el presentado pueden llamar
dismissViewController:animated:
para descartar el controlador de vista presentado.La primera opción es (posiblemente) la "correcta", en cuanto al diseño: el mismo controlador de vista "principal" es responsable de presentar y descartar el controlador de vista modal ("secundario").
Sin embargo, lo último es más conveniente: normalmente, el botón "descartar" se adjunta a la vista del controlador de vista presentado, y tiene dicho controlador de vista configurado como su objetivo de acción.
Si está adoptando el enfoque anterior, ya conoce la línea de código en su controlador de vista de presentación donde ocurre el despido: ejecute su código justo después
dismissViewControllerAnimated:completion:
o dentro del bloque de finalización.Si está adoptando el último enfoque (el controlador de vista presentado se descarta a sí mismo), tenga en cuenta que llamar
dismissViewControllerAnimated:completion:
desde el controlador de vista presentado hace que UIKit a su vez llame a ese método en el controlador de vista de presentación:( fuente: Referencia de clase UIViewController )
Entonces, para interceptar dicho evento, puede anular ese método en el controlador de vista de presentación :
override func dismiss(animated flag: Bool, completion: (() -> Void)?) { super.dismiss(animated: flag, completion: completion) // Your custom code here... }
fuente
extension Foo: UIAdaptivePresentationControllerDelegate { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { //call whatever you want } } vc.presentationController?.delegate = foo
fuente
iOS 13.0+
soloPuede utilizar el segue de desenrollado para realizar esta tarea, sin necesidad de utilizar el controlador de descarteModalViewController. Defina un método de segue de desenrollado en su VC1.
Vea este enlace sobre cómo crear la secuencia de relajación, https://stackoverflow.com/a/15839298/5647055 .
Suponiendo que la transición de desconexión esté configurada, en el método de acción definido para el botón "Cancelar", puede realizar la transición como:
[self performSegueWithIdentifier:@"YourUnwindSegueName" sender:nil];
Ahora, siempre que presione el botón "Cancelar" en el VC2, se cerrará y aparecerá VC1. También llamará al método de desenrollado, definido en VC1. Ahora, ya sabe cuándo se descarta el controlador de vista presentado.
fuente
Utilizo lo siguiente para indicar a un coordinador que el controlador de vista está "listo". Esto se usa en una
AVPlayerViewController
subclase en una aplicación tvOS y se llamará después de que se haya completado la transición de despido de 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?() } }) } }
fuente
Usar el
willMove(toParent: UIViewController?)
de la siguiente manera pareció funcionar para mí. (Probado en iOS12).override func willMove(toParent parent: UIViewController?) { super.willMove(toParent: parent); if parent == nil { // View controller is being removed. // Perform onDismiss action } }
fuente
@ user523234 - "Pero no se llamaba al DispenssViewControllerAnimated del VC1"
No puede suponer que VC1 realmente hace la presentación; podría ser el controlador de vista raíz, VC0, por ejemplo. Hay 3 controladores de vista involucrados:
En su ejemplo,
VC1 = sourceViewController
,VC2 = presentedViewController
,?? = presentingViewController
- tal vez VC1, tal vez no.Sin embargo, siempre puede confiar en que se llame a VC1.animationControllerForDismissedController (si ha implementado los métodos delegados) al descartar VC2 y en ese método puede hacer lo que quiera con VC1
fuente
He visto esta publicación tantas veces al tratar este problema, que pensé que finalmente podría arrojar algo de luz sobre una posible respuesta.
Si lo que necesita es saber si las acciones iniciadas por el usuario (como los gestos en la pantalla) provocaron el rechazo de un UIActionController , y no quiere invertir tiempo en crear subclases o extensiones o lo que sea en su código, existe una alternativa.
Resulta que la propiedad popoverPresentationController de un UIActionController (o, más bien, cualquier UIViewController a tal efecto), tiene un delegado que puede establecer en cualquier momento en su código, que es de tipo UIPopoverPresentationControllerDelegate , y tiene los siguientes métodos:
Asigne el delegado de su controlador de acción, implemente su método (s) de elección en la clase de delegado (vista, controlador de vista o lo que sea), ¡y listo!
Espero que esto ayude.
fuente
Seleccione una subclase de: UIStoryboardSegue
Vaya al archivo DismissSegue.m y escriba el siguiente código:
- (void)perform { UIViewController *sourceViewController = self.sourceViewController; [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; }
Abra el guión gráfico y luego Ctrl + arrastre desde el botón cancelar a VC1 y seleccione Acción Segue como Descartar y listo.
fuente
Si anula la atenuación del controlador de vista:
override func removeFromParentViewController() { super.removeFromParentViewController() // your code here }
Al menos esto funcionó para mí.
fuente
Puede manejar uiviewcontroller cerrado usando Unwind Segues.
https://developer.apple.com/library/content/technotes/tn2298/_index.html
https://spin.atomicobject.com/2014/12/01/program-ios-unwind-segue/
fuente
override
ing meviewDidAppear
hizo el truco. Usé un Singleton en mi modal y ahora puedo configurar y obtener de eso dentro del VC que llama, el modal y en cualquier otro lugar.fuente
viewDidAppear
?viewWillDisappear
Función de anulación en el controlador de vista presentado.override func viewWillDisappear(_ animated: Bool) { //Your code here }
fuente
viewDidAppear
.Como se ha mencionado, la solución es utilizar
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)
.Para aquellos que se preguntan por qué
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)
no siempre parece funcionar, es posible que descubran que la llamada está siendo interceptada por unUINavigationController
si está siendo administrada. Escribí una subclase que debería ayudar:class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }
fuente
Si desea manejar el cierre del controlador de vista, debe usar el código a continuación.
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (self.isBeingDismissed && self.completion != NULL) { self.completion(); } }
Desafortunadamente, no podemos llamar a la finalización en el método anulado,
(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;
porque este método se llama solo si llama al método de descarte de este controlador de vista.fuente
viewWillDisappear
tampoco funciona correctamente sin emparejarse conviewDidAppear
.Otra opción es escuchar dispatsalTransitionDidEnd () de su UIPresentationController personalizado
fuente
He usado deinit para ViewController
deinit { dataSource.stopUpdates() }
fuente