Home » Swift » how to swipe cells in uitableview left and right, showing an image on left and an image on right

how to swipe cells in uitableview left and right, showing an image on left and an image on right

Posted by: admin November 30, 2017 Leave a comment

Questions:

As illustrated, what I need to achieve is when swiping left, a button with an image shows up, the blue one, and when swiping right the green button shows up, how to do this? i use swift and xcode 6.4

This is what i tried before asking, I was able to show two options with text in the right of a cell, but i don’t want that, what is needed is in the illustration, and as said, the buttons need to be images not text.

enter image description here

Answers:

You can subclass UITableViewCell to incorporate a UIPanGestureRecognizer that manipulates the cell’s contentViews frame and add your buttons behind the contentView.

To see how this can work it detail, I added example code on how to do that below for reference. This also adds a tap gesture recognizer to ‘close’ the action on tap instead of selecting the cell.

Also, as requested in the comments, here is a gif of how this works (showing the colors of the buttons on the side as an indication of action, but you can easily modify the contentView‘s frame to be completely overlapping the buttons in your subclass.)

enter image description here

// // MWSwipeableTableViewCell.swift // MW UI Toolkit // // Created by Jan Greve on 02.12.14. // Copyright (c) 2014 Markenwerk GmbH. All rights reserved. // import UIKit protocol MWSwipeableTableViewCellDelegate : NSObjectProtocol { func swipeableTableViewCellDidRecognizeSwipe(cell : MWSwipeableTableViewCell) func swipeableTableViewCellDidTapLeftButton(cell : MWSwipeableTableViewCell) func swipeableTableViewCellDidTapRightButton(cell : MWSwipeableTableViewCell) } class MWSwipeableTableViewCell: UITableViewCell { weak var delegate : MWSwipeableTableViewCellDelegate? var animationOptions : UIViewAnimationOptions = [.AllowUserInteraction, .BeginFromCurrentState] var animationDuration : NSTimeInterval = 0.5 var animationDelay : NSTimeInterval = 0 var animationSpingDamping : CGFloat = 0.5 var animationInitialVelocity : CGFloat = 1 private weak var leftWidthConstraint : NSLayoutConstraint! private weak var rightWidthConstraint : NSLayoutConstraint! var buttonWidth :CGFloat = 80 { didSet(val) { if let r = self.rightWidthConstraint { r.constant = self.buttonWidth } if let l = self.leftWidthConstraint { l.constant = self.buttonWidth } } } private weak var panRecognizer : UIPanGestureRecognizer! private weak var buttonCancelTap : UITapGestureRecognizer! private var beginPoint : CGPoint = CGPointZero weak var rightButton : UIButton! { willSet(val) { if let r = self.rightButton { r.removeFromSuperview() } if let b = val { self.addSubview(b) b.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside) b.translatesAutoresizingMaskIntoConstraints = false self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(0)-[v]-(0)-|", options: [], metrics: nil, views: ["v":b])) self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("[v]-(0)-|", options: [], metrics: nil, views: ["v":b])) let wc = NSLayoutConstraint(item: b, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: self.buttonWidth) b.addConstraint(wc) self.rightWidthConstraint = wc self.sendSubviewToBack(b) } } } weak var leftButton : UIButton! { willSet(val) { if let l = self.leftButton { l.removeFromSuperview() } if let b = val { self.addSubview(b) b.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside) b.translatesAutoresizingMaskIntoConstraints = false self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(0)-[v]-(0)-|", options: [], metrics: nil, views: ["v":b])) self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-(0)-[v]", options: [], metrics: nil, views: ["v":b])) let wc = NSLayoutConstraint(item: b, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: self.buttonWidth) b.addConstraint(wc) self.leftWidthConstraint = wc self.sendSubviewToBack(b) } } } override func awakeFromNib() { super.awakeFromNib() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) commonInit() } private func commonInit() { let pan = UIPanGestureRecognizer(target: self, action: "didPan:") pan.delegate = self self.addGestureRecognizer(pan) self.panRecognizer = pan let tap = UITapGestureRecognizer(target: self, action: "didTap:") tap.delegate = self self.addGestureRecognizer(tap) self.buttonCancelTap = tap self.contentView.backgroundColor = UIColor.clearColor() } override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { if let tap = gestureRecognizer as? UITapGestureRecognizer { if tap == self.buttonCancelTap { return self.contentView.frame.origin.x != 0 } else { return super.gestureRecognizerShouldBegin(gestureRecognizer) } } else if let pan = gestureRecognizer as? UIPanGestureRecognizer { let trans = pan.translationInView(self) if abs(trans.x) > abs(trans.y) { return true } else if self.contentView.frame.origin.x != 0 { return true } else { return false } } else { return super.gestureRecognizerShouldBegin(gestureRecognizer) } } func didTap(sender : UITapGestureRecognizer) { UIView.animateWithDuration(self.animationDuration, delay: self.animationDelay, usingSpringWithDamping: self.animationSpingDamping, initialSpringVelocity: self.animationInitialVelocity, options: self.animationOptions, animations: { () -> Void in self.contentView.frame.origin.x = 0 }, completion: nil) } func didPan(sender: UIPanGestureRecognizer) { switch sender.state { case .Began: self.delegate?.swipeableTableViewCellDidRecognizeSwipe(self) self.beginPoint = sender.locationInView(self) self.beginPoint.x -= self.contentView.frame.origin.x case .Changed: let now = sender.locationInView(self) let distX = now.x - self.beginPoint.x if distX <= 0 { let d = max(distX,-(self.contentView.frame.size.width-self.buttonWidth)) if d > -self.buttonWidth*2 || self.rightButton != nil || self.contentView.frame.origin.x > 0 { self.contentView.frame.origin.x = d } else { sender.enabled = false sender.enabled = true } } else { let d = min(distX,self.contentView.frame.size.width-self.buttonWidth) if d < self.buttonWidth*2 || self.leftButton != nil || self.contentView.frame.origin.x < 0 { self.contentView.frame.origin.x = d } else { sender.enabled = false sender.enabled = true } } default: delegate?.swipeableTableViewCellDidRecognizeSwipe(self) let offset = self.contentView.frame.origin.x if offset > self.buttonWidth && self.leftButton != nil { UIView.animateWithDuration(self.animationDuration, delay: self.animationDelay, usingSpringWithDamping: self.animationSpingDamping, initialSpringVelocity: self.animationInitialVelocity, options: self.animationOptions, animations: { () -> Void in self.contentView.frame.origin.x = self.buttonWidth }, completion: nil) } else if -offset > self.buttonWidth && self.rightButton != nil { UIView.animateWithDuration(self.animationDuration, delay: self.animationDelay, usingSpringWithDamping: self.animationSpingDamping, initialSpringVelocity: self.animationInitialVelocity, options: self.animationOptions, animations: { () -> Void in self.contentView.frame.origin.x = -self.buttonWidth }, completion: nil) } else { UIView.animateWithDuration(self.animationDuration, delay: self.animationDelay, usingSpringWithDamping: self.animationSpingDamping, initialSpringVelocity: self.animationInitialVelocity, options: self.animationOptions, animations: { () -> Void in self.contentView.frame.origin.x = 0 }, completion: nil) } } } func closeButtonsIfShown(animated:Bool = true) -> Bool { if self.contentView.frame.origin.x != 0 { if animated { UIView.animateWithDuration(self.animationDuration, delay: self.animationDelay, usingSpringWithDamping: self.animationSpingDamping, initialSpringVelocity: self.animationInitialVelocity, options: self.animationOptions, animations: { () -> Void in self.contentView.frame.origin.x = 0 self.panRecognizer.enabled = false self.panRecognizer.enabled = true }, completion: nil) } else { self.contentView.frame.origin.x = 0 self.panRecognizer.enabled = false self.panRecognizer.enabled = true } return true } else { return false } } func didTapButton(sender:UIButton!) { if let d = delegate { if let l = self.leftButton { if sender == l { d.swipeableTableViewCellDidTapLeftButton(self) } } if let r = self.rightButton { if sender == r { d.swipeableTableViewCellDidTapRightButton(self) } } } self.closeButtonsIfShown(false) } override func setHighlighted(highlighted: Bool, animated: Bool) { let showing = self.contentView.frame.origin.x != 0 if !showing { super.setHighlighted(highlighted, animated: animated) self.rightButton?.alpha = showing || !highlighted ? 1 : 0 self.leftButton?.alpha = showing || !highlighted ? 1 : 0 } } override func setSelected(selected: Bool, animated: Bool) { let showing = self.contentView.frame.origin.x != 0 if !showing { super.setSelected(selected, animated: animated) self.rightButton?.alpha = showing || !selected ? 1 : 0 self.leftButton?.alpha = showing || !selected ? 1 : 0 } } } 

