以编程方式打开 UITableView 编辑操作按钮

Posted

技术标签:

【中文标题】以编程方式打开 UITableView 编辑操作按钮【英文标题】:Open UITableView edit action buttons programmatically 【发布时间】:2015-07-21 15:48:15 【问题描述】:

我有一个UIPageViewController,里面有UITableViewControllers,并且向左滑动手势在UIPageViewController 之间发生冲突以在视图之间切换,而UITableViewCells 手势则用于打开编辑操作,所以我需要在单元格中单击某个按钮时显示编辑操作。

我的问题是我可以以编程方式显示编辑操作按钮,而不是在滑动手势上显示它们吗?

【问题讨论】:

我也遇到了同样的问题,请问您有解决方法吗?请与我们分享。 @eng_rawan 实际上没有这个问题仍然悬而未决。 您找到解决方案了吗? @Firas 据我所知,UITableView 没有公开任何方法来做到这一点,所以你最好的选择可能是只使用像 MGSwipeTableCell 这样的框架或遵循像 @ 这样的教程987654322@. 更新:我认为我已经找到了启动滑动的(私有)函数,我可能能够在几个我有时间的几个小时 【参考方案1】:

Apple 有一个私有 API 可以让您执行此操作,但是请注意,这可能会导致您的应用被 App Store 拒绝,除非您使用类似 Method Swizzling 之类的方法混淆了所述 API 的使用。以下是执行此操作的步骤:

    创建一个名为 PrivateMethodRevealer 的协议,让您可以访问所需的私有 Apple API,即显示和关闭编辑操作的那些。感谢 this answer 提供了这种公开私有 API 的方法。协议中的方法被声明为optional,因此如果Apple更改方法名称,应用程序不会崩溃,而是不会显示编辑操作。

    @objc protocol PrivateMethodRevealer 
        optional func setShowingDeleteConfirmation(arg1: Bool)
        optional func _endSwipeToDeleteRowDidDelete(arg1: Bool)
    
    

    请注意,虽然方法引用了delete,但这会显示单元格上的所有UITableViewRowAction

    UITableViewCell 子类(如果有的话)中创建一个函数来处理编辑操作的显示和隐藏,或者在UITableViewCell extension 中创建方法。我将此方法命名为showActions 以进行演示。

    将以下主体添加到您的函数中:

    func showActions() 
        (superview?.superview as? AnyObject)?._endSwipeToDeleteRowDidDelete?(false)
        (self as AnyObject).setShowingDeleteConfirmation?(true)
    
    

    这首先通过在UITableView(这是单元格的超级视图的超级视图)上调用_endSwipeToDeleteRowDidDelete: 来消除任何可见单元格的编辑操作,然后显示单元格自己的编辑操作(通过调用setShowingDeleteConfirmation:)。请注意,我们需要关闭其他单元格的操作,因为显示多行的编辑操作非常有问题。

    如果您愿意,您还可以在UIViewController 中创建一个按钮,用于关闭任何当前正在编辑的单元格。为此,只需调用以下方法,其中tableView 是您对UITableView 的引用:

    (tableView as AnyObject)?._endSwipeToDeleteRowDidDelete?(false)
    

如果您的UIPageViewControllerUITableViewCells 之间的滑动手势冲突,只需覆盖tableView:editingStyleForRowAtIndexPath: 方法以返回.None

最后,您的代码可能会产生以下结果

编辑:这是一种使用方法调配来隐藏 API 使用的快速方法。感谢 this website 提供此方法的基本实现。请注意,我不能保证它会起作用,因为无法对其进行现场测试。

为此,请将协议替换为以下代码,并且无论您调用setShowingDeleteConfirmation(true)_endSwipeToDeleteRowDidDelete(false),将其替换为showRowActions()hideRowActions()。然而,这种方法似乎有一些意想不到的效果,例如 UITableViewCells 在编辑操作可见时不响应用户交互。

extension UITableViewCell 
    func showRowActions(arg1: Bool = true) 

    public override static func initialize() 
        struct Static 
            static var token: dispatch_once_t = 0
        

        guard self === UITableViewCell.self else return

        dispatch_once(&Static.token) 
            let hiddenString = String(":noitamrifnoCeteleDgniwohStes".characters.reverse())
            let originalSelector = NSSelectorFromString(hiddenString)
            let swizzledSelector = #selector(showRowActions(_:))
            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
            class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
            method_exchangeImplementations(originalMethod, swizzledMethod)
        
    


extension UITableView 
    func hideRowActions(arg1: Bool = false) 

    public override static func initialize() 
        struct Static 
            static var token: dispatch_once_t = 0
        

        guard self === UITableView.self else return

        dispatch_once(&Static.token) 
            let hiddenString = String(":eteleDdiDwoReteleDoTepiwSdne_".characters.reverse())
            let originalSelector = NSSelectorFromString(hiddenString)
            let swizzledSelector = #selector(hideRowActions(_:))
            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
            class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
            method_exchangeImplementations(originalMethod, swizzledMethod)
        
    

【讨论】:

好答案,比我快一天。很好地参考了我的问题。我不建议使用NSObject/AnyObject 符合的一种全局协议。考虑两个单独的协议并使用unsafeBitCast,如my answer 所示。 @JAL 虽然使用一种协议看起来很危险,但optional 协议的美妙之处在于,如果对象不符合协议,它就会默默地失败。这也可能发生在方法名称被更改的情况下,因此它用一块石头杀死了两只鸟(即您不需要调用respondsToSelector:)。 @JAL,如果您看到我链接的答案,它说明unsafeBitCasts 本质上是不安全。如果由于某种原因该方法不存在,应用程序将崩溃。 我当然看了答案,这是我的问题。是的,所有这些都是不安全的,但我们知道这些方法是由类实现的,所以unsafeBitCast 没有任何危害。如果您想更安全,请使用可选协议并检查选择器。对于小型原型设计,unsafeBitCast 就足够了。 @JAL 哦,对不起,我不知何故错过了您提出链接问题的事实 xD【参考方案2】:

