如何使用UIScrollView触发UITableViewCell中的滑动操作

最近,我想向UITableViewCells添加快速操作,而不是使用按钮而是使用平移手势,例如可以在许多邮件应用程序中找到的动作。

UIScrollView是最好和最简单的方法。 首先,您将看到如何在UITableViewCell中设置UIScrollView,以显示操作的自定义视图。 然后,您将看到如何通过实现UIScrollViewDelegate通过平移手势执行操作。

1. UITableViewCell设置

首先,创建UITableViewCell的子类。 容器视图将是我们的单元格内容。 动作视图和标签将是滚动显示的视图。

class SwipeCell: UITableViewCell { 
var scrollView: UIScrollView!
var containerView: UIView!
var actionView: UIView!
var actionLabel: UILabel!
}

在初始化程序中调用的方法中,我们实例化此视图并将其添加到视图层次结构中,如下所示:

  contentView 
滚动查看
动作视图
动作标签
containerView

对于scrollView,我们要隐藏滚动指示器。 当用户结束拖动时,我们还希望快速减速。 注意,我们需要设置contentInset以便在scrollView的填充中显示actionView。

 func setup() { 
scrollView = UIScrollView(frame: bounds)
scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.contentSize = bounds.size
scrollView.contentInset = UIEdgeInsets(
top: 0, left: bounds.width, bottom: 0, right: bounds.width)
scrollView.delegate = self
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.decelerationRate = UIScrollViewDecelerationRateFast
contentView.addSubview(scrollView)

actionView = UIView(frame: CGRect(origin: .zero, size: bounds.size))
actionView.backgroundColor = UIColor.gray
actionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.addSubview(actionView)

actionLabel = UILabel(frame:
CGRect(x: 10, y: 0, width: bounds.width - 20, height: bounds.height))
actionLabel.font = UIFont.systemFont(ofSize: 12)
actionLabel.autoresizingMask = [.flexibleWidth, .flexibleHeight]
actionLabel.textColor = .white
actionView.addSubview(actionLabel)

containerView = UIView(frame: scrollView.bounds)
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.backgroundColor = UIColor.lightGray
scrollView.addSubview(containerView)
}

为了确保在单元格出现时正确设置了scrollView插入和大小,我们还应该在layoutSubviews方法中对其进行更新。

 override func layoutSubviews() { 
super.layoutSubviews()
scrollView.contentInset = UIEdgeInsets(top: 0, left: bounds.width, bottom: 0, right: bounds.width)
scrollView.contentSize = contentView.bounds.size
}

2.显示动作视图

如果您尝试使用此代码,则会注意到scrollView起作用。 但是,在滚动时,不会显示actionView。 实际上,它沿着containerView滚动。

为了实现此行为,我们必须在scrollViewDidScroll方法中更新actionView框架。 通过使用contentOffset更新其x,可以确保actionView永不移动,并且在containerView进行操作时不会显示出来。

 func scrollViewDidScroll(_ scrollView: UIScrollView) { 
let offsetX = scrollView.contentOffset.x
actionView.frame = CGRect(origin: CGPoint(x: offsetX, y: 0), size: actionView.frame.size)
}

3.滚动动画

当用户滑动超过最小偏移量(此处为50px)并结束手势时,我们要完全显示actionView。 为此,我们需要检测滑动方向并实现一些UIScrollViewDelegate方法。

使用scrollview,可以通过检查contentOffset值来找到滚动方向。 因此,这里有一些实用程序属性,用于找出用户滚动时哪一侧可见。

 let minOffset: CGFloat = 50 

var isLeftSideVisible:Bool {
return scrollView.contentOffset.x < 0
}
var isRightSideVisible:Bool {
return scrollView.contentOffset.x > 0
}

当用户完成内容滚动时,将调用“ scrollViewWillEndDragging”。 我们可以更改«targetContentOffset»参数的值,以调整滚动视图完成其滚动动画的位置。 就我们而言

 func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 

let offsetX = abs(scrollView.contentOffset.x)
let width = scrollView.contentSize.width

if offsetX < minOffset {
targetContentOffset.pointee = .zero
}else{
if isLeftSideVisible {
targetContentOffset.pointee.x = -width
} else if isRightSideVisible {
targetContentOffset.pointee.x = width
}
}
}

现在,当动画结束时,我们想回到初始位置。

 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 
scrollView.setContentOffset(.zero, animated: false)
}

4.检测滚动动作

当用户滚动超过最小偏移量时,我们可以开始显示动作标题及其背景颜色。 但是我想处理单元格上的几个动作,所以如果用户进一步滚动,我想显示另一个动作(我受到Polymail应用程序的启发)。

一方面有两个动作就足够了,但如果有更多动作可能会使用户困惑。 但是从技术上讲,我们不应该将自己限制为两个,而应该使用数组。

 protocol SwipeAction { 
var color: UIColor { get }
var title: String { get }
}
var leftActions: [SwipeAction] = []
var rightActions: [SwipeAction] = []

这是一种从contentOffset检索操作(如果有)的方法。 我们基本上将宽度划分为动作数,并使用contentOffset检索索引。

 func findVisibleAction() -> SwipeAction? { 

let offsetX = abs(scrollView.contentOffset.x)
let actionsWidth = scrollView.contentSize.width - minOffset

if offsetX < minOffset {
return nil
}
let relativeX = offsetX - minOffset
if isLeftSideVisible {
let singleActionWidth = actionsWidth / CGFloat(leftActions.count)
let i = max(0, Int(relativeX / singleActionWidth))
return i < leftActions.count ? leftActions[i] : nil
}
if isRightSideVisible {
let singleActionWidth = actionsWidth / CGFloat(rightActions.count)
let i = max(0, Int(relativeX / singleActionWidth))
return i < rightActions.count ? rightActions[i] : nil
}
return nil
}

5.滚动显示动作

当视图结束拖动时,我们要保存用户选择的动作。

 var selectedAction: SwipeAction? 

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
selectedAction = findVisibleAction()
}

当视图滚动时,我们仍然像以前看到的那样更改actionView框架,但是我们还使用选定的动作或可见的动作(如果用户仍在拖动)来设置actionView。

 func scrollViewDidScroll(_ scrollView: UIScrollView) { 

let offsetX = scrollView.contentOffset.x
actionView.frame = CGRect(origin: CGPoint(x: offsetX, y: 0), size: actionView.frame.size)

let action = selectedAction ?? findVisibleAction()
actionView.backgroundColor = action?.color ?? UIColor.lightGray
actionLabel.textAlignment = isRightSideVisible ? .right : .left
actionLabel.text = action?.title
}

6.执行动作

动画结束时,如果选择了一个动作,则可以调用一个方法来执行它。 否则,您可以通过将内容偏移设置为零来关闭操作视图。

 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 
if let action = selectedAction {
performAction(action)
} else {
dismissActionView()
}
}
func dismissActionView() {
selectedAction = nil
scrollView.setContentOffset(.zero, animated: false)
}

当用户再次开始拖动时,别忘了取消选择的动作!

 func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 
selectedAction = nil
}

最后,用不到150行代码,您最终获得了一种简单且可自定义的方式来处理UITableViewCells上的操作。

您可以在此处找到最终代码:https://gist.github.com/AdrienCog/9baf1489d507ff282ecbdc29d61e2b78