在iOS 7的邮件应用中,有一种名为“滑动删除”和“更多”按钮的功能。

246
如何在表视图中实现“更多”按钮(就像iOS 7中的邮件应用程序一样),当用户滑动单元格时?
我一直在这里和Cocoa Touch论坛上寻找这些信息,但似乎找不到答案,希望有比我更聪明的人能给我一个解决方案。
当用户滑动表视图单元格时,我想显示不止一个编辑按钮(默认是删除按钮)。在iOS 7的邮件应用程序中,您可以向左滑动以删除,但会出现一个“更多”按钮。 enter image description here

3
看起来,这个苹果开发者论坛的帖子(https://devforums.apple.com/message/860459#860459)中有人已经自己构建了实现。您可以在GitHub上找到一个能够做到您想要的功能的项目:https://github.com/daria-kopaliani/DAContextMenuTableViewController - Guy Kahlon
8
@GuyKahlonMatrix,谢谢你提供的解决方案,它非常有效。这个问题在很多谷歌搜索结果中排名第一,人们不得不通过评论来交换知识,因为某些人认为关闭问题并传播民主思想更有帮助。显然,这个地方需要更好的管理者。 - Şafak Gezer
请查看此链接:http://www.teehanlax.com/blog/reproducing-the-ios-7-mail-apps-interface/,以及我使用自动布局实现的代码:https://github.com/Jafared/JASwipeCellExperiment。 - Alexis
2
如果你的目标是iOS 8,那么下面的答案就是你想要的。 - Johnny
显示剩余4条评论
20个回答

127

如何实现

看起来iOS 8开放了这个API。这种功能的提示在Beta 2中存在。

要使某些东西工作,需要在您的UITableView的委托上实现以下两种方法以获得所需的效果(请参见gist中的示例)。

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

已知问题

文档中说tableView:commitEditingStyle:forRowAtIndexPath方法不会被UITableViewRowAction调用,而是会调用action的处理程序。然而,没有这个方法就无法进行滑动操作。即使方法存根为空,现在仍需要它。这显然是beta 2版本中的一个bug。

来源

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

原始答案:https://stackoverflow.com/a/24540538/870028

更新:

此处提供可行的示例代码(使用Swift):http://dropbox.com/s/0fvxosft2mq2v5m/DeleteRowExampleSwift.zip

示例代码包含在MasterViewController.swift中易于跟随的方法,只需使用此方法即可获得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];
}

1
这似乎是正确的,但在Xcode 6 GM中,滑动手势似乎不起作用。可以通过将表视图置于编辑模式来访问editActions。还有其他人发现滑动无法正常工作吗? - Siegfoult
@Siegfoult 你尝试过实现(即使为空)tableView:commitEditingStyle:forRowAtIndexPath:吗? - Johnny
3
回答自己的评论。您可以调用 tableView.editing = false (NO in objc),这样单元格就会“关闭”。 - Ben Lachman
如何添加图标而不是文本?我不想删除文本,只想要一个删除图标。在Swift中如何实现这一点?请帮忙。 - Alok Nair
非常感谢。 :) 您的回复帮了我很多。 - Somu
显示剩余8条评论

120

我创建了一个新的库,用于实现可滑动按钮,支持多种过渡效果和可扩展的按钮,就像iOS 8邮件应用程序。

https://github.com/MortimerGoro/MGSwipeTableCell

该库与所有不同的UITableViewCell创建方式兼容,并已在iOS 5、iOS 6、iOS 7和iOS 8上进行了测试。

以下是一些转换示例:

边框转换:

边框转换

剪辑转换:

剪辑转换

3D转换:

3D转换


