简体   繁体   中英

How to add a Swipe Left gesture to a TableView and return the cell location when swiped in Swift 3

I have a TableView which is already coded to perform an action when any cell it selected using the didSelectRowAt method.

Now, I'd like to add a Swipe Left gesture to the Table (or cell) so that I can perform a secondary action when a cell is swiped rather than when tapped.

1) I would like the cell to move left while swiping but I do NOT want to add a button in the space where the cell has moved from.

2) Instead, I'd like to be able to 'drag' the cell left until a certain point (say halfway) and at that point execute the secondary action with the indexPath (so I know which cell was dragged).

3) If the user stops dragging or lets go of the cell, I'd like it to return to it's starting position and have no actions occur.

I've seen a lot of samples that do various pieces of this but most are in Obj-C or insert buttons in the same row as the cell.

Also, is it better to add the Gesture to each cell? It seems smarter to add it to the table...

EDIT: See below for my complete answer with code

I've done some research and created a bare-bones example of how to this - create a table cell that can be swiped AND tapped. I'm using it in a music player - tap a cell and play the song, swipe the same cell and segue to a different view.

I've built my solution based on these two existing samples: https://www.raywenderlich.com/77974/making-a-gesture-driven-to-do-list-app-like-clear-in-swift-part-1

https://gabrielghe.github.io/swift/2016/03/20/swipable-uitableviewcell

I don't have a repository to share so all the code is here.

I'm using Xcode 8.2.1 and Swift 3

STEP 1:

  • Create a New, Single-View Swift project, and open the Storyboard.
  • Drag a TableView onto the existing ViewController and drag it to fit the view.

STEP 2:

  • Add new Swift File to the Project and name it "TableViewCellSliding.swift".
  • Copy/paste the code below into the new file.

     // // TableViewCellSliding.swift // import UIKit protocol SlidingCellDelegate { // tell the TableView that a swipe happened func hasPerformedSwipe(touch: CGPoint) func hasPerformedTap(touch: CGPoint) } class SlidingTableViewCell: UITableViewCell { var delegate: SlidingCellDelegate? var originalCenter = CGPoint() var isSwipeSuccessful = false var touch = CGPoint() required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) // add a PAN gesture let pRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SlidingTableViewCell.handlePan(_:))) pRecognizer.delegate = self addGestureRecognizer(pRecognizer) // add a TAP gesture // note that adding the PAN gesture to a cell disables the built-in tap responder (didSelectRowAtIndexPath) // so we can add in our own here if we want both swipe and tap actions let tRecognizer = UITapGestureRecognizer(target: self, action: #selector(SlidingTableViewCell.handleTap(_:))) tRecognizer.delegate = self addGestureRecognizer(tRecognizer) } override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer { let translation = panGestureRecognizer.translation(in: superview!) //look for right-swipe if (fabs(translation.x) > fabs(translation.y)) && (translation.x > 0){ // look for left-swipe //if (fabs(translation.x) > fabs(translation.y)) && (translation.x < 0){ //print("gesture 1") touch = panGestureRecognizer.location(in: superview) return true } //not left or right - must be up or down return false }else if gestureRecognizer is UITapGestureRecognizer { touch = gestureRecognizer.location(in: superview) return true } return false } func handleTap(_ recognizer: UITapGestureRecognizer){ // call function to get indexPath since didSelectRowAtIndexPath will be disabled delegate?.hasPerformedTap(touch: touch) } func handlePan(_ recognizer: UIPanGestureRecognizer) { if recognizer.state == .began { originalCenter = center } if recognizer.state == .changed { checkIfSwiped(recongizer: recognizer) } if recognizer.state == .ended { let originalFrame = CGRect(x: 0, y: frame.origin.y, width: bounds.size.width, height: bounds.size.height) if isSwipeSuccessful{ delegate?.hasPerformedSwipe(touch: touch) //after 'short' swipe animate back to origin quickly moveViewBackIntoPlaceSlowly(originalFrame: originalFrame) } else { //after successful swipe animate back to origin slowly moveViewBackIntoPlace(originalFrame: originalFrame) } } } func checkIfSwiped(recongizer: UIPanGestureRecognizer) { let translation = recongizer.translation(in: self) center = CGPoint(x: originalCenter.x + translation.x, y: originalCenter.y) //this allows only swipe-right isSwipeSuccessful = frame.origin.x > frame.size.width / 2.0 //pan is 1/2 width of the cell //this allows only swipe-left //isSwipeSuccessful = frame.origin.x < -frame.size.width / 3.0 //pan is 1/3 width of the cell } func moveViewBackIntoPlace(originalFrame: CGRect) { UIView.animate(withDuration: 0.2, animations: {self.frame = originalFrame}) } func moveViewBackIntoPlaceSlowly(originalFrame: CGRect) { UIView.animate(withDuration: 1.5, animations: {self.frame = originalFrame}) } } 

STEP 3:

  • Copy/paste the code below into the existing file "ViewController.swift"

     // // ViewController.swift // import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SlidingCellDelegate { @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. tableView.dataSource = self tableView.delegate = self tableView.register(SlidingTableViewCell.self, forCellReuseIdentifier: "cell") tableView.rowHeight = 50; } func hasPerformedSwipe(touch: CGPoint) { if let indexPath = tableView.indexPathForRow(at: touch) { // Access the image or the cell at this index path print("got a swipe row:\\(indexPath.row)") } } func hasPerformedTap(touch: CGPoint){ if let indexPath = tableView.indexPathForRow(at: touch) { // Access the image or the cell at this index path print("got a tap row:\\(indexPath.row)") } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int)-> Int { return 100 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell=tableView.dequeueReusableCell(withIdentifier: "cell",for: indexPath) as! SlidingTableViewCell // Configure cell cell.selectionStyle = .none cell.textLabel?.text = "hello \\(indexPath.row)" cell.delegate = self return cell } func tableView(sender: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { // do stuff with indexPath.row and indexPath.section //never make it here because we added a tap gesture but this will print("selected cell") } } 

STEP 4:

Connect it all in the Storyboard.

  • Open the Storyboard view and select the TableView. Go to the Connections Inspector (upper-right corner arrow in a circle) and drag from New Referencing Outlet to the TableView and select "tableView" from the popup menu.

  • With the TableView still selected, drag from Outlets > dataSource to the TableView in the Storyboard. Repeat starting with Outlets > delegate.

STEP 5:

  • Run it!

I'm not going into details about any of the code as the two links at the top do that very well. This is just about having complete, simple, clean code that you can build on. Enjoy.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM