Faites glisser vers Supprimer et le bouton "Plus" (comme dans l'application Mail sur iOS 7)

246

Comment créer un bouton "plus" lorsque l'utilisateur glisse une cellule en mode tableau (comme l'application de messagerie dans iOS 7)

J'ai cherché ces informations ici et sur le forum Cocoa Touch, mais je n'arrive pas à trouver la réponse et j'espère que quelqu'un plus intelligent que moi pourra me donner une solution.

J'aimerais que lorsque l'utilisateur glisse une cellule de vue de table, pour afficher plus d'un bouton d'édition (il est par défaut le bouton Supprimer). Dans l'application Mail pour iOS 7, vous pouvez glisser pour supprimer, mais il y a un bouton "PLUS" qui apparaît.

entrez la description de l'image ici

Guy Kahlon
la source
Pour ajouter le bouton "Supprimer", j'implémente les deux fonctions suivantes. - (BOOL) tableView: (UITableView *) tableView canEditRowAtIndexPath: (NSIndexPath *) indexPath; - (void) tableView: (UITableView *) tableView commitEditingStyle: (UITableViewCellEditingStyle) editStyle forRowAtIndexPath: (NSIndexPath *) indexPath; Et je veux ajouter le bouton "Plus" à côté.
Guy Kahlon
3
@MonishBansal Bansal On dirait que quelqu'un dans ce fil ( devforums.apple.com/message/860459#860459 dans le forum des développeurs Apple) est allé de l'avant et a construit sa propre implémentation. Vous pouvez trouver un projet qui fait ce que vous voulez sur GitHub: github.com/daria-kopaliani/DAContextMenuTableViewController
Guy Kahlon
8
@GuyKahlonMatrix merci pour la solution, cela fonctionne comme un charme. Cette question est le résultat n ° 1 de nombreuses recherches sur Google, et les gens sont obligés d'échanger leurs connaissances en utilisant les commentaires parce qu'un gars a décidé qu'il était plus utile de fermer la question et de prêcher la démocratie à la place. Cet endroit a clairement besoin de meilleurs mods.
Şafak Gezer
2
Si vous pouvez cibler iOS 8, ma réponse ci-dessous sera ce que vous voulez.
Johnny

Réponses:

126

Comment mettre en œuvre

Il semble que iOS 8 ouvre cette API. Des indices d'une telle fonctionnalité sont présents dans la version bêta 2.

Pour que quelque chose fonctionne, implémentez les deux méthodes suivantes sur le délégué de votre UITableView pour obtenir l'effet souhaité (voir gist pour un exemple).

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


Problèmes connus

La documentation indique que tableView: commitEditingStyle: forRowAtIndexPath est:

"Non appelé pour les actions de modification à l'aide de UITableViewRowAction - le gestionnaire de l'action sera appelé à la place."

Cependant, le balayage ne fonctionne pas sans lui. Même si le stub de la méthode est vide, il en a toujours besoin, pour l'instant. Il s'agit bien évidemment d'un bug dans la bêta 2.


Sources

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

Réponse originale: https://stackoverflow.com/a/24540538/870028


Mettre à jour:

Exemple de code avec ce fonctionnement (dans Swift): http://dropbox.com/s/0fvxosft2mq2v5m/DeleteRowExampleSwift.zip

L'exemple de code contient cette méthode facile à suivre dans MasterViewController.swift, et avec juste cette méthode, vous obtenez le comportement indiqué dans la capture d'écran 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
la source
1
Cela semble être correct, mais dans Xcode 6 GM, le geste de balayage ne semble pas fonctionner. Les actions d'édition sont toujours accessibles en mettant la vue de table en mode édition. Quelqu'un d'autre trouve que le balayage ne fonctionne pas?
Siegfoult
@Siegfoult Avez-vous essayé d'implémenter (même s'il est vide) tableView: commitEditingStyle: forRowAtIndexPath :?
Johnny
Je n'ai pas travaillé dans l'objectif c .. Même code que j'ai écrit. veuillez suggérer quelques indices.
Solid Soft
@SolidSoft Avez-vous un exemple de projet que je pourrais regarder? Je pourrais peut-être mieux aider de cette façon.
Johnny
3
Pour répondre à mon propre commentaire. Vous appelez tableView.editing = false( NOen objc) et la cellule "se fermera".
Ben Lachman
121

J'ai créé une nouvelle bibliothèque pour implémenter des boutons swippables qui prennent en charge une variété de transitions et de boutons extensibles comme l'application de messagerie iOS 8.