Questions:
Answers:

There are many ways to go at this, you could look into one of the many existing libraries such as BMXSwipeableCell by Massimiliano Bigatti https://github.com/mbigatti/BMXSwipableCell, where you can either look at the source code or copy it completely.

Another approach is reading through one of the following two great tutorials:

How to Make a Swipeable TableViewCell With Actions – Without Going Nuts ScrollViews – by Ellen Shapiro:
https://www.raywenderlich.com/62435/make-swipeable-table-view-cell-actions-without-going-nuts-scroll-views

How to Make a Gesture-Driven To-Do List App Like Clear in Swift – by Audrey Tam: https://www.raywenderlich.com/77974/making-a-gesture-driven-to-do-list-app-like-clear-in-swift-part-1

To give you a quick idea, the gist is as follows:

i. Create a custom TableViewCell

ii. Add a UIPangestureRecognizer

iii. Keep a reference to the original contentView frame, this is necessary since you will be swiping(panning) this contentView left and right. And keep a reference to the originalCenter of the cell.

iv. Add the images, buttons, or other views you want to reveal to the cell

What happens is that you will guide the appearance of your swipeable cell through the three stages of the UIPanGestureRecognizer: UIGestureStateBegan, UIGestureStateChanged, UIGestureStateEnded

  1. UIGestureStateBegan: First check if this a horizontal pan, and not a vertical one in the UIGestureDelegate. We do this to not confuse the UITableView which, as you might recall scroll Vertically. Then get a reference to the original Center of the cell.

  2. UIGestureStateChanged: As a user moves his finger to the left or to the right, we need to update the appearance of the cell. By moving the cell’s contentView's center using the original center reference, and the movement given to us by the gesture we get exactly the behavior we wish to achieve.

  3. UIGestureStateEnded: Here we need to decide if we want to keep the revealed image, button etc. in view after the user releases the cell, or if we want to ‘snap’ back. The threshold for this is really up to to, but will be some sort of percentage change left or right compared to the total width of the cell. If you wish to ‘snap’ back, simply set the contentView‘s frame to the original frame we kept a reference to. If not, set it to an offset that nicely displays the content you wished to reveal.

