Deslice el dedo hacia Eliminar y el botón "Más" (como en la aplicación de Correo en iOS 7)

246

Cómo crear un botón "más" cuando el usuario desliza una celda en la vista de tabla (como la aplicación de correo en ios 7)

He estado buscando esta información aquí y en el foro de Cocoa Touch, pero parece que no puedo encontrar la respuesta y espero que alguien más inteligente que yo pueda darme una solución.

Me gustaría que cuando el usuario deslice una celda de vista de tabla, muestre más de un botón de edición (el predeterminado es el botón Eliminar). En la aplicación Correo para iOS 7, puede deslizar para eliminar, pero aparece un botón "MÁS".

ingrese la descripción de la imagen aquí

Guy Kahlon
fuente
Para agregar el botón "Eliminar", implemento las siguientes dos funciones. - (BOOL) tableView: (UITableView *) tableView canEditRowAtIndexPath: (NSIndexPath *) indexPath; - (void) tableView: (UITableView *) tableView commitEditingStyle: (UITableViewCellEditingStyle) editingStyle forRowAtIndexPath: (NSIndexPath *) indexPath; Y quiero agregar el botón "Más" al lado.
Guy Kahlon
3
@MonishBansal Bansal Parece que alguien en este hilo ( devforums.apple.com/message/860459#860459 en el foro de desarrolladores de Apple) siguió adelante y creó su propia implementación. Puedes encontrar un proyecto que haga lo que quieras en GitHub: github.com/daria-kopaliani/DAContextMenuTableViewController
Guy Kahlon
8
@GuyKahlonMatrix gracias por la solución, funciona de maravilla. Esta pregunta es el resultado número 1 en muchas búsquedas de Google, y las personas se ven obligadas a intercambiar sus conocimientos utilizando los comentarios porque un tipo decidió que es más útil cerrar la pregunta y predicar la democracia. Este lugar claramente necesita mejores modificaciones.
Şafak Gezer
2
Si puede apuntar a iOS 8, mi respuesta a continuación será la que desee.
Johnny

Respuestas:

126

Cómo implementar

Parece que iOS 8 abre esta API. Hay indicios de dicha funcionalidad en Beta 2.

Para que algo funcione, implemente los siguientes dos métodos en el delegado de su UITableView para obtener el efecto deseado (vea la esencia para un ejemplo).

- tableView:editActionsForRowAtIndexPath:
- tableView:commitEditingStyle:forRowAtIndexPath:


Problemas conocidos

La documentación dice tableView: commitEditingStyle: forRowAtIndexPath es:

"No se requieren acciones de edición con UITableViewRowAction; en su lugar, se invocará el controlador de la acción".

Sin embargo, el deslizamiento no funciona sin él. Incluso si el código auxiliar del método está en blanco, todavía lo necesita, por ahora. Esto es obviamente un error en beta 2.


Fuentes

https://twitter.com/marksands/status/481642991745265664 https://gist.github.com/marksands/76558707f583dbb8f870

Respuesta original: https://stackoverflow.com/a/24540538/870028


Actualizar:

Código de muestra con este funcionamiento (en Swift): http://dropbox.com/s/0fvxosft2mq2v5m/DeleteRowExampleSwift.zip

El código de muestra contiene este método fácil de seguir en MasterViewController.swift, y solo con este método obtienes el comportamiento que se muestra en la captura de pantalla de OP:

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {

    var moreRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "More", handler:{action, indexpath in
        println("MORE•ACTION");
    });
    moreRowAction.backgroundColor = UIColor(red: 0.298, green: 0.851, blue: 0.3922, alpha: 1.0);

    var deleteRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler:{action, indexpath in
        println("DELETE•ACTION");
    });

    return [deleteRowAction, moreRowAction];
}
Johnny
fuente
1
Esto parece ser correcto, pero en Xcode 6 GM el gesto de deslizar no parece funcionar. Todavía se puede acceder a editActions poniendo la vista de tabla en modo de edición. ¿Alguien más encuentra que deslizar no funciona?
Siegfoult
@Siegfoult ¿Has intentado implementar (incluso si se deja vacío) tableView: commitEditingStyle: forRowAtIndexPath :?
Johnny
No he trabajado en el objetivo c .. Mismo código que he escrito. por favor sugiera algunas pistas.
Solid Soft
@SolidSoft ¿Tiene un proyecto de ejemplo que podría mirar? Podría ayudarlo mejor de esa manera.
Johnny
3
Para responder mi propio comentario. Llama tableView.editing = false( NOen objc) y la celda se "cerrará".
Ben Lachman
121