https://github.com/MortimerGoro/MGSwipeTableCell

Cette bibliothèque est compatible avec toutes les différentes façons de créer un UITableViewCell et son testé sur iOS 5, iOS 6, iOS 7 et iOS 8.

Voici un échantillon de quelques transitions:

Transition frontalière:

Transition frontalière

Transition de clip

Transition de clip

Transition 3D:

entrez la description de l'image ici

MortimerGoro
la source
1
Bon travail! Ce serait génial d'avoir des rappels pour personnaliser les animations.
Pacu
1
@MortimerGoro Nice work man. Ça à l'air bon. J'essaie d'implémenter un effet similaire dans l'un de mes projets Android. Veuillez me dire comment puis-je y parvenir dans Android?
Nitesh Kumar
sur iOS 8 + iPad, je n'arrive tout simplement pas à faire glisser.
ScorpionKing2k5
C'est une bibliothèque incroyable et ce qui est très bien, c'est qu'elle a toujours un support.
confile le
@MortimerGoro, j'ai essayé avec le framework "MGSwipeTableCel" mais le problème est quand je recharge ma table puis que le bouton de glisser est caché. Tout contournement pour ce problème.
Ganesh Guturi
71

La réponse de Johnny est la bonne à voter. J'ajoute juste ceci ci-dessous dans objective-c pour le rendre plus clair pour les débutants (et ceux d'entre nous qui refusent d'apprendre la syntaxe Swift :)

Assurez-vous de déclarer uitableviewdelegate et disposez des méthodes suivantes:

 -(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
la source
1
important de mentionner canEditRowAtIndexPath
Heckscheibe
Si je recharge le tableau après avoir glissé la cellule, ces boutons de balayage sont-ils visibles ou masqués?
Ganesh Guturi
25

Il s'agit (plutôt ridiculement) d'une API privée.

Les deux méthodes suivantes sont privées et envoyées au délégué de UITableView:

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

Ils sont assez explicites.

Jonathan.
la source
4
Apple a ouvert cette fonctionnalité avec iOS 8. Voir la réponse de Johnny ci-dessous.
Siegfoult
24

Pour améliorer la réponse de Johnny, cela peut maintenant être fait en utilisant l'API publique comme suit:

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
la source
17

J'espère que vous ne pouvez pas attendre que Apple vous donne ce dont vous avez besoin, non? Voici donc mon option.

Créez une cellule personnalisée. Avoir deux uiviews dedans

1. upper
2. lower

Dans la vue inférieure, ajoutez les boutons dont vous avez besoin. Traitez ses actions comme n'importe quelle autre IBActions. vous pouvez décider de la durée de l'animation, du style et de tout.

ajoutez maintenant une uiswipegesture à la vue supérieure et révélez votre vue inférieure sur le geste de balayage. Je l'ai déjà fait auparavant et c'est l'option la plus simple en ce qui me concerne.

J'espère que cette aide.

Deepukjayan
la source
7

Cela n'est pas possible avec le SDK standard. Cependant, il existe différentes solutions tierces qui imitent plus ou moins le comportement dans Mail.app. Certains d'entre eux (par exemple MCSwipeTableViewCell , DAContextMenuTableViewController , RMSwipeTableViewCell ) détectent les balayages à l'aide de reconnaissance de gestes, certains d'entre eux (par exemple SWTableViewCell ) placent un deuxième UISScrollView en dessous de la norme UITableViewCellScrollView(sous-vue privée de UITableViewCell) et certains d'entre eux modifient le comportement de UITableViewCellScrollView.

J'aime la dernière approche, car la manipulation tactile est la plus naturelle. Plus précisément, MSCMoreOptionTableViewCell est bon. Votre choix peut varier en fonction de vos besoins spécifiques (si vous avez également besoin d'un panoramique de gauche à droite, si vous avez besoin de la compatibilité iOS 6, etc.). Sachez également que la plupart de ces approches sont lourdes: elles peuvent facilement s'introduire dans une future version iOS si Apple apporte des modifications dans la UITableViewCellhiérarchie des sous- vues.

Ortwin Gentz
la source
7

Code de version Swift 3 sans utiliser de bibliothèque:

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
la source
6

Vous devez sous UITableViewCell- willTransitionToState:(UITableViewCellStateMask)stateclasser et la méthode de sous -classe qui est appelée chaque fois que l'utilisateur glisse la cellule. lestate drapeaux vous indiqueront si le bouton Supprimer s'affiche et afficheront / masqueront votre bouton Plus.