我与 kabiroberai 走在同一条道路上寻找这个答案的解决方案,但采取了不同的方法,使用两个独立的协议,而不是一个可能被滥用的 Objective-C/NSObject 协议。这也避免了将协议方法设为可选。

首先,创建两个单独的协议以公开UITableViewUITableViewCell 上的私有方法。我通过挖掘每个类的private headers 找到了这些。

@objc protocol UITableViewCellPrivate 
    func setShowingDeleteConfirmation(arg1: Bool)


@objc protocol UITableViewPrivate 
    func _endSwipeToDeleteRowDidDelete(arg1: Bool)

cellForRowAtIndexPath 中,保留对要显示其编辑操作的单元格(或多个单元格)的引用:

class MyTableViewController: UITableViewController 

    var cell: UITableViewCell?

    // ...

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell")!

       // ...        

        if indexPath.row == 1 
            self.cell = cell
        

        return cell
    

现在,触发私有方法。我用performSelector:withObject:afterDelay,或者你可以用一个按钮。

override func viewDidLoad() 
    super.viewDidLoad()    
    self.performSelector(#selector(showActionsForCell), withObject: nil, afterDelay: 2.0)


func showActionsForCell() 

    if let cell = cell 
        let cellPrivate = unsafeBitCast(cell, UITableViewCellPrivate.self)
        let tableViewPrivate = unsafeBitCast(self.tableView, UITableViewPrivate.self)

        // Dismiss any other edit actions that are open
        tableViewPrivate._endSwipeToDeleteRowDidDelete(false)

        // Open the edit actions for the selected cell
        cellPrivate.setShowingDeleteConfirmation(true)
    

直接拨打unsafeBitCast 很危险。为安全起见,请检查您的 UITableViewUITableViewCell 是否响应这些选择器,或将这些功能设为可选。

【讨论】:

我很确定_endSwipeToDeleteRowDidDelete: 中的参数是指该行是否被删除,而不是它是否应该结束滑动。 在 Xcode 9 / ios 10 中不适合我,你能确认它适合你吗?【参考方案3】:

在我的情况下(swift 3,iOS11)MGSwipeTableCell 完美。您可以在

中配置通话按钮
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: prettyIdentifier, for: indexPath)

    cell.allowsButtonsWithDifferentWidth = true
    cell.rightButtons = [MGSwipeButton(title: "Delete\npermanently", backgroundColor: #colorLiteral(red: 0.9745360017, green: 0.7205639482, blue: 0.3932176828, alpha: 1)), MGSwipeButton(title: "Undo",backgroundColor: .black)]
    cell.rightSwipeSettings.transition = .rotate3D
    cell.delegate = self

    return cell

而不是

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

并在

extension RecordingViewController: MGSwipeTableCellDelegate 
    func swipeTableCell(_ cell: MGSwipeTableCell, tappedButtonAt index: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool 
        cell.hideSwipe(animated: true)
        // do your stuff here like
        if index == 0 
            print("right button")
        

        return true
    

【讨论】:

【参考方案4】:

使用IBAction或其他方法设置表格视图编辑:

@IBAction func editOn(sender: UIBarButtonItem) 
    self.tableView.setEditing(true, animated: true)

您可以使用 UITableViewController 中的方法:

// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool 
    // Return false if you do not want the specified item to be editable.
    return true

这个方法是编辑的动作:

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? 
    let action = UITableViewRowAction(style: .Default, title: "Delete")  (rowAction: UITableViewRowAction, indexPAth: NSIndexPath) in
        //code for delete
        print("Delete")
    
    let action2 = UITableViewRowAction(style: .Normal, title: "Share")  (rowAction: UITableViewRowAction, indexPAth: NSIndexPath) in
        //code for share
        print("Share")
    
    return [action, action2]

希望对你有帮助。

【讨论】:

这会添加动作,但不显示如何以编程方式使它们可见。【参考方案5】:

在您的按钮点击事件中,请您尝试以下功能。

func setEditing(_ editing: Bool,
   animated animated: Bool)

这是函数在 swift 语法中。

根据Apple developer support 网站,它将执行以下操作,

当您调用此方法并将editing的值设置为true时,并且 UITableViewCell 对象被配置为具有控件,单元格 显示插入(绿色加号)或删除控制(红色减号) 每个单元格的左侧和右侧的重新排序控件。 此方法在每个可见单元格上调用,当 setEditing:animated: UITableView 的方法被调用。调用这个 编辑设置为 false 的方法从单元格中删除控件。

希望这能回答问题。

【讨论】:

我确实尝试过该功能,但不幸的是它不起作用。

以上是关于以编程方式打开 UITableView 编辑操作按钮的主要内容,如果未能解决你的问题,请参考以下文章

UITableView以编程方式调用滑动操作

在 UITableViewController 中以编程方式切换 UITableView

以编程方式编辑 UITableView 在所有行上显示删除按钮

以编程方式开始在另一个表格视图单元格中编辑 UITextField

以编程方式创建 UITableView

在 UITableView 重新加载时以编程方式打开/关闭 UITextField 的 inputView