He creado una nueva biblioteca para implementar botones intercambiables que admite una variedad de transiciones y botones expandibles como la aplicación de correo iOS 8.

https://github.com/MortimerGoro/MGSwipeTableCell

Esta biblioteca es compatible con todas las diferentes formas de crear un UITableViewCell y se ha probado en iOS 5, iOS 6, iOS 7 e iOS 8.

Aquí una muestra de algunas transiciones:

Transición fronteriza:

Transición fronteriza

Transición de clip

Transición de clip

Transición 3D:

ingrese la descripción de la imagen aquí

MortimerGoro
fuente
1
¡Buen trabajo! Sería increíble tener devoluciones de llamada para personalizar las animaciones.
Pacu
1
@MortimerGoro Buen hombre de trabajo. Se ve bien. Estoy tratando de implementar un efecto similar en uno de mis proyectos de Android. Por favor, dime cómo puedo lograr esto en Android.
Nitesh Kumar
en iOS 8 + iPad, simplemente no estoy logrando el deslizamiento.
ScorpionKing2k5
Esta es una biblioteca increíble y lo que es muy bueno es que todavía tiene soporte.
Confile
@MortimerGoro, probé con el marco "MGSwipeTableCel" l pero el problema es cuando vuelvo a cargar mi mesa y luego el botón deslizar está oculto. Cualquier solución para este problema.
Ganesh Guturi
71

La respuesta de Johnny es la correcta para votar. Solo estoy agregando esto a continuación en el objetivo-c para que sea más claro para los principiantes (y aquellos de nosotros que se niegan a aprender la sintaxis de Swift :)

Asegúrese de declarar el uitableviewdelegate y tenga los siguientes métodos:

 -(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
 UITableViewRowAction *button = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 1" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
    {
        NSLog(@"Action to perform with Button 1");
    }];
    button.backgroundColor = [UIColor greenColor]; //arbitrary color
    UITableViewRowAction *button2 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 2" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
                                    {
                                        NSLog(@"Action to perform with Button2!");
                                    }];
    button2.backgroundColor = [UIColor blueColor]; //arbitrary color

    return @[button, button2]; //array with all the buttons you want. 1,2,3, etc...
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// you need to implement this method too or nothing will work:

}
 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return YES; //tableview must be editable or nothing will work...
    }
timothykc
fuente
1
importante mencionar canEditRowAtIndexPath
Heckscheibe
Si recargo la tabla después de deslizar la celda, ¿esos botones de deslizamiento están visibles u ocultos?
Ganesh Guturi
25

Esta es (bastante ridícula) una API privada.

Los siguientes dos métodos son privados y se envían al delegado de UITableView:

-(NSString *)tableView:(UITableView *)tableView titleForSwipeAccessoryButtonForRowAtIndexPath:(NSIndexPath *)indexPath;
-(void)tableView:(UITableView *)tableView swipeAccessoryButtonPushedForRowAtIndexPath:(NSIndexPath *)indexPath;

Son bastante explicativos.

Jonathan
fuente
44
Apple ha abierto esta función con iOS 8. Vea la respuesta de Johnny a continuación.
Siegfoult
24

Para mejorar la respuesta de Johnny, esto ahora se puede hacer usando la API pública de la siguiente manera:

func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {

    let moreRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: "More", handler:{action, indexpath in
        print("MORE•ACTION");
    });
    moreRowAction.backgroundColor = UIColor(red: 0.298, green: 0.851, blue: 0.3922, alpha: 1.0);

    let deleteRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: "Delete", handler:{action, indexpath in
        print("DELETE•ACTION");
    });

    return [deleteRowAction, moreRowAction];
}
Arunabh Das
fuente
17