Malheureusement, cette méthode ne vous donne ni la largeur du bouton Supprimer ni la durée de l'animation. Vous devez donc observer et coder en dur le temps de trame et d'animation de votre bouton Plus dans votre code (je pense personnellement qu'Apple doit faire quelque chose).

Khanh Nguyen
la source
7
"Je pense personnellement qu'Apple doit faire quelque chose". Je suis d'accord. Leur avez-vous déjà écrit un rapport de bug / demande de fonctionnalité?
Tafkadasoh
4

Pour une programmation rapide

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
la source
@bibscy vous êtes invités à suggérer une modification. Je n'ai pas utilisé Swift depuis longtemps, donc
je
3

CELA POURRAIT VOUS AIDER.

-(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
la source
3

Je cherchais à ajouter la même fonctionnalité à mon application, et après avoir parcouru tant de didacticiels différents ( raywenderlich étant la meilleure solution de bricolage), j'ai découvert qu'Apple avait sa propre UITableViewRowActionclasse, ce qui est très pratique.

Vous devez changer la méthode de point de chaudière de Tableview en ceci:

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]
  }

Vous pouvez en savoir plus sur ce site . La propre documentation d' Apple est vraiment utile pour changer la couleur d'arrière-plan:

La couleur d'arrière-plan du bouton d'action.

Déclaration OBJECTIVE-C @property (nonatomique, copie) UIColor * backgroundColor Discussion Utilisez cette propriété pour spécifier la couleur d'arrière-plan de votre bouton. Si vous ne spécifiez pas de valeur pour cette propriété, UIKit attribue une couleur par défaut en fonction de la valeur dans la propriété de style.

Disponibilité Disponible dans iOS 8.0 et versions ultérieures.

Si vous voulez changer la police du bouton, c'est un peu plus délicat. J'ai vu un autre post sur SO. Pour fournir le code ainsi que le lien, voici le code qu'ils ont utilisé là-bas. Vous devez changer l'apparence du bouton. Vous devriez faire une référence spécifique à tableviewcell, sinon vous changeriez l'apparence du bouton dans votre application (je ne le voulais pas, mais vous pourriez, je ne sais pas :))

Objectif c:

+ (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];
}

Rapide:

    //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])

Ceci est la version IMHO la plus simple et la plus rationalisée. J'espère que ça aide.

Mise à jour: voici la version 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
la source
1
Merci pour votre réponse, je suis sûr que cela aidera de nombreux développeurs. Oui, vous avez raison, Apple fournit en fait cette solution à partir d'iOS 8. Mais malheureusement, cette solution native ne fournit pas toutes les fonctionnalités. Par exemple, dans l'application Mail d'Apple, vous avez des boutons des deux côtés (un bouton du côté gauche et trois du côté droit) avec l'API actuelle d'Apple, vous ne pouvez pas ajouter de boutons des deux côtés, et l'API actuelle ne prend pas en charge l'action par défaut lorsque l'utilisateur glisse longtemps de chaque côté. La meilleure solution pour l'instant à mon humble avis est le MGSwipeTableCell open source.
Guy Kahlon
@GuyKahlon oui, vous avez absolument raison en ce qui concerne le problème de balayage gauche et droit, et je suis d'accord pour que pour plus de personnalisation, le MGSwipeTableCell est le meilleur. La propre d'Apple n'est pas l'option la plus sophistiquée, mais je l'ai trouvée la plus simple pour les tâches simples.
Septronic
@Septronic Pourriez-vous mettre à jour votre code vers Swift 3? shareMenu.ne prend pas de addActionméthode. Merci
bibscy
@bibscy J'ai ajouté la version rapide. Avez-vous également besoin du bit pour l'attribut? sharemenu est juste un UIAlertController, il doit donc agir. Essayez-le et faites-moi savoir si vous avez de la chance :)
Septronic
3

Actual Swift 3 Answer

C'est la SEULE fonction dont vous avez besoin. Vous n'avez pas besoin des fonctions CanEdit ou CommitEditingStyle pour les actions personnalisées.

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.
la source
3

Depuis iOS 11, cela est accessible au public dans UITableViewDelegate . Voici un exemple de code:

- (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;
}

Aussi disponible:

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

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

lewis
la source
3

Swift 4 et 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
la source
2

J'ai utilisé tableViewCell pour afficher plusieurs données, après avoir glissé () de droite à gauche sur une cellule, il affichera deux boutons Approuver et rejeter, il existe deux méthodes, la première est ApproveFunc qui prend un argument et l'autre est RejectFunc qui a également prend un argument.