1
干得好!如果有回调来自定义动画就太棒了。 - Pacu
1
@MortimerGoro 做得好啊,看起来很不错。我正在尝试在我的一个Android项目中实现类似的效果,请告诉我如何在Android中实现这个效果? - Nitesh Kumar
在iOS 8+ iPad上,我无法进行滑动操作。 - ScorpionKing2k5
这是一个非常棒的库,而且它仍然得到支持,这非常好。 - Michael
@MortimerGoro,我尝试使用"MGSwipeTableCel"框架,但问题是当我重新加载表格时,滑动按钮会隐藏。有什么解决方法吗? - Ganesh G
这个设置还存在一些VoiceOver的问题。否则,它是一件了不起的作品! - Kane Cheshire

71

Johnny的回答是正确的,应该点赞。我在下面使用objective-c添加了一些内容,以使初学者更加清晰(还有那些拒绝学习Swift语法的人们):)

确保你声明了uitableviewdelegate并具备以下方法:

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

1
重要的是要提到 canEditRowAtIndexPath。 - Heckscheibe
如果我在滑动单元格后重新加载表格,那么那些滑动按钮是可见还是隐藏的? - Ganesh G

25

这是一个(相当荒谬的)私有 API。

以下两种方法是私有的,并发送给 UITableView 的代理:

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

它们相当容易理解。


4
苹果公司在iOS 8中推出了此功能。请参见约翰尼的答案。 - Siegfoult

24

为了改善Johnny的回答,现在可以按照以下方式使用公共API进行操作:

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

17

我希望你迫不及待地想要苹果公司提供你所需的一切,对吗?那么这是我的建议。

创建一个自定义单元格,在其中放置两个UIViews。

1. upper
2. lower

在低视图中,添加任何你需要的按钮。像处理其他IBAction一样处理它的操作。你可以决定动画时间、风格以及任何东西。

现在,在上视图中添加一个uiswipegesture,在滑动手势中显示你的下视图。我以前做过这个,就我而言,这是最简单的选项。

希望能有所帮助。


7
这是无法通过标准SDK实现的。但是,有各种第三方解决方案可以模仿Mail.app中的行为。其中一些(例如MCSwipeTableViewCellDAContextMenuTableViewControllerRMSwipeTableViewCell)使用手势识别器检测滑动操作,一些解决方案(例如SWTableViewCell)在标准的UITableViewCellScrollViewUITableViewCell的私有子视图)下方放置第二个UISScrollView,还有一些则修改了UITableViewCellScrollView的行为。
我最喜欢最后一种方法,因为触摸处理感觉最自然。具体来说,MSCMoreOptionTableViewCell是不错的选择。您的选择可能会根据您的具体需求而有所不同(是否需要从左到右滑动,是否需要iOS 6兼容性等)。此外,请注意,大多数这些解决方案都存在一个负面影响:如果苹果在UITableViewCell子视图层次结构中进行更改,则这些解决方案很容易在未来的iOS版本中出现故障。

7

不使用任何库的Swift 3版本代码:

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

}

6
你需要创建一个UITableViewCell的子类,并重写willTransitionToState:(UITableViewCellStateMask)state方法。每当用户滑动单元格时,该方法都会被调用。通过检查state标志,您可以知道是否正在显示删除按钮,并在那里显示/隐藏您的“更多”按钮。
不幸的是,这个方法既不提供删除按钮的宽度,也不提供动画时间。因此,你需要观察和硬编码你的“更多”按钮的框架和动画时间到你的代码中(我个人认为苹果应该对此做些改进)。

7
我认为苹果需要采取行动。我同意。你已经给他们写了一个漏洞报告/功能请求吗? - Tafkadasoh

5

从 iOS 11 开始,这个功能已经在 UITableViewDelegate 中公开。以下是一些示例代码:

Swift

func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

    let action = UIContextualAction(style: .normal, title: nil) { (_, _, _) in
        print("Swipe action tapped")
    }

    action.image = UIImage(systemName: "plus.slash.minus")
    action.backgroundColor = .green

    return UISwipeActionsConfiguration(actions: [action])
}

Objective C

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

还可用:

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接