Espero que no puedas esperar hasta que Apple te dé lo que necesitas, ¿verdad? Así que aquí está mi opción.

Crea una celda personalizada. Tener dos uiviews en el

1. upper
2. lower

En la vista inferior, agregue los botones que necesite. Maneje sus acciones como cualquier otra IBActions. Puedes decidir el tiempo de animación, el estilo y cualquier cosa.

ahora agregue una uiswipegesture a la vista superior y revele su vista inferior en el gesto de deslizar. He hecho esto antes y es la opción más simple en lo que a mí respecta.

Espero que te sirva de ayuda.

Deepukjayan
fuente
7

Esto no es posible con el SDK estándar. Sin embargo, hay varias soluciones de terceros que imitan más o menos el comportamiento en Mail.app. Algunos de ellos (por ejemplo , MCSwipeTableViewCell , DAContextMenuTableViewController , RMSwipeTableViewCell ) detectan deslizamientos utilizando reconocedores de gestos, algunos de ellos (por ejemplo, SWTableViewCell ) colocan un segundo UISScrollView por debajo del estándar UITableViewCellScrollView(subvista privada de UITableViewCell) y algunos modifican el comportamiento deUITableViewCellScrollView .

Me gusta más el último enfoque ya que el manejo táctil se siente más natural. Específicamente, MSCMoreOptionTableViewCell es bueno. Su elección puede variar según sus necesidades específicas (si necesita una panorámica de izquierda a derecha, si necesita compatibilidad con iOS 6, etc.). También tenga en cuenta que la mayoría de estos enfoques conllevan una carga: pueden romperse fácilmente en una futura versión de iOS si Apple realiza cambios en la UITableViewCelljerarquía de subvista.

Ortwin Gentz
fuente
7

Código de versión de Swift 3 sin usar ninguna biblioteca:

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        tableView.tableFooterView = UIView(frame: CGRect.zero) //Hiding blank cells.
        tableView.separatorInset = UIEdgeInsets.zero
        tableView.dataSource = self
        tableView.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return 4
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)

        return cell
    }

    //Enable cell editing methods.
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {

        return true
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

    }

    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {

        let more = UITableViewRowAction(style: .normal, title: "More") { action, index in
            //self.isEditing = false
            print("more button tapped")
        }
        more.backgroundColor = UIColor.lightGray

        let favorite = UITableViewRowAction(style: .normal, title: "Favorite") { action, index in
            //self.isEditing = false
            print("favorite button tapped")
        }
        favorite.backgroundColor = UIColor.orange

        let share = UITableViewRowAction(style: .normal, title: "Share") { action, index in
            //self.isEditing = false
            print("share button tapped")
        }
        share.backgroundColor = UIColor.blue

        return [share, favorite, more]
    }

}
mriaz0011
fuente
6

Necesita subclase UITableViewCelly método de subclasewillTransitionToState:(UITableViewCellStateMask)state que se llama cada vez que el usuario desliza la celda. Las statebanderas le permitirán saber si se muestra el botón Eliminar y mostrar / ocultar su botón Más allí.

Lamentablemente, este método no le proporciona el ancho del botón Eliminar ni el tiempo de animación. Por lo tanto, debe observar y codificar el marco y el tiempo de animación de su botón Más en su código (personalmente creo que Apple necesita hacer algo al respecto).

Khanh Nguyen
fuente
77
"Personalmente, creo que Apple necesita hacer algo al respecto". Estoy de acuerdo. ¿Ya les escribió un informe de error / solicitud de función?
Tafkadasoh
4

Para una programación rápida

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
  if editingStyle == UITableViewCellEditingStyle.Delete {
    deleteModelAt(indexPath.row)
    self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
  }
  else if editingStyle == UITableViewCellEditingStyle.Insert {
    println("insert editing action")
  }
}

