一种使用 UIPinchGestureRecognizer UIRotationGestureRecognizer 和 UIPanGestureRecognizer 添加类似 Instagram 的布
Posted
技术标签:
【中文标题】一种使用 UIPinchGestureRecognizer UIRotationGestureRecognizer 和 UIPanGestureRecognizer 添加类似 Instagram 的布局指南的方法?【英文标题】:A way to add Instagram like layout guide with UIPinchGestureRecognizer UIRotationGestureRecognizer & UIPanGestureRecognizer? 【发布时间】:2018-12-29 05:56:13 【问题描述】:我使用 UIPinchGestureRecognizer
UIPanGestureRecognizer
& UIRotationGestureRecognizer
和 UILabel
来实现 Instagram 之类的缩放和拖动功能。现在我想显示布局指南,例如当UILabel
被拖到中心时,它应该显示布局指南,如下例所示。当您旋转UILabel
时,它还应该显示布局指南。
实现此功能的最佳且准确的方法是什么?
这是我已经拥有的
(图片取自this question@Skiddswarmik)
这是我用于简单拖动和缩放功能的代码(取自 this answer @lbsweek)
SnapGesture 类
import UIKit
/*
usage:
add gesture:
yourObjToStoreMe.snapGesture = SnapGesture(view: your_view)
remove gesture:
yourObjToStoreMe.snapGesture = nil
disable gesture:
yourObjToStoreMe.snapGesture.isGestureEnabled = false
advanced usage:
view to receive gesture(usually superview) is different from view to be transformed,
thus you can zoom the view even if it is too small to be touched.
yourObjToStoreMe.snapGesture = SnapGesture(transformView: your_view_to_transform, gestureView: your_view_to_recieve_gesture)
*/
class SnapGesture: NSObject, UIGestureRecognizerDelegate
// MARK: - init and deinit
convenience init(view: UIView)
self.init(transformView: view, gestureView: view)
init(transformView: UIView, gestureView: UIView)
super.init()
self.addGestures(v: gestureView)
self.weakTransformView = transformView
deinit
self.cleanGesture()
// MARK: - private method
private weak var weakGestureView: UIView?
private weak var weakTransformView: UIView?
private var panGesture: UIPanGestureRecognizer?
private var pinchGesture: UIPinchGestureRecognizer?
private var rotationGesture: UIRotationGestureRecognizer?
private func addGestures(v: UIView)
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panProcess(_:)))
v.isUserInteractionEnabled = true
panGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(panGesture!)
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchProcess(_:)))
//view.isUserInteractionEnabled = true
pinchGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(pinchGesture!)
rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotationProcess(_:)))
rotationGesture?.delegate = self
v.addGestureRecognizer(rotationGesture!)
self.weakGestureView = v
private func cleanGesture()
if let view = self.weakGestureView
//for recognizer in view.gestureRecognizers ?? []
// view.removeGestureRecognizer(recognizer)
//
if panGesture != nil
view.removeGestureRecognizer(panGesture!)
panGesture = nil
if pinchGesture != nil
view.removeGestureRecognizer(pinchGesture!)
pinchGesture = nil
if rotationGesture != nil
view.removeGestureRecognizer(rotationGesture!)
rotationGesture = nil
self.weakGestureView = nil
self.weakTransformView = nil
// MARK: - API
private func setView(view:UIView?)
self.setTransformView(view, gestgureView: view)
private func setTransformView(_ transformView: UIView?, gestgureView:UIView?)
self.cleanGesture()
if let v = gestgureView
self.addGestures(v: v)
self.weakTransformView = transformView
open func resetViewPosition()
UIView.animate(withDuration: 0.4)
self.weakTransformView?.transform = CGAffineTransform.identity
open var isGestureEnabled = true
// MARK: - gesture handle
// location will jump when finger number change
private var initPanFingerNumber:Int = 1
private var isPanFingerNumberChangedInThisSession = false
private var lastPanPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func panProcess(_ recognizer:UIPanGestureRecognizer)
if isGestureEnabled
//guard let view = recognizer.view else return
guard let view = self.weakTransformView else return
// init
if recognizer.state == .began
lastPanPoint = recognizer.location(in: view)
initPanFingerNumber = recognizer.numberOfTouches
isPanFingerNumberChangedInThisSession = false
// judge valid
if recognizer.numberOfTouches != initPanFingerNumber
isPanFingerNumberChangedInThisSession = true
if isPanFingerNumberChangedInThisSession
return
// perform change
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPanPoint.x, y: point.y - lastPanPoint.y)
lastPanPoint = recognizer.location(in: view)
private var lastScale:CGFloat = 1.0
private var lastPinchPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func pinchProcess(_ recognizer:UIPinchGestureRecognizer)
if isGestureEnabled
guard let view = self.weakTransformView else return
// init
if recognizer.state == .began
lastScale = 1.0;
lastPinchPoint = recognizer.location(in: view)
// judge valid
if recognizer.numberOfTouches < 2
lastPinchPoint = recognizer.location(in: view)
return
// Scale
let scale = 1.0 - (lastScale - recognizer.scale);
view.transform = view.transform.scaledBy(x: scale, y: scale)
lastScale = recognizer.scale;
// Translate
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPinchPoint.x, y: point.y - lastPinchPoint.y)
lastPinchPoint = recognizer.location(in: view)
@objc func rotationProcess(_ recognizer: UIRotationGestureRecognizer)
if isGestureEnabled
guard let view = self.weakTransformView else return
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
//MARK:- UIGestureRecognizerDelegate Methods
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool
return true
在
UILabel
中添加手势
// define
var snapGesture: SnapGesture?
// add gesture
self.snapGesture = SnapGesture(view: self.myLabel!)
【问题讨论】:
您说希望在以下情况下出现指南: a) 文本在屏幕上水平居中。 b)当你旋转物体时?您能否添加更多关于 b) 的信息?谢谢 添加了 gif 以便您获得更多详细信息。 【参考方案1】:您将在下面找到您所描述的课程的更新版本。
大部分更新的代码位于接近末尾的最后一部分(指南),但我已经稍微更新了您的 UIGestureRecognizer
操作以及您的主要 init
方法。
特点:
- 用于水平居中视图位置的垂直参考线。
- 用于将视图旋转居中于 0 度的水平参考线。
- 位置和旋转捕捉到具有公差值的参考线(snapToleranceDistance
和 snapToleranceAngle
属性)。
- 指南的动画外观/消失(animateGuides
和 guideAnimationDuration
属性)。
- 可以根据用例更改的指南视图(movementGuideView
和 rotationGuideView
属性)
class SnapGesture: NSObject, UIGestureRecognizerDelegate
// MARK: - init and deinit
convenience init(view: UIView)
self.init(transformView: view, gestureView: view)
init(transformView: UIView, gestureView: UIView)
super.init()
self.addGestures(v: gestureView)
self.weakTransformView = transformView
guard let transformView = self.weakTransformView, let superview = transformView.superview else
return
// This is required in order to be able to snap the view to center later on,
// using the `tx` property of its transform.
transformView.center = superview.center
deinit
self.cleanGesture()
// MARK: - private method
private weak var weakGestureView: UIView?
private weak var weakTransformView: UIView?
private var panGesture: UIPanGestureRecognizer?
private var pinchGesture: UIPinchGestureRecognizer?
private var rotationGesture: UIRotationGestureRecognizer?
private func addGestures(v: UIView)
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panProcess(_:)))
v.isUserInteractionEnabled = true
panGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(panGesture!)
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchProcess(_:)))
//view.isUserInteractionEnabled = true
pinchGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(pinchGesture!)
rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotationProcess(_:)))
rotationGesture?.delegate = self
v.addGestureRecognizer(rotationGesture!)
self.weakGestureView = v
private func cleanGesture()
if let view = self.weakGestureView
//for recognizer in view.gestureRecognizers ?? []
// view.removeGestureRecognizer(recognizer)
//
if panGesture != nil
view.removeGestureRecognizer(panGesture!)
panGesture = nil
if pinchGesture != nil
view.removeGestureRecognizer(pinchGesture!)
pinchGesture = nil
if rotationGesture != nil
view.removeGestureRecognizer(rotationGesture!)
rotationGesture = nil
self.weakGestureView = nil
self.weakTransformView = nil
// MARK: - API
private func setView(view:UIView?)
self.setTransformView(view, gestgureView: view)
private func setTransformView(_ transformView: UIView?, gestgureView:UIView?)
self.cleanGesture()
if let v = gestgureView
self.addGestures(v: v)
self.weakTransformView = transformView
open func resetViewPosition()
UIView.animate(withDuration: 0.4)
self.weakTransformView?.transform = CGAffineTransform.identity
open var isGestureEnabled = true
// MARK: - gesture handle
// location will jump when finger number change
private var initPanFingerNumber:Int = 1
private var isPanFingerNumberChangedInThisSession = false
private var lastPanPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func panProcess(_ recognizer:UIPanGestureRecognizer)
guard isGestureEnabled, let view = self.weakTransformView else return
// init
if recognizer.state == .began
lastPanPoint = recognizer.location(in: view)
initPanFingerNumber = recognizer.numberOfTouches
isPanFingerNumberChangedInThisSession = false
// judge valid
if recognizer.numberOfTouches != initPanFingerNumber
isPanFingerNumberChangedInThisSession = true
if isPanFingerNumberChangedInThisSession
hideGuidesOnGestureEnd(recognizer)
return
// perform change
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPanPoint.x, y: point.y - lastPanPoint.y)
lastPanPoint = recognizer.location(in: view)
updateMovementGuide()
hideGuidesOnGestureEnd(recognizer)
private var lastScale:CGFloat = 1.0
private var lastPinchPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func pinchProcess(_ recognizer:UIPinchGestureRecognizer)
guard isGestureEnabled, let view = self.weakTransformView else return
// init
if recognizer.state == .began
lastScale = 1.0;
lastPinchPoint = recognizer.location(in: view)
// judge valid
if recognizer.numberOfTouches < 2
lastPinchPoint = recognizer.location(in: view)
hideGuidesOnGestureEnd(recognizer)
return
// Scale
let scale = 1.0 - (lastScale - recognizer.scale);
view.transform = view.transform.scaledBy(x: scale, y: scale)
lastScale = recognizer.scale;
// Translate
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPinchPoint.x, y: point.y - lastPinchPoint.y)
lastPinchPoint = recognizer.location(in: view)
updateMovementGuide()
hideGuidesOnGestureEnd(recognizer)
@objc func rotationProcess(_ recognizer: UIRotationGestureRecognizer)
guard isGestureEnabled, let view = self.weakTransformView else return
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
updateRotationGuide()
hideGuidesOnGestureEnd(recognizer)
func hideGuidesOnGestureEnd(_ recognizer: UIGestureRecognizer)
if recognizer.state == .ended
showMovementGuide(false)
showRotationGuide(false)
// MARK:- UIGestureRecognizerDelegate Methods
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool
return true
// MARK:- Guides
var animateGuides = true
var guideAnimationDuration: TimeInterval = 0.3
var snapToleranceDistance: CGFloat = 5 // pts
var snapToleranceAngle: CGFloat = 1 // degrees
* CGFloat.pi / 180 // (converted to radians)
var movementGuideView: UIView =
let view = UIView()
view.backgroundColor = UIColor.blue
return view
()
var rotationGuideView: UIView =
let view = UIView()
view.backgroundColor = UIColor.red
return view
()
// MARK: Movement guide and snap
func updateMovementGuide()
guard let transformView = weakTransformView, let superview = transformView.superview else
return
let transformX = transformView.frame.midX
let superX = superview.bounds.midX
if transformX - snapToleranceDistance < superX && transformX + snapToleranceDistance > superX
transformView.transform.tx = 0
showMovementGuide(true)
else
showMovementGuide(false)
updateGuideFrames()
var isShowingMovementGuide = false
func showMovementGuide(_ shouldShow: Bool)
guard isShowingMovementGuide != shouldShow,
let transformView = weakTransformView,
let superview = transformView.superview
else return
superview.insertSubview(movementGuideView, belowSubview: transformView)
movementGuideView.frame = CGRect(
x: superview.frame.midX,
y: 0,
width: 1,
height: superview.frame.size.height
)
let duration = animateGuides ? guideAnimationDuration : 0
isShowingMovementGuide = shouldShow
UIView.animate(withDuration: duration) [weak self] in
self?.movementGuideView.alpha = shouldShow ? 1 : 0
// MARK: Rotation guide and snap
func updateRotationGuide()
guard let transformView = weakTransformView else
return
let angle = atan2(transformView.transform.b, transformView.transform.a)
if angle > -snapToleranceAngle && angle < snapToleranceAngle
transformView.transform = transformView.transform.rotated(by: angle * -1)
showRotationGuide(true)
else
showRotationGuide(false)
var isShowingRotationGuide = false
func showRotationGuide(_ shouldShow: Bool)
guard isShowingRotationGuide != shouldShow,
let transformView = weakTransformView,
let superview = transformView.superview
else return
superview.insertSubview(rotationGuideView, belowSubview: transformView)
let duration = animateGuides ? guideAnimationDuration : 0
isShowingRotationGuide = shouldShow
UIView.animate(withDuration: duration) [weak self] in
self?.rotationGuideView.alpha = shouldShow ? 1 : 0
func updateGuideFrames()
guard let transformView = weakTransformView,
let superview = transformView.superview
else return
rotationGuideView.frame = CGRect(
x: 0,
y: transformView.frame.midY,
width: superview.frame.size.width,
height: 1
)
对于任何感兴趣的人,这里有一个 test project 使用这个类。
【讨论】:
非常感谢您的出色工作。我有更新问题,当它被击中时,有一些问题标签框架被改变了。 谢谢。嗯,你能举个例子让我看看吗? 嗯,我无法在此处重现...您可能在其中一个视图中使用 AutoLayout 吗? 我已经尝试通过将标签视图的水平和垂直中心约束添加到其超级视图的中心,它似乎在这里工作正常,一直回到 ios 8.0。您能否提供一些有关您的约束的更多信息,或者您的代码的链接,以便我看一下? 不用担心,我会在最后修复它,非常感谢您的工作,它真的对我有很大帮助以上是关于一种使用 UIPinchGestureRecognizer UIRotationGestureRecognizer 和 UIPanGestureRecognizer 添加类似 Instagram 的布的主要内容,如果未能解决你的问题,请参考以下文章
在 Typescript/React 中使用一种或另一种接口类型
jQuery Datepicker:向用户显示一种日期格式,在后台使用另一种日期格式