Open UITableView edit action buttons programmatically
Apple has a private API that lets you do this, however, be warned that this may get your app rejected from the App Store unless you obfuscate the usage of said API using something like Method Swizzling. Here are the steps to do so:
Create a protocol called
PrivateMethodRevealer
that lets you access the required private Apple APIs, namely the ones to show and dismiss edit actions. Credits to this answer for providing this method of exposing private APIs. The methods in the protocol are declared asoptional
, so that in case Apple changes the name of the method, the app will not crash, but rather, it'll just not show the edit actions.@objc protocol PrivateMethodRevealer {
optional func setShowingDeleteConfirmation(arg1: Bool)
optional func _endSwipeToDeleteRowDidDelete(arg1: Bool)
}Note that although the methods refer to
delete
, this shows all theUITableViewRowAction
s that are on the cell.Create a function that handles the showing and hiding of the edit actions in your
UITableViewCell
subclass (if you have one), or create the method in aUITableViewCell
extension
. I will name this methodshowActions
for demonstrative purposes.Add the following body to your function:
func showActions() {
(superview?.superview as? AnyObject)?._endSwipeToDeleteRowDidDelete?(false)
(self as AnyObject).setShowingDeleteConfirmation?(true)
}This firstly dismisses any visible cells' editing actions, by calling
_endSwipeToDeleteRowDidDelete:
on theUITableView
(which is the cell's superview's superview), and then shows the cell's own editing actions (by callingsetShowingDeleteConfirmation:
). Note that we need to dismiss other cells' actions as showing multiple rows with edit actions is extremely buggy.If you want, you may also create a button in the
UIViewController
that dismisses any currently editing cells. To do this, just call the following method, wheretableView
is your reference to theUITableView
:(tableView as AnyObject)?._endSwipeToDeleteRowDidDelete?(false)
If the swipe gestures between your UIPageViewController
and UITableViewCell
s are conflicting, simply override the tableView:editingStyleForRowAtIndexPath:
method to return .None
.
In the end, your code might produce the following result
EDIT: Here is a quick way to hide the usage of your API using method swizzling. Credits to this website for providing the basic implementation of this method. Be warned that I can't guarantee that it'll work, as it isn't possible to test it live.
To do this, replace the protocols with the following code, and wherever you call setShowingDeleteConfirmation(true)
or _endSwipeToDeleteRowDidDelete(false)
, replace it with showRowActions()
and hideRowActions()
instead. This method appears to have some unintended effects however, such as the UITableViewCell
s not responding to user interaction whilst edit actions are visible.
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)
}
}
}
How to trigger UITableViewCell editActions programmatically?
Short answer is - there is no such way.
However, if you really need something like that, you can mimic this behaviour, though it requires lot more implementation and state handling on your own.
Here is a quick and very dirty solution, which overrides touchesEnded
method of your custom cell. Remember to set up Cell as a dynamic prototype of the cell in your table view in relevant storyboard and set its reuse identifier to identitifer
.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CellDelegate {
@IBOutlet weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "identifier") as? Cell else {
return UITableViewCell()
}
cell.textLabel?.text = "\(indexPath.row)"
cell.indexPath = indexPath
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
return nil
}
func doAction(for cell: Cell) {
let indexPath = cell.indexPath
print("doing action for cell at: \(indexPath!.row)")
// your implementation for action
// maybe delete a cell or whatever?
cell.hideFakeActions()
}
}
protocol CellDelegate: class {
func doAction(for cell: Cell)
}
class Cell: UITableViewCell {
weak var delegate: CellDelegate?
var indexPath: IndexPath!
@IBOutlet weak var buttonAction1: UIButton?
@IBOutlet weak var constraintButtonFakeActionWidth: NSLayoutConstraint?
override func awakeFromNib() {
self.constraintButtonFakeActionWidth?.constant = 0
}
override func touchesEnded(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else {
return
}
if self.point(inside: point, with: event) {
print("event is in cell number \(indexPath.row)")
self.showFakeActions()
}
}
func showFakeActions() {
self.constraintButtonFakeActionWidth?.constant = 80
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
func hideFakeActions() {
self.constraintButtonFakeActionWidth?.constant = 0
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
@IBAction func fakeAction() {
delegate?.doAction(for: self)
}
}
So how does it work? Each cell is a UIView
which inherits from abstract UIResponder
interface. You can override its methods to do actions on your own on behalf of events that are dispatched to them. This is the first step where you override touchesEnded
.
Take a look at the screenshot from interface builder - you have to hook up the constraint.
I've also implemented the delegate which returns nil for all edit actions of the cells, so they don't interfere with your workaround.
Next, you set up a custom delegate to get a callback from the cell. I also attached IndexPath to the cell for the convenience of managing data in the dataSource, which you have to implement.
Remember that this code lacks a lot, like prepareForReuse
method implementation. Also, you probably want to do additional checks in touchesEnded
override which would guarantee that this delegate callback is not fired more than once per touch and prevent multiple touches. The logic for disabling user interaction on a cell is not implemented here as well. And interface requires fine-tuning (like text appears to be squashed during the animation).
UITableViewCell Buttons with action
I was resolving this using a cell delegate method within UITableViewCell's subclass.
Quick overview:
1) Create a protocol
protocol YourCellDelegate : class {
func didPressButton(_ tag: Int)
}
2) Subclass your UITableViewCell
(if you haven't done so):
class YourCell : UITableViewCell
{
var cellDelegate: YourCellDelegate?
@IBOutlet weak var btn: UIButton!
// connect the button from your cell with this method
@IBAction func buttonPressed(_ sender: UIButton) {
cellDelegate?.didPressButton(sender.tag)
}
...
}
3) Let your view controller conform to YourCellDelegate
protocol that was implemented above.
class YourViewController: ..., YourCellDelegate { ... }
4) Set a delegate, after the cell has been defined (for reusing).
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! YourCell
cell.cellDelegate = self
cell.btn.tag = indexPath.row
5) In the same controller (where is your implemented UITableView delegate/datasource), put a method from YourCellDelegate
protocol.
func didPressButton(_ tag: Int) {
print("I have pressed a button with a tag: \(tag)")
}
Now, your solution is not tag / number dependent. You can add as many buttons as you want, so you are ready to get response via delegate regardless how many buttons you want to install.
This protocol-delegate solution is preferred in iOS logic and it can be used for other elements in table cell, like UISwitch
, UIStepper
, and so on.
Related Topics
Looping Through Nsattributedstring Attributes to Increase Font Size
iOS - How to Play a Video with Transparency
Remove Single Remote Notification from Notification Center
iOS Open Youtube App with Query (Url Schemes)
Ios: Frame.Size.Width/2 Doesn't Produce a Circle on Every Device
Send Messages Between iOS and Watchos with Watchconnectivity in Watchos2
How to Present a Uiviewcontroller from Skscene
iOS 7 Uiwebview Keyboard Issue
How to Debug "Terminated Due to Memory Error"
Uncaught Exception: This Class Is Not Key Value Coding-Compliant
Test Whether a Uiview Is in the Middle of Animation
Swift - Get Local Date and Time
Why Is -Diddeselectrowatindexpath Not Being Called
Gamecenter Authentication in Landscape-Only App Throws Uiapplicationinvalidinterfaceorientation
Xcode 10.2 with Swift 5.0 Compiler - Protocol Inheritance Issue
Rebuild an Nsarray by Grouping Objects That Have Matching Id Numbers