func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
  var archiveAction = UITableViewRowAction(style: .Default, title: "Archive",handler: { (action: UITableViewRowAction!, indexPath: NSIndexPath!) in
        // maybe show an action sheet with more options
        self.tableView.setEditing(false, animated: false)
      }
  )
  archiveAction.backgroundColor = UIColor.lightGrayColor()

  var deleteAction = UITableViewRowAction(style: .Normal, title: "Delete",
      handler: { (action: UITableViewRowAction!, indexPath: NSIndexPath!) in
        self.deleteModelAt(indexPath.row)
        self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic);
      }
  );
  deleteAction.backgroundColor = UIColor.redColor()

  return [deleteAction, archiveAction]
}

func deleteModelAt(index: Int) {
  //... delete logic for model
}
Michael Yagudaev
fuente
@bibscy le invitamos a sugerir una edición. No he usado Swift en mucho tiempo, así que no estoy seguro de cuál es la sintaxis correcta
Michael Yagudaev
3

ESTO PODRÍA AYUDARTE.

-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
 UITableViewRowAction *button = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 1" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
    {
        NSLog(@"Action to perform with Button 1");
    }];
    button.backgroundColor = [UIColor greenColor]; //arbitrary color
    UITableViewRowAction *button2 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 2" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
                                    {
                                        NSLog(@"Action to perform with Button2!");
                                    }];
    button2.backgroundColor = [UIColor blueColor]; //arbitrary color

    return @[button, button2]; //array with all the buttons you want. 1,2,3, etc...
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// you need to implement this method too or nothing will work:

}
 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return YES; //tableview must be editable or nothing will work...
    }
santoshIOS
fuente
3

Estaba buscando agregar la misma funcionalidad a mi aplicación, y después de leer tantos tutoriales diferentes ( raywenderlich es la mejor solución de bricolaje), descubrí que Apple tiene su propia UITableViewRowActionclase, lo cual es muy útil.

Debe cambiar el método boilerpoint de Tableview a esto:

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?  {
    // 1   
    var shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Share" , handler: { (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
    // 2
    let shareMenu = UIAlertController(title: nil, message: "Share using", preferredStyle: .ActionSheet)

    let twitterAction = UIAlertAction(title: "Twitter", style: UIAlertActionStyle.Default, handler: nil)
    let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)

    shareMenu.addAction(twitterAction)
    shareMenu.addAction(cancelAction)


    self.presentViewController(shareMenu, animated: true, completion: nil)
    })
    // 3
    var rateAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Rate" , handler: { (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
    // 4
    let rateMenu = UIAlertController(title: nil, message: "Rate this App", preferredStyle: .ActionSheet)

    let appRateAction = UIAlertAction(title: "Rate", style: UIAlertActionStyle.Default, handler: nil)
    let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)

    rateMenu.addAction(appRateAction)
    rateMenu.addAction(cancelAction)


    self.presentViewController(rateMenu, animated: true, completion: nil)
    })
    // 5
    return [shareAction,rateAction]
  }

Puede encontrar más información sobre esto en este sitio . La documentación de Apple es realmente útil para cambiar el color de fondo:

El color de fondo del botón de acción.

Declaración OBJETIVO-C @property (no atómico, copia) UIColor * backgroundColor Discussion Use esta propiedad para especificar el color de fondo para su botón. Si no especifica un valor para esta propiedad, UIKit asigna un color predeterminado basado en el valor de la propiedad de estilo.

Disponibilidad Disponible en iOS 8.0 y posterior.

Si desea cambiar la fuente del botón, es un poco más complicado. He visto otra entrada en SO. En aras de proporcionar el código y el enlace, aquí está el código que usaron allí. Tendría que cambiar la apariencia del botón. Tendría que hacer una referencia específica a tableviewcell, de lo contrario cambiaría la apariencia del botón en toda su aplicación (no quería eso, pero es posible que no lo sepa :))

C objetivo:

+ (void)setupDeleteRowActionStyleForUserCell {

    UIFont *font = [UIFont fontWithName:@"AvenirNext-Regular" size:19];

    NSDictionary *attributes = @{NSFontAttributeName: font,
                      NSForegroundColorAttributeName: [UIColor whiteColor]};

    NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString: @"DELETE"
                                                                          attributes: attributes];

    /*
     * We include UIView in the containment hierarchy because there is another button in UserCell that is a direct descendant of UserCell that we don't want this to affect.
     */
    [[UIButton appearanceWhenContainedIn:[UIView class], [UserCell class], nil] setAttributedTitle: attributedTitle
                                                                                          forState: UIControlStateNormal];
}

