ios - UITableViewCell no se anula cuando se desliza hacia atrás rápidamente
ios7 (15)
He actualizado tres de mis aplicaciones a iOS 7, pero en las tres, a pesar de que no comparten ningún código, tengo el problema de que si el usuario se desliza para regresar al controlador de navegación (en lugar de presionar el botón Atrás) rápidamente , la celda permanecerá en su estado seleccionado.
Para las tres aplicaciones, una usa células personalizadas creadas mediante programación, otra usa celdas personalizadas creadas en un guión gráfico y la tercera usa celdas predeterminadas en una subclase muy básica de UITableView, también en un guión gráfico. En los tres casos, las celdas no se anulan por sí mismas. Si el usuario desliza lentamente o toca el botón Atrás, se desactiva como normal.
Esto solo sucede en mis aplicaciones para iOS 7, las propias aplicaciones de Apple y las aplicaciones de terceros actualizadas para iOS 7 parecen comportarse normalmente (aunque con ligeras diferencias en la rapidez con la que se anula la selección de las celdas).
Debe haber algo que estoy haciendo mal, pero no estoy seguro de qué?
Basado en el código de Rhult , hice algunos cambios.
Esta implementación permite al usuario cancelar el deslizamiento hacia atrás y aún así mantenerse seleccionado para volver a deslizar en el futuro anular la selección de la animación
@property(strong, nonatomic) NSIndexPath *savedSelectedIndexPath;
- (void)viewDidLoad {
[super viewDidLoad];
self.clearsSelectionOnViewWillAppear = NO;
}
-(void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.savedSelectedIndexPath = nil;
}
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.savedSelectedIndexPath && ![self.tableView indexPathForSelectedRow]) {
[self.tableView selectRowAtIndexPath:self.savedSelectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
} else {
self.savedSelectedIndexPath = [self.tableView indexPathForSelectedRow];
}
}
Después de encontrarme con esto hoy, descubrí que aparentemente este es un problema bastante conocido con UITableView, su soporte para transiciones de navegación interactivas está ligeramente roto. La gente detrás de Castro ha publicado un excelente análisis y solución a esto: http://blog.supertop.co/post/80781694515/viewmightappear
Decidí usar su solución que también considera las transiciones canceladas:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSIndexPath *selectedRowIndexPath = [self.tableView indexPathForSelectedRow];
if (selectedRowIndexPath) {
[self.tableView deselectRowAtIndexPath:selectedRowIndexPath animated:YES];
[[self transitionCoordinator] notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
if ([context isCancelled]) {
[self.tableView selectRowAtIndexPath:selectedRowIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}];
}
}
Esta solución anima la anulación de la fila junto con el coordinador de transición (para que un VC dirigido por el usuario descarte) y vuelve a aplicar la selección si el usuario cancela la transición. Adaptado de una solución de Caleb Davenport en Swift. Solo probado en iOS 9. Probado para funcionar tanto con la transición impulsada por el usuario (deslizamiento) como con el botón "Atrás" al estilo antiguo.
En la subclase UITableViewController
:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Workaround. clearsSelectionOnViewWillAppear is unreliable for user-driven (swipe) VC dismiss
NSIndexPath *indexPath = self.tableView.indexPathForSelectedRow;
if (indexPath && self.transitionCoordinator) {
[self.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self.tableView deselectRowAtIndexPath:indexPath animated:animated];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
if ([context isCancelled]) {
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}];
}
}
Esto funcionó mejor para mí:
- (void)viewDidLoad {
[super viewDidLoad];
self.clearsSelectionOnViewWillAppear = NO;
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:animated];
}
Incluso conseguí desmarcar mucho mejor cuando estaba deslizando hacia atrás lentamente.
Estoy lidiando con el mismo problema ahora mismo. El UICatalog -muestra de Apple parece traer la solución sucia.
Realmente no me hace feliz en absoluto. Como se mencionó anteriormente, usa [self.tableView deselectRowAtIndexPath:tableSelection animated:NO];
para anular la selección de la fila seleccionada actualmente.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// this UIViewController is about to re-appear, make sure we remove the current selection in our table view
NSIndexPath *tableSelection = [self.tableView indexPathForSelectedRow];
[self.tableView deselectRowAtIndexPath:tableSelection animated:NO];
// some over view controller could have changed our nav bar tint color, so reset it here
self.navigationController.navigationBar.tintColor = [UIColor darkGrayColor];
}
Tengo que mencionar que el código de muestra puede no ser ios 7 iOS 8 iOS 9 iOS 10 listo
Algo que realmente me confunde es la referencia de clase UITableViewController :
Cuando la vista de tabla está a punto de aparecer la primera vez que se carga, el controlador de vista de tabla vuelve a cargar los datos de la vista de tabla. También borra su selección (con o sin animación, dependiendo de la solicitud) cada vez que se muestra la vista de tabla. La clase
UITableViewController
implementa esto en el método de superclaseviewWillAppear:
Puede deshabilitar este comportamiento cambiando el valor en la propiedadclearsSelectionOnViewWillAppear
.
Este es exactamente el comportamiento que espero ... pero parece que no funciona. Ni para ti ni para mí. Realmente tenemos que usar la solución "sucia" y hacerlo por nuestra cuenta.
Estoy usando
[tableView deselectRowAtIndexPath:indexPath animated:YES];
al final del método
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Me gusta esto:
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//doing something according to selected cell...
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
He encontrado una solución muy simple a este problema que hace que el comportamiento predeterminado funcione como debería. No estaba satisfecho con las soluciones que implican deselectRowAtIndexPath
ya que el efecto visual resultante era ligeramente diferente.
Todo lo que tiene que hacer para evitar este extraño comportamiento es volver a cargar la tabla cuando se muestre la vista:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.tableView reloadData];
}
La respuesta de Fabio funciona bien, pero no da la apariencia correcta si el usuario se desliza un poco y luego cambia de opinión. Para que el caso sea el correcto, debe guardar la ruta de índice seleccionada y restablecerla cuando sea necesario.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.savedSelectedIndexPath = nil;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.savedSelectedIndexPath) {
[self.tableView selectRowAtIndexPath:self.savedSelectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.savedSelectedIndexPath = self.tableView.indexPathForSelectedRow;
if (self.savedSelectedIndexPath) {
[self.tableView deselectRowAtIndexPath:self.savedSelectedIndexPath animated:YES];
}
}
Si usa un UITableViewController, asegúrese de deshabilitar el clearing incorporado:
self.clearsSelectionOnViewWillAppear = NO;
y agregue la propiedad para savedSelectedIndexPath:
@property(strong, nonatomic) NSIndexPath *savedSelectedIndexPath;
Si necesita hacer esto en algunas clases diferentes, puede tener sentido dividirlo en un ayudante, por ejemplo, como lo hice en este momento: https://gist.github.com/rhult/46ee6c4e8a862a8e66d4
Para veloz
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
guard let indexPath = tableView.indexPathForSelectedRow else{
return
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
Puedes intentar establecer
self.clearsSelectionOnViewWillAppear = YES;
en un UITableViewController o
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO];
en viewWillAppear, tal vez antes de llamar a [super viewWillAppear: animated]; Si su UItableView no está dentro de un UITableViewController, debe anular la selección de las celdas manualmente:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Simple Swift 3 Respuesta:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if tableView.indexPathForSelectedRow != nil {
self.tableView.deselectRow(at: tableView.indexPathForSelectedRow! as IndexPath, animated: true)
}
}
Utilizar
[tableView deselectRowAtIndexPath:indexPath animated:YES];
código en
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method
Codestage siempre fue la mejor respuesta , así que decidí convertirlo en Swift 2.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
let selectedRowIndexPath = self.tableView.indexPathForSelectedRow
if ((selectedRowIndexPath) != nil) {
self.tableView.deselectRowAtIndexPath(selectedRowIndexPath!, animated: true)
self.transitionCoordinator()?.notifyWhenInteractionEndsUsingBlock({ context in
if (context.isCancelled()) {
self.tableView.selectRowAtIndexPath(selectedRowIndexPath, animated: false, scrollPosition: UITableViewScrollPosition.None)
}
})
}
}
La solución de Rhult funciona perfectamente en iOS 9.2. Esta es la implementación en Swift:
Declare una variable en su MasterViewController
para guardar IndexPath:
var savedSelectedIndexPath: NSIndexPath?
Luego puede poner el código en una extensión para mayor claridad:
extension MasterViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.savedSelectedIndexPath = nil
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if let indexPath = self.savedSelectedIndexPath {
self.tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None)
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.savedSelectedIndexPath = tableView.indexPathForSelectedRow
if let indexPath = self.savedSelectedIndexPath {
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
}
Respuesta de Codestage , en Swift 3. notifyWhenInteractionEnds
está en desuso.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if let indexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: indexPath, animated: true)
self.transitionCoordinator?.notifyWhenInteractionChanges { (context) in
if context.isCancelled {
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
}
}
}