I hope this helped in getting the concept across, please check out one of those two amazing tutorials for a more detailed explanation!

Questions:
Answers:

I could achieved the same result using the swipe gesture recognizer. Hope this would help.

  1. Create a table with prototype cell.
  2. Add button both on left side and right side of the cell with image for default control state (you can change the image based on the state control state).
  3. Add a container view (here it is mainView)on top of the cell covering the entire cell area.
  4. Create a TableViewCustomCell by subclassing the UITableViewCell
  5. Change the class of the prototype cell to the custom TableViewCustomCell
  6. Use the following code in TableViewCustomCell

Swift Code :

 import UIKit class TableViewCustomCell:UITableViewCell { @IBOutlet weak var rightButton: UIButton! @IBOutlet weak var leftButton: UIButton! @IBOutlet weak var mainView: UIView! @IBAction func leftButtonTap(sender: AnyObject) { print("leftTap") } @IBAction func rightButtonTap(sender: AnyObject) { print("rightTap") } override func awakeFromNib() { let leftSwipe = UISwipeGestureRecognizer(target: self, action:Selector("swipe:")) leftSwipe.direction = .Left; self.mainView.addGestureRecognizer(leftSwipe) let rightSwipe = UISwipeGestureRecognizer(target: self, action:Selector("swipe:")) rightSwipe.direction = .Right; self.mainView.addGestureRecognizer(rightSwipe) } func swipe(sender:AnyObject) { let swipeGesture:UISwipeGestureRecognizer = sender as! UISwipeGestureRecognizer if(swipeGesture.direction == .Left) { var frame:CGRect = self.mainView.frame; frame.origin.x = -self.leftButton.frame.width; self.mainView.frame = frame; } else if(swipeGesture.direction == .Right) { var frame:CGRect = self.mainView.frame; frame.origin.x = +self.rightButton.frame.width; self.mainView.frame = frame; } } } 

Questions:
Answers:

If you want to use library for such function, i would suggest you to use
https://github.com/MortimerGoro/MGSwipeTableCell

its easy to use and easy customise.

Questions:
Answers:

The overall idea is simple – your cell’s content view is a UIScrollView with views on the sides.

However, the complete working solution is a bit complicated and probably too broad for an answer.

I would recommend you to start with an already implemented solution, e.g. SWTableViewCell (but there are others) and look into the source code. Or just use it directly. Most of the solutions can be installed using cocoapods and they work both in Swift and Objective-C.