Rápido:

    //create your attributes however you want to
    let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(UIFont.systemFontSize())] as Dictionary!            

   //Add more view controller types in the []
    UIButton.appearanceWhenContainedInInstancesOfClasses([ViewController.self])

Esta es la versión más fácil y más optimizada en mi humilde opinión. Espero eso ayude.

Actualización: Aquí está la versión Swift 3.0:

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    var shareAction:UITableViewRowAction = UITableViewRowAction(style: .default, title: "Share", handler: {(action, cellIndexpath) -> Void in
        let shareMenu = UIAlertController(title: nil, message: "Share using", preferredStyle: .actionSheet)

        let twitterAction = UIAlertAction(title: "Twitter", style: .default, handler: nil)
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

        shareMenu.addAction(twitterAction)
        shareMenu.addAction(cancelAction)


        self.present(shareMenu,animated: true, completion: nil)
    })

    var rateAction:UITableViewRowAction = UITableViewRowAction(style: .default, title: "Rate" , handler: {(action, cellIndexpath) -> Void in
        // 4
        let rateMenu = UIAlertController(title: nil, message: "Rate this App", preferredStyle: .actionSheet)

        let appRateAction = UIAlertAction(title: "Rate", style: .default, handler: nil)
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

        rateMenu.addAction(appRateAction)
        rateMenu.addAction(cancelAction)


        self.present(rateMenu, animated: true, completion: nil)
    })
    // 5
    return [shareAction,rateAction]
}
Septronic
fuente
1
Gracias por su respuesta, estoy seguro de que ayudará a muchos desarrolladores. Sí, tiene razón, en realidad Apple proporciona esta solución desde iOS 8. Pero desafortunadamente esta solución nativa no proporciona la funcionalidad completa. Por ejemplo, en la aplicación Apple Mail tiene botones de dos lados (un botón del lado izquierdo y tres del lado derecho) con la API actual de Apple, no puede agregar botones a ambos lados, y la API actual no admite la acción predeterminada cuando el usuario desliza mucho tiempo a cada lado. La mejor solución por ahora en mi humilde opinión es el código abierto MGSwipeTableCell.
Guy Kahlon el
@GuyKahlon sí, tiene toda la razón en lo que respecta al problema de deslizamiento hacia la izquierda y hacia la derecha, y estoy de acuerdo en que para una mayor personalización, el MGSwipeTableCell es el mejor. La propia Apple no es la opción más sofisticada, pero la encontré más sencilla para tareas simples.
Septronic
@Septronic ¿Podría actualizar su código a Swift 3? shareMenu.No toma un addActionmétodo. Gracias
bibscy
@bibscy He agregado la versión rápida. ¿Necesita también el bit para el atributo? sharemenu es solo un UIAlertController, por lo que debe tomar la acción. Pruébalo y avísame si hay suerte :)
Septronic
3

Respuesta real de Swift 3

Esta es la ÚNICA función que necesita. No necesita las funciones CanEdit o CommitEditingStyle para acciones personalizadas.

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    let action1 = UITableViewRowAction(style: .default, title: "Action1", handler: {
        (action, indexPath) in
        print("Action1")
    })
    action1.backgroundColor = UIColor.lightGray
    let action2 = UITableViewRowAction(style: .default, title: "Action2", handler: {
        (action, indexPath) in
        print("Action2")
    })
    return [action1, action2]
}
William T.
fuente
3

A partir de iOS 11, esto está disponible públicamente en UITableViewDelegate. Aquí hay un código de muestra:

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
    UIContextualAction *delete = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
                                                                         title:@"DELETE"
                                                                       handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
                                                                           NSLog(@"index path of delete: %@", indexPath);
                                                                           completionHandler(YES);
                                                                       }];

    UIContextualAction *rename = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal
                                                                         title:@"RENAME"
                                                                       handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
                                                                           NSLog(@"index path of rename: %@", indexPath);
                                                                           completionHandler(YES);
                                                                       }];

    UISwipeActionsConfiguration *swipeActionConfig = [UISwipeActionsConfiguration configurationWithActions:@[rename, delete]];
    swipeActionConfig.performsFirstActionWithFullSwipe = NO;

    return swipeActionConfig;
}

