前言:
之前写了两份相关的,
思路就是从 touch 相关手势,识别点,直接绘制
不方便,简单地添加动画
本文介绍另一思路,通过控件
有参考 rzmn/CropView
思路具体
1, 拿到点,还是同样的三个方法,
touchesBegan, touchesMoved, touchesEnded
2, 视图层级
- 功能视图
cropView, 添加在 image view 上,
功能视图 cropView 中, 识别手势
- 画线,四条边,
var areaQuadrangle = SEAreaView()
添加在功能视图 cropView 上
2.1, 方便做动画,四个角都是控件, 添加在功能视图 cropView 上
class CornerView: UIView
拖动某个角,那个角落的圆圈变大;
放开那个角,那个角落的圆圈缩回
3,坐标的对应关系
imageView 设置为 sizeToFit,
默认,imageView 的 size > 上面看到的 image 的 size
( 当然,image.size 图片数据的 size 很大的 )
功能视图 cropView 的 frame = 上面看到的 image 的 frame
那么画线,四条边,var areaQuadrangle = SEAreaView() 的 frame = 功能视图 cropView 的 bounds
坐标: var cornerLocations : [CGPoint]?
这个是外部第一次传入的,或者外部需要修改传入的,
同时,他也是手势移动 touchesMoved 识别到的
实现
配置
func configure(corners imageView: UIImageView) {
// 配置功能视图
self.imageView = imageView
self.imageView?.isUserInteractionEnabled = true
imageView.addSubview(self)
// 初始化
for subview in subviews {
if subview is SECornerView {
subview.removeFromSuperview()
}
}
// 配置四个角
for _ in 0..<Setting.std.cornerCount {
let corner = SECornerView(frame: CGRect(x: 0, y: 0, width: Setting.std.cornerSize, height: Setting.std.cornerSize))
addSubview(corner)
cornerViews.append(corner)
}
// 配置四条边
areaQuadrangle.backgroundColor = .clear
addSubview(areaQuadrangle)
}
功能视图 cropView 的 frame = 上面看到的 image 的 frame
第一次进来,实现上面那句
因为一般在 viewDidLoad 里面配置,这时候,imageView 的 frame 还没确定下来
控件内部实现的合适时机是,func layoutSubviews(), 需要走一次
public override func layoutSubviews() {
super.layoutSubviews()
guard first else {
return
}
if let imgsize = imageView?.image?.size, let imageBounds = imageView?.bounds {
let f = AVMakeRect(aspectRatio: imgsize, insideRect: imageBounds)
frame = f
}
first = false
// 从 bounds, 算出内部四个角落的点
let f = bounds
let first = f.origin
let rhsTop = CGPoint(x: first.x + f.width, y: first.y)
let lhsHip = CGPoint(x: first.x, y: first.y + f.height)
let end = CGPoint(x: rhsTop.x, y: lhsHip.y)
let dots = [first, rhsTop, end, lhsHip]
self.cornerLocations = dots
areaQuadrangle.frame = bounds
update(scale: nil)
cornerOnTouch = nil
}
AVMakeRect 方法挺好用的,直接把 imageView 内部 image sizeToFit 的 size, 算出来了
手势移动识别
当然,至少还有两个手势
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
guard let touchIdx = cornerOnTouch, touches.count == 1, let first = touches.first, let cornerLocations = cornerLocations, let img = imageView?.image else { return }
// 拿到点,
// 通过两个点的距离 + 起始点 cornerLocations[touchIdx].x , 稍微繁琐
let from = first.previousLocation(in: self)
let to = first.location(in: self)
let derivative = CGPoint(x: to.x - from.x, y: to.y - from.y)
let rawPt = CGPoint(x: cornerLocations[touchIdx].x + derivative.x, y: cornerLocations[touchIdx].y + derivative.y)
// 确保不越界
let newCenterOnImage = rawPt.normalized(size: img.size)
// 更新数据
self.cornerLocations?[touchIdx] = newCenterOnImage
// 效果
update(scale: nil)
}
效果
func update(scale isBigger: Bool?) {
guard let touchIdx = cornerOnTouch else {
return
}
// 更新位置
// 更新画线
pairPositionsAndViews()
if let bigger = isBigger{
switch bigger {
case true:
// 角落圆圈,动画缩放
cornerViews[touchIdx].scaleUp()
case false:
cornerViews[touchIdx].scaleDown()
}
}
// 角落圆圈,涂色
for corner in cornerViews {
corner.layer.borderColor = (isPathValid ? Setting.std.goodAreaColor : Setting.std.badAreaColor).cgColor
}
}
更新位置
func pairPositionsAndViews() {
// 控件更新位置,就是改中心点
if let cornerPositions = self.cornerLocations {
for i in 0 ..< Setting.std.cornerCount {
self.cornerViews[i].center = CGPoint(x: cornerPositions[i].x, y: cornerPositions[i].y)
}
}
// 更新画线
self.areaQuadrangle.fill(path: path)
}
动画
控件动画,简单
class CornerView: UIView {
// 放大动画
func scaleUp() {
UIView.animate(withDuration: 0.15, animations: {
self.layer.borderWidth = 0.5
self.transform = CGAffineTransform.identity.scaledBy(x: 2, y: 2)
})
}
// 缩小动画类似
}
交叉重联
如果一个点拖动,越过对面的边,导致交叉了
如果是凸四边形,就重新连接
这段逻辑的触发时机是 touchesEnded
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
guard cornerOnTouch != nil, touches.count == 1 else {
return
}
// 交叉重联
sortPointClockwise()
// 初始化
update(scale: false)
cornerOnTouch = nil
}
交叉重联,在篇首的博客里面有讲解,这里略