
前言
最近项目开发正好遇到了类似苹果地图中那个可以拖动的 view:

感觉有点意思,于是实现了一波。
需求
需满足三点:
- view 跟随手势而动。
- 支持快速向上刨和向下刨。
- 拖动过程中停止拖动,然后手指离屏,view 向上弹还是往回缩由 view 当前的高度决定。
实现
首先是 view 跟随手势而动,这个很简单,对应方法是: - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
,在里面做相关处理即可:
// 拖动过程中
private func handleMoved(touch: UITouch) {
// 拿到当前 point
let currentPoint: CGPoint = (touch.location(in: panView))
// 拿到上一个 point
let prePoint: CGPoint = (touch.previousLocation(in: panView))
// 这是拖动的距离
let offsetY = currentPoint.y - prePoint.y
// 得到当前的高度
let height = self.panView.frame.size.height - offsetY
// 拖动过程修改高度,不能比最大值大,不能比最小值小(根据你们需求而定)
if height <= panViewMaxHeight, height >= panViewMinHeight {
self.panView.snp.remakeConstraints { (make) in
make.height.equalTo(height)
make.left.right.bottom.equalToSuperview()
}
}
}
然后第二、三点需求都是在拖动结束时处理,对应方法是:- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
,这里需要知道拖动结束时 view 到底是向上、向下还是处于静止状态。
拖动方向我们只需要根据两个点就可以知晓:拖动结束瞬间的那个点,及拖动结束瞬间前一瞬间的那个点:
核心代码:
let currentPoint: CGPoint = (touch.location(in: panView))
let prePoint: CGPoint = (touch.previousLocation(in: panView))
// 获取拖动方向
// 0.0 表示静止(可以用 绝对值<1 表示,非常非常轻微的拖动,看作是静止)
// 大于0表示向下
// 小于0表示向上
let offsetY = currentPoint.y - prePoint.y
这里需要注意的是理论上offsetY == 0
是表示拖动结束时是绝对静止的,但是,很多时候我们认为是静止的实际却可能有一丢丢的偏差,也就是说offsetY
并不等于0。对于这种情况我的处理方式是判断offsetY
的绝对值,如果它的绝对值小于1,那么可以认为拖动结束时 view 是静止的。
提供一个完整 demo 代码
import UIKit
class GaodeMapPanDemoViewController: UIViewController {
// 最大高度
let panViewMaxHeight: CGFloat = 500
// 最小高度
let panViewMinHeight: CGFloat = 200
private lazy var panView: PanView = {
let view = PanView()
view.touchMovedClosure = { [weak self] (touch) in
self?.handleMoved(touch: touch)
}
view.touchEndedClosure = { [weak self] (touch) in
self?.handleMovedEnded(touch: touch)
}
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .white
view.addSubview(panView)
panView.snp.makeConstraints { (make) in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(panViewMinHeight)
}
}
// 拖动过程中
private func handleMoved(touch: UITouch) {
// 拿到当前 point
let currentPoint: CGPoint = (touch.location(in: panView))
// 拿到上一个 point
let prePoint: CGPoint = (touch.previousLocation(in: panView))
// 这是拖动的距离
let offsetY = currentPoint.y - prePoint.y
// 得到当前的高度
let height = self.panView.frame.size.height - offsetY
// 拖动过程修改高度,不能比最大值大,不能比最小值小(根据你们需求而定)
if height <= panViewMaxHeight, height >= panViewMinHeight {
self.panView.snp.remakeConstraints { (make) in
make.height.equalTo(height)
make.left.right.bottom.equalToSuperview()
}
}
}
// 拖动结束
private func handleMovedEnded(touch: UITouch) {
let currentPoint: CGPoint = (touch.location(in: panView))
let prePoint: CGPoint = (touch.previousLocation(in: panView))
let offsetY = currentPoint.y - prePoint.y
// 获取拖动方向
// 0.0 表示静止(可以用 绝对值<1 表示,非常非常轻微的拖动,看作是静止)
// 大于0表示向下
// 小于0表示向上
if abs(offsetY) <= 1 {
if self.panView.height() < (panViewMaxHeight+panViewMinHeight)/2 {
UIView.animate(withDuration: 0.1) {
self.panView.snp.remakeConstraints { (make) in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(self.panViewMinHeight)
}
self.panView.superview?.layoutIfNeeded()
}
} else {
UIView.animate(withDuration: 0.1) {
self.panView.snp.remakeConstraints { (make) in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(self.panViewMaxHeight)
}
self.panView.superview?.layoutIfNeeded()
}
}
} else if offsetY > 0 {
UIView.animate(withDuration: 0.1) {
self.panView.snp.remakeConstraints { (make) in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(self.panViewMinHeight)
}
self.panView.superview?.layoutIfNeeded()
}
} else {
UIView.animate(withDuration: 0.1) {
self.panView.snp.remakeConstraints { (make) in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(self.panViewMaxHeight)
}
self.panView.superview?.layoutIfNeeded()
}
}
}
}
// 可拖动的 view
fileprivate class PanView: UIView {
var touchMovedClosure: ((_ touch: UITouch)->())?
var touchEndedClosure: ((_ touch: UITouch)->())?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.colorWithHexString("#EBEDF0")
self.layer.cornerRadius = 20
self.layer.shadowColor = UIColor.colorWithHexString("#000000").withAlphaComponent(0.3).cgColor
self.layer.shadowOffset = .init(width: 0, height: 2)
self.layer.shadowOpacity = 1
self.layer.shadowRadius = 12
let lineView = UIView()
addSubview(lineView)
lineView.backgroundColor = UIColor.colorWithHexString("#C2C7CC")
lineView.layer.cornerRadius = 2
lineView.snp.makeConstraints { (make) in
make.width.equalTo(36)
make.height.equalTo(4)
make.centerX.equalToSuperview()
make.top.equalTo(12)
}
let label = UILabel()
addSubview(label)
label.text = "盘它"
label.textColor = .red
label.font = .boldSystemFont(ofSize: 22)
label.snp.makeConstraints { (make) in
make.top.equalTo(35)
make.centerX.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
if let touch = touches.first {
touchMovedClosure?(touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if let touch = touches.first {
touchEndedClosure?(touch)
}
}
}
最后
请认真看文章开头那张图的说明,这才是最重要的。


网友评论