También disponible:

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath;

Documentos: https://developer.apple.com/documentation/uikit/uitableviewdelegate/2902367-tableview?language=objc

Luis
fuente
3

Swift 4 y iOs 11+

@available(iOS 11.0, *)
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

    let delete = UIContextualAction(style: .destructive, title: "Delete") { _, _, handler in

        handler(true)
        // handle deletion here
    }

    let more = UIContextualAction(style: .normal, title: "More") { _, _, handler in

        handler(true)
        // handle more here
    }

    return UISwipeActionsConfiguration(actions: [delete, more])
}
Andrey
fuente
2

Solía tableViewCell para mostrar los datos múltiple, después de golpe () de derecha a izquierda en una celda se mostrará dos botones Aprobar y rechazan, hay dos métodos, el primero es ApproveFunc que toma un argumento, y el otro es RejectFunc que también toma un argumento

ingrese la descripción de la imagen aquí

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let Approve = UITableViewRowAction(style: .normal, title: "Approve") { action, index in

            self.ApproveFunc(indexPath: indexPath)
        }
        Approve.backgroundColor = .green

        let Reject = UITableViewRowAction(style: .normal, title: "Reject") { action, index in

            self.rejectFunc(indexPath: indexPath)
        }
        Reject.backgroundColor = .red



        return [Reject, Approve]
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    func ApproveFunc(indexPath: IndexPath) {
        print(indexPath.row)
    }
    func rejectFunc(indexPath: IndexPath) {
        print(indexPath.row)
    }
Akbar Khan
fuente
¿Puedes agregar alguna explicación a tu respuesta para que un lector pueda aprender de ella?
Nico Haase
Gracias por este fragmento de código, que puede proporcionar una ayuda limitada e inmediata. Una explicación adecuada mejoraría en gran medida su valor a largo plazo al mostrar por qué esta es una buena solución al problema y lo haría más útil para futuros lectores con otras preguntas similares. Por favor, editar su respuesta a añadir un poco de explicación, incluyendo los supuestos realizados.
Tim Diekmann
1

Aquí hay una forma algo frágil de hacerlo que no involucra API privadas o la construcción de su propio sistema. Estás cubriendo tus apuestas para que Apple no rompa esto y esperemos que publiquen una API con la que puedas reemplazar estas pocas líneas de código.

  1. KVO self.contentView.superview.layer.sublayer. Haz esto en init. Esta es la capa de UIScrollView. No puedes 'subvistas' de KVO.
  2. Cuando las subvistas cambien, busque la vista de confirmación de eliminación en scrollview.subviews. Esto se hace en la devolución de llamada de observación.
  3. Duplique el tamaño de esa vista y agregue un botón UIB a la izquierda de su única subvista. Esto también se hace en la devolución de llamada de observación. La única subvista de la vista de confirmación de eliminación es el botón Eliminar.
  4. (opcional) El evento UIButton debe buscar self.superview hasta que encuentre un UITableView y luego invoque un origen de datos o un método delegado que cree, como tableView: commitCustomEditingStyle: forRowAtIndexPath :. Puede encontrar el indexPath de la celda utilizando [tableView indexPathForCell: self].

Esto también requiere que implemente las devoluciones de llamada delegadas de edición de vista de tabla estándar.

static char kObserveContext = 0;

@implementation KZTableViewCell {
    UIScrollView *_contentScrollView;
    UIView *_confirmationView;
    UIButton *_editButton;
    UIButton *_deleteButton;
}

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        _contentScrollView = (id)self.contentView.superview;

        [_contentScrollView.layer addObserver:self
             forKeyPath:@"sublayers"
                options:0
                context:&kObserveContext];

        _editButton = [UIButton new];
        _editButton.backgroundColor = [UIColor lightGrayColor];
        [_editButton setTitle:@"Edit" forState:UIControlStateNormal];
        [_editButton addTarget:self
                        action:@selector(_editTap)
              forControlEvents:UIControlEventTouchUpInside];

    }
    return self;
}

