J'ai une UIPageViewController
qui a UITableViewControllers
à l'intérieur, et les gestes de glissement vers la gauche sont en conflit entre la UIPageViewController
pour changer entre les vues et le geste UITableViewCells
pour ouvrir les actions d'édition. Je dois donc afficher les actions d'édition lorsqu'un clic est effectué sur un bouton donné. la cellule.
Ma question est la suivante: puis-je afficher les boutons d’action de montage par programmation au lieu de les afficher sur le geste de balayage?
Apple dispose d'une API privée qui vous permet de le faire, mais sachez que votre application peut être rejetée de l'App Store à moins que vous ne dissimuliez l'utilisation de cette API en utilisant quelque chose comme Method Swizzling. Voici les étapes à suivre:
Créez un protocole appelé PrivateMethodRevealer
qui vous permet d'accéder aux API Apple privées requises, à savoir celles permettant d'afficher et de rejeter les actions de montage. Crédits à cette réponse pour avoir fourni cette méthode d’exposition d’API privées. Les méthodes du protocole sont déclarées en tant que optional
, de sorte que si Apple change le nom de la méthode, l'application ne se plante pas, mais ne montrera simplement pas les actions d'édition.
@objc protocol PrivateMethodRevealer {
optional func setShowingDeleteConfirmation(arg1: Bool)
optional func _endSwipeToDeleteRowDidDelete(arg1: Bool)
}
Notez que bien que les méthodes se réfèrent à delete
, cela affiche toutes les UITableViewRowAction
s qui sont sur la cellule.
Créez une fonction qui gère l'affichage et le masquage des actions d'édition dans votre sous-classe UITableViewCell
(si vous en avez une) ou créez la méthode dans une UITableViewCell
extension
. Je nommerai cette méthode showActions
à des fins de démonstration.
Ajoutez le corps suivant à votre fonction:
func showActions() {
(superview?.superview as? AnyObject)?._endSwipeToDeleteRowDidDelete?(false)
(self as AnyObject).setShowingDeleteConfirmation?(true)
}
Cela supprime tout d'abord les actions d'édition des cellules visibles, en appelant _endSwipeToDeleteRowDidDelete:
sur la UITableView
(qui est la vue du dessus de la cellule), puis affiche ses propres actions d'édition (en appelant setShowingDeleteConfirmation:
). Notez que nous devons ignorer les actions d'autres cellules car l'affichage de plusieurs lignes avec des actions d'édition est extrêmement bogué.
Si vous le souhaitez, vous pouvez également créer un bouton dans la variable UIViewController
qui supprime les cellules en cours d’édition. Pour ce faire, appelez simplement la méthode suivante, où tableView
est votre référence à la UITableView
:
(tableView as AnyObject)?._endSwipeToDeleteRowDidDelete?(false)
Si les mouvements de balayage entre vos UIPageViewController
et UITableViewCell
s sont en conflit, substituez simplement la méthode tableView:editingStyleForRowAtIndexPath:
pour renvoyer .None
.
En fin de compte, votre code peut produire le résultat suivant
EDIT: Voici un moyen rapide de masquer l'utilisation de votre API à l'aide de la méthode swizzling. Crédits à ce site Web pour la mise en œuvre de base de cette méthode. Soyez averti que je ne peux pas garantir que cela fonctionnera, car il n'est pas possible de le tester en direct.
Pour ce faire, remplacez les protocoles par le code suivant et remplacez-le chaque fois que vous appelez setShowingDeleteConfirmation(true)
ou _endSwipeToDeleteRowDidDelete(false)
par showRowActions()
et hideRowActions()
. Cette méthode semble toutefois avoir des effets inattendus, tels que les UITableViewCell
s ne répondant pas à l’interaction de l’utilisateur alors que les actions d’édition sont visibles.
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)
}
}
}
Dans mon cas (Swift 3, iOS11) MGSwipeTableCell fonctionne parfaitement. Vous pouvez configurer les boutons d'appel dans
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
}
au lieu de
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {...}
et attraper des touches dans
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
}
}
J'étais sur le même chemin que kabiroberai pour trouver une solution à cette réponse, mais j'ai adopté une approche différente avec deux protocoles distincts plutôt qu'un protocole Objective-C/NSObject potentiellement abusif. Cela évite également de rendre les méthodes de protocole facultatives.
Commencez par créer deux protocoles distincts pour exposer les méthodes privées à la fois sur UITableView
et UITableViewCell
. J'ai trouvé ceux-ci en fouillant dans les en-têtes privés de chaque classe.
@objc protocol UITableViewCellPrivate {
func setShowingDeleteConfirmation(arg1: Bool)
}
@objc protocol UITableViewPrivate {
func _endSwipeToDeleteRowDidDelete(arg1: Bool)
}
Dans cellForRowAtIndexPath
, conservez une référence à la cellule (ou aux plusieurs cellules) pour laquelle vous souhaitez afficher les actions de modification pour:
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
}
}
Maintenant, tirez les méthodes privées. J'ai utilisé performSelector:withObject:afterDelay
, ou vous pouvez utiliser un bouton.
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)
}
}
Appeler directement unsafeBitCast
est dangereux. Pour des raisons de sécurité, vérifiez si vos UITableView
et UITableViewCell
répondent à ces sélecteurs ou rendez les fonctions facultatives.