SwipeTableViewCell.swift 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. //
  2. // SwipeTableViewCell.swift
  3. //
  4. // Created by Jeremy Koch
  5. // Copyright © 2017 Jeremy Koch. All rights reserved.
  6. //
  7. import UIKit
  8. /**
  9. The `SwipeTableViewCell` class extends `UITableViewCell` and provides more flexible options for cell swiping behavior.
  10. The default behavior closely matches the stock Mail.app. If you want to customize the transition style (ie. how the action buttons are exposed), or the expansion style (the behavior when the row is swiped passes a defined threshold), you can return the appropriately configured `SwipeOptions` via the `SwipeTableViewCellDelegate` delegate.
  11. */
  12. open class SwipeTableViewCell: UITableViewCell {
  13. /// The object that acts as the delegate of the `SwipeTableViewCell`.
  14. public weak var delegate: SwipeTableViewCellDelegate?
  15. var state = SwipeState.center
  16. var actionsView: SwipeActionsView?
  17. var scrollView: UIScrollView? {
  18. return tableView
  19. }
  20. var indexPath: IndexPath? {
  21. return tableView?.indexPath(for: self)
  22. }
  23. var panGestureRecognizer: UIGestureRecognizer
  24. {
  25. return swipeController.panGestureRecognizer;
  26. }
  27. var swipeController: SwipeController!
  28. var isPreviouslySelected = false
  29. weak var tableView: UITableView?
  30. /// :nodoc:
  31. open override var frame: CGRect {
  32. set { super.frame = state.isActive ? CGRect(origin: CGPoint(x: frame.minX, y: newValue.minY), size: newValue.size) : newValue }
  33. get { return super.frame }
  34. }
  35. /// :nodoc:
  36. open override var layoutMargins: UIEdgeInsets {
  37. get {
  38. return frame.origin.x != 0 ? swipeController.originalLayoutMargins : super.layoutMargins
  39. }
  40. set {
  41. super.layoutMargins = newValue
  42. }
  43. }
  44. /// :nodoc:
  45. override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
  46. super.init(style: style, reuseIdentifier: reuseIdentifier)
  47. configure()
  48. }
  49. /// :nodoc:
  50. required public init?(coder aDecoder: NSCoder) {
  51. super.init(coder: aDecoder)
  52. configure()
  53. }
  54. deinit {
  55. tableView?.panGestureRecognizer.removeTarget(self, action: nil)
  56. }
  57. func configure() {
  58. clipsToBounds = false
  59. swipeController = SwipeController(swipeable: self, actionsContainerView: self)
  60. swipeController.delegate = self
  61. }
  62. /// :nodoc:
  63. override open func prepareForReuse() {
  64. super.prepareForReuse()
  65. reset()
  66. resetSelectedState()
  67. }
  68. /// :nodoc:
  69. override open func didMoveToSuperview() {
  70. super.didMoveToSuperview()
  71. var view: UIView = self
  72. while let superview = view.superview {
  73. view = superview
  74. if let tableView = view as? UITableView {
  75. self.tableView = tableView
  76. swipeController.scrollView = tableView;
  77. tableView.panGestureRecognizer.removeTarget(self, action: nil)
  78. tableView.panGestureRecognizer.addTarget(self, action: #selector(handleTablePan(gesture:)))
  79. return
  80. }
  81. }
  82. }
  83. /// :nodoc:
  84. override open func setEditing(_ editing: Bool, animated: Bool) {
  85. super.setEditing(editing, animated: animated)
  86. if editing {
  87. hideSwipe(animated: false)
  88. }
  89. }
  90. // Override so we can accept touches anywhere within the cell's minY/maxY.
  91. // This is required to detect touches on the `SwipeActionsView` sitting alongside the
  92. // `SwipeTableCell`.
  93. /// :nodoc:
  94. override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
  95. guard let superview = superview else { return false }
  96. let point = convert(point, to: superview)
  97. if !UIAccessibility.isVoiceOverRunning {
  98. for cell in tableView?.swipeCells ?? [] {
  99. if (cell.state == .left || cell.state == .right) && !cell.contains(point: point) {
  100. tableView?.hideSwipeCell()
  101. return false
  102. }
  103. }
  104. }
  105. return contains(point: point)
  106. }
  107. func contains(point: CGPoint) -> Bool {
  108. return point.y > frame.minY && point.y < frame.maxY
  109. }
  110. /// :nodoc:
  111. override open func setHighlighted(_ highlighted: Bool, animated: Bool) {
  112. if state == .center {
  113. super.setHighlighted(highlighted, animated: animated)
  114. }
  115. }
  116. /// :nodoc:
  117. override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
  118. return swipeController.gestureRecognizerShouldBegin(gestureRecognizer)
  119. }
  120. /// :nodoc:
  121. open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
  122. super.traitCollectionDidChange(previousTraitCollection)
  123. swipeController.traitCollectionDidChange(from: previousTraitCollection, to: self.traitCollection)
  124. }
  125. @objc func handleTablePan(gesture: UIPanGestureRecognizer) {
  126. if gesture.state == .began {
  127. hideSwipe(animated: true)
  128. }
  129. }
  130. func reset() {
  131. swipeController.reset()
  132. clipsToBounds = false
  133. }
  134. func resetSelectedState() {
  135. if isPreviouslySelected {
  136. if let tableView = tableView, let indexPath = tableView.indexPath(for: self) {
  137. tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
  138. }
  139. }
  140. isPreviouslySelected = false
  141. }
  142. }
  143. extension SwipeTableViewCell: SwipeControllerDelegate {
  144. func swipeController(_ controller: SwipeController, canBeginEditingSwipeableFor orientation: SwipeActionsOrientation) -> Bool {
  145. return self.isEditing == false
  146. }
  147. func swipeController(_ controller: SwipeController, editActionsForSwipeableFor orientation: SwipeActionsOrientation) -> [SwipeAction]? {
  148. guard let tableView = tableView, let indexPath = tableView.indexPath(for: self) else { return nil }
  149. return delegate?.tableView(tableView, editActionsForRowAt: indexPath, for: orientation)
  150. }
  151. func swipeController(_ controller: SwipeController, editActionsOptionsForSwipeableFor orientation: SwipeActionsOrientation) -> SwipeOptions {
  152. guard let tableView = tableView, let indexPath = tableView.indexPath(for: self) else { return SwipeOptions() }
  153. return delegate?.tableView(tableView, editActionsOptionsForRowAt: indexPath, for: orientation) ?? SwipeOptions()
  154. }
  155. func swipeController(_ controller: SwipeController, visibleRectFor scrollView: UIScrollView) -> CGRect? {
  156. guard let tableView = tableView else { return nil }
  157. return delegate?.visibleRect(for: tableView)
  158. }
  159. func swipeController(_ controller: SwipeController, willBeginEditingSwipeableFor orientation: SwipeActionsOrientation) {
  160. guard let tableView = tableView, let indexPath = tableView.indexPath(for: self) else { return }
  161. // Remove highlight and deselect any selected cells
  162. super.setHighlighted(false, animated: false)
  163. isPreviouslySelected = isSelected
  164. tableView.deselectRow(at: indexPath, animated: false)
  165. delegate?.tableView(tableView, willBeginEditingRowAt: indexPath, for: orientation)
  166. }
  167. func swipeController(_ controller: SwipeController, didEndEditingSwipeableFor orientation: SwipeActionsOrientation) {
  168. guard let tableView = tableView, let indexPath = tableView.indexPath(for: self), let actionsView = self.actionsView else { return }
  169. resetSelectedState()
  170. delegate?.tableView(tableView, didEndEditingRowAt: indexPath, for: actionsView.orientation)
  171. }
  172. func swipeController(_ controller: SwipeController, didDeleteSwipeableAt indexPath: IndexPath) {
  173. tableView?.deleteRows(at: [indexPath], with: .none)
  174. }
  175. }