-(void)dealloc {
    [_contentScrollView.layer removeObserver:self forKeyPath:@"sublayers" context:&kObserveContext];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if(context != &kObserveContext) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }
    if(object == _contentScrollView.layer) {
        for(UIView * view in _contentScrollView.subviews) {
            if([NSStringFromClass(view.class) hasSuffix:@"ConfirmationView"]) {
                _confirmationView = view;
                _deleteButton = [view.subviews objectAtIndex:0];
                CGRect frame = _confirmationView.frame;
                CGRect frame2 = frame;
                frame.origin.x -= frame.size.width;
                frame.size.width *= 2;
                _confirmationView.frame = frame;

                frame2.origin = CGPointZero;
                _editButton.frame = frame2;
                frame2.origin.x += frame2.size.width;
                _deleteButton.frame = frame2;
                [_confirmationView addSubview:_editButton];
                break;
            }
        }
        return;
    }
}

-(void)_editTap {
    UITableView *tv = (id)self.superview;
    while(tv && ![tv isKindOfClass:[UITableView class]]) {
        tv = (id)tv.superview;
    }
    id<UITableViewDelegate> delegate = tv.delegate;
    if([delegate respondsToSelector:@selector(tableView:editTappedForRowWithIndexPath:)]) {
        NSIndexPath *ip = [tv indexPathForCell:self];
        // define this in your own protocol
        [delegate tableView:tv editTappedForRowWithIndexPath:ip];
    }
}
@end
xtravar
fuente
Estoy muy contento si puede proporcionar un código de muestra, gracias
Guy Kahlon
Hecho. Puede tener un error o dos, pero entiendes la esencia.
xtravar
1

Hay una biblioteca increíble llamada SwipeCellKit, debería ganar más reconocimiento. En mi opinión, es más genial que MGSwipeTableCell. Este último no replica completamente el comportamiento de las celdas de la aplicación de correo, mientras que SwipeCellKitsí lo hace. Echar un vistazo

Andrey Chernukha
fuente
Probé SwipeCellKity me impresionó ... hasta que obtuve una de esas Excepciones porque el número de filas antes de una actualización de vista de tabla no era el mismo que después de la actualización +/- el cambio en las filas. La cuestión es que nunca cambié mi conjunto de datos. Entonces, si eso no es preocupante, no sé qué es. Así que decidí no usarlo y simplemente usé los nuevos métodos UITableViewDelegate. Si necesita más personalización, siempre se puede anularwillBeginEditingRowAt: ....
horseshoe7
@ horseshoe7 eso es raro. Nunca me he encontrado con ninguna excepción al usar SwipeCellKit. Después de todo, ¿qué tipo de relación puede tener una celda con una excepción que ocurre debido a cambios en el origen de datos?
Andrey Chernukha
1

Swift 4

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    let delete = UIContextualAction(style: .destructive, title: "Delete") { (action, sourceView, completionHandler) in
        print("index path of delete: \(indexPath)")
        completionHandler(true)
    }
    let rename = UIContextualAction(style: .normal, title: "Edit") { (action, sourceView, completionHandler) in
        print("index path of edit: \(indexPath)")
        completionHandler(true)
    }
    let swipeActionConfig = UISwipeActionsConfiguration(actions: [rename, delete])
    swipeActionConfig.performsFirstActionWithFullSwipe = false
    return swipeActionConfig
}
Vini App
fuente
¿Qué es la vista de origen en sus códigos? ¿Es icono o imagen?
Saeed Rahmatolahi
1
@SaeedRahmatolahi, sourceViewes "La vista en la que se mostró la acción". Para obtener más información, busque "UIContextualAction.Handler".
Mark Moeykens
0

Aquí hay una solución simple. Es capaz de mostrar y ocultar UIView personalizado dentro de UITableViewCell. La lógica de visualización está contenida dentro de la clase extendida desde UITableViewCell, BaseTableViewCell.

BaseTableViewCell.h

#import <UIKit/UIKit.h>

@interface BaseTableViewCell : UITableViewCell

