照片選擇區域功能的另一實現: 加動效
前言:
之前寫了兩份相關的,
思路就是從 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()
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
}
交叉重聯,在篇首的部落格裡面有講解,這裡略