entrez la description de l'image ici

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
la source
Pouvez-vous ajouter quelques explications à votre réponse afin qu'un lecteur puisse en tirer des leçons?
Nico Haase
Merci pour cet extrait de code, qui pourrait fournir une aide limitée et immédiate. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez faites.
Tim Diekmann
1

Voici une façon quelque peu fragile de le faire qui n'implique pas d'API privées ni de construction de votre propre système. Vous couvrez vos paris qu'Apple ne casse pas et que, espérons-le, ils publieront une API avec laquelle vous pourrez remplacer ces quelques lignes de code.

  1. KVO self.contentView.superview.layer.sublayer. Faites-le en init. Il s'agit de la couche UIScrollView. Vous ne pouvez pas "sous-visualiser" KVO.
  2. Lorsque les sous-vues changent, recherchez la vue de confirmation de suppression dans scrollview.subviews. Cela se fait dans le rappel d'observation.
  3. Doublez la taille de cette vue et ajoutez un UIButton à gauche de sa seule sous-vue. Cela se fait également dans le rappel d'observation. La seule sous-vue de la vue de confirmation de suppression est le bouton Supprimer.
  4. (facultatif) L'événement UIButton doit rechercher self.superview jusqu'à ce qu'il trouve une UITableView, puis appeler une source de données ou une méthode déléguée que vous créez, telle que tableView: commitCustomEditingStyle: forRowAtIndexPath :. Vous pouvez trouver l'indexPath de la cellule en utilisant [tableView indexPathForCell: self].

Cela nécessite également que vous implémentiez les rappels de délégué d'édition de vue de table standard.

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
la source
Je suis vraiment heureux si vous pouvez fournir un exemple de code, merci
Guy Kahlon
Terminé. Il peut y avoir un bug ou deux, mais vous obtenez l'essentiel.
xtravar
1

Il existe une bibliothèque étonnante appelée SwipeCellKit, elle devrait gagner en reconnaissance. À mon avis, il fait plus frais que MGSwipeTableCell. Ce dernier ne reproduit pas complètement le comportement des cellules de l'application Mail alors qu'il le SwipeCellKitfait. Regarde

Andrey Chernukha
la source
J'ai essayé SwipeCellKitet j'ai été impressionné ... jusqu'à ce que j'obtienne l'une de ces exceptions, car le nombre de lignes avant une mise à jour de la vue de table n'était pas le même qu'après la mise à jour +/- le changement de lignes. Le fait est que je n'ai jamais changé mon jeu de données. Donc, si ce n'est pas inquiétant, je ne sais pas ce que c'est. J'ai donc décidé de ne pas l'utiliser et j'ai simplement utilisé les nouvelles méthodes UITableViewDelegate. Si vous avez besoin de plus de personnalisation, vous pouvez toujours passer outrewillBeginEditingRowAt: ....
horseshoe7
@ horseshoe7 c'est bizarre. Je n'ai jamais rencontré d'exception lors de l'utilisation de SwipeCellKit. Après tout, quel type de relation une cellule peut-elle avoir avec une telle exception qui se produit en raison des changements de source de données?
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
la source
qu'est-ce que la vue source dans vos codes? est-ce une icône ou une image?
Saeed Rahmatolahi
1
@SaeedRahmatolahi, sourceViewest "La vue dans laquelle l'action a été affichée." Pour plus d'informations, recherchez «UIContextualAction.Handler».
Mark Moeykens,
0

Voici une solution simple. Il est capable d'afficher et de masquer UIView personnalisé dans UITableViewCell. La logique d'affichage est contenue dans une classe étendue à partir de 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

Pour obtenir cette fonctionnalité, étendez simplement votre cellule de vue de table à partir de BaseTableViewCell.

Ensuite, Inside UIViewController, qui implémente UITableViewDelegate, crée deux reconnaisseurs de gestes pour gérer les balayages gauche et droit.

- (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];
}

Ensuite, ajoutez deux gestionnaires de balayage

- (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)];
    }
}

Maintenant, à l'intérieur de cellForRowAtIndexPath, de UITableViewDelegate, vous pouvez créer une UIView personnalisée et l'attacher à la cellule retirée de la file d'attente.

-(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;
}

Bien sûr, cette façon de charger UIView personnalisé est juste pour cet exemple. Gérez-le comme vous le souhaitez.

slobodans
la source