@property(nonatomic,strong)UIView* customView;

-(void)showCustomView;

-(void)hideCustomView;

@end

BaseTableViewCell.M

#import "BaseTableViewCell.h"

@interface BaseTableViewCell()
{
    BOOL _isCustomViewVisible;
}

@end

@implementation BaseTableViewCell

- (void)awakeFromNib {
    // Initialization code
}

-(void)prepareForReuse
{
    self.customView = nil;
    _isCustomViewVisible = NO;
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

-(void)showCustomView
{
    if(nil != self.customView)
    {
        if(!_isCustomViewVisible)
        {
            _isCustomViewVisible = YES;

            if(!self.customView.superview)
            {
                CGRect frame = self.customView.frame;
                frame.origin.x = self.contentView.frame.size.width;
                self.customView.frame = frame;
                [self.customView willMoveToSuperview:self.contentView];
                [self.contentView addSubview:self.customView];
                [self.customView didMoveToSuperview];
            }

            __weak BaseTableViewCell* blockSelf = self;
            [UIView animateWithDuration:.5 animations:^(){

                for(UIView* view in blockSelf.contentView.subviews)
                {
                    CGRect frame = view.frame;
                    frame.origin.x = frame.origin.x - blockSelf.customView.frame.size.width;
                    view.frame = frame;
                }
            }];
        }
    }
}

-(void)hideCustomView
{
    if(nil != self.customView)
    {
        if(_isCustomViewVisible)
        {
            __weak BaseTableViewCell* blockSelf = self;
            _isCustomViewVisible = NO;
            [UIView animateWithDuration:.5 animations:^(){
                for(UIView* view in blockSelf.contentView.subviews)
                {
                    CGRect frame = view.frame;
                    frame.origin.x = frame.origin.x + blockSelf.customView.frame.size.width;
                    view.frame = frame;
                }
            }];
        }
    }
}

@end

Para obtener esta funcionalidad, simplemente extienda su celda de vista de tabla desde BaseTableViewCell.

A continuación, Inside UIViewController, que implementa UITableViewDelegate, crea dos reconocedores de gestos para manejar los deslizamientos izquierdo y derecho.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self.tableView registerNib:[UINib nibWithNibName:CUSTOM_CELL_NIB_NAME bundle:nil] forCellReuseIdentifier:CUSTOM_CELL_ID];

    UISwipeGestureRecognizer* leftSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleLeftSwipe:)];
    leftSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
    [self.tableView addGestureRecognizer:leftSwipeRecognizer];

    UISwipeGestureRecognizer* rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleRightSwipe:)];
    rightSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
    [self.tableView addGestureRecognizer:rightSwipeRecognizer];
}

Luego, agregue dos controladores deslizantes

- (void)handleLeftSwipe:(UISwipeGestureRecognizer*)recognizer
{
    CGPoint point = [recognizer locationInView:self.tableView];
    NSIndexPath* index = [self.tableView indexPathForRowAtPoint:point];

    UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:index];

    if([cell respondsToSelector:@selector(showCustomView)])
    {
        [cell performSelector:@selector(showCustomView)];
    }
}

- (void)handleRightSwipe:(UISwipeGestureRecognizer*)recognizer
{
    CGPoint point = [recognizer locationInView:self.tableView];
    NSIndexPath* index = [self.tableView indexPathForRowAtPoint:point];

    UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:index];

    if([cell respondsToSelector:@selector(hideCustomView)])
    {
        [cell performSelector:@selector(hideCustomView)];
    }
}

Ahora, dentro de cellForRowAtIndexPath, de UITableViewDelegate, puede crear UIView personalizado y adjuntarlo a la celda en desuso.

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CustomCellTableViewCell* cell = (CustomCellTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"CustomCellTableViewCell" forIndexPath:indexPath];

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"CellCustomView"
                                                      owner:nil
                                                    options:nil];

    CellCustomView* customView = (CellCustomView*)[ nibViews objectAtIndex: 0];

    cell.customView = customView;

    return cell;
}

Por supuesto, esta forma de cargar UIView personalizada es solo para este ejemplo. Adminístralo como quieras.

slobodans
fuente