我正在尝试创建一个可以像Snapchat一样移动的TextView。我做了类似的东西,虽然当你尝试旋转时进行缩放时,它往往会无限地水平拉伸,拖动有时可能会有点儿。
我有这个:
func panGesture(pan: UIPanGestureRecognizer) {
print("Being Dragged")
if pan.state == .began {
textViewOrigin = pan.location(in: textView)
}else {
let location = pan.location(in: view) // get pan location
textView.frame.origin = CGPoint(x: location.x - textViewOrigin.x, y: location.y - textViewOrigin.y)
}
}
func scaleGesture(_ gesture: UIPinchGestureRecognizer){
print("Being Scaled")
switch gesture.state{
case.began:
identity = textView.transform
case.changed,.ended:
textView.transform = identity.scaledBy(x: gesture.scale, y: gesture.scale)
default:
break
}
}
func rotationGesture(sender: UIRotationGestureRecognizer){
print("Being Rotated")
textView.transform = textView.transform.rotated(by: sender.rotation)
sender.rotation = 0
}
如果有人可以帮助改变或重新编写我认为非常好的代码,请提前感谢!
答案 0 :(得分:18)
默认情况下,在视图上的一个手势识别器开始处理手势后,其他识别器将被忽略。可以通过覆盖方法(对于Swift)来控制此行为:
gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
返回true。默认实现返回false。要覆盖该函数,只需将实现(返回true)添加到ViewController源代码文件中。
以下是一些示例Swift代码(请参阅https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate/1624208-gesturerecognizer?changes=_7):
class ViewController: UIViewController,UIGestureRecognizerDelegate {
@IBOutlet var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//add pan gesture
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
gestureRecognizer.delegate = self
textField.addGestureRecognizer(gestureRecognizer)
//Enable multiple touch and user interaction for textfield
textField.isUserInteractionEnabled = true
textField.isMultipleTouchEnabled = true
//add pinch gesture
let pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(pinchRecognized(pinch:)))
pinchGesture.delegate = self
textField.addGestureRecognizer(pinchGesture)
//add rotate gesture.
let rotate = UIRotationGestureRecognizer.init(target: self, action: #selector(handleRotate(recognizer:)))
rotate.delegate = self
textField.addGestureRecognizer(rotate)
}
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view)
// note: 'view' is optional and need to be unwrapped
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
}
}
func pinchRecognized(pinch: UIPinchGestureRecognizer) {
if let view = pinch.view {
view.transform = view.transform.scaledBy(x: pinch.scale, y: pinch.scale)
pinch.scale = 1
}
}
func handleRotate(recognizer : UIRotationGestureRecognizer) {
if let view = recognizer.view {
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}
}
//MARK:- UIGestureRecognizerDelegate Methods
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
}
在Objective C中,重写函数函数就是这个(参见https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate/1624208-gesturerecognizer?language=objc):
-(BOOL)gestureRecognizer:(UIGestureRecognizer*)aR1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)aR2
{
return YES;
}
答案 1 :(得分:3)
我编写了一个易于使用的类。
基本用法:
// define
var snapGesture: SnapGesture?
// add gesture
self.snapGesture = SnapGesture(view: self.testView!)
// remove gesture
self.snapGesture = nil
高级用法,用于视图接收手势为背景视图的情况:
// add gesture
self.snapGesture = SnapGesture(transformView: self.testView!, gestureView: self.view)
// remove gesture
self.snapGesture = nil
班级:
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
}
}
现在,您到此为止,请继续。 如您所知,superView中的手势可能会吃掉subView的事件,而在gmail示例中,手势将吃掉工具栏的事件,因此,如果我们触摸工具栏,则需要防止任何手势被Superview识别。
这个想法是向工具栏添加一个pseduo定制手势,因此将阻止任何手势进行超级视图,并且该pseduo手势除了将手势或事件传递到子视图或自己的视图外,什么也不做。
在这里,我还编写了一个易于使用的类。
用法:
toolbarView.addGestureRecognizer(SnapBlockGestureRecognizer)
实现:
import UIKit
class SnapBlockGestureRecognizer: UIGestureRecognizer {
init() {
//self.init(target: self, action: #selector(__dummyAction))
super.init(target: nil, action: nil)
self.addTarget(self, action: #selector(__dummyAction))
self.cancelsTouchesInView = false
}
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.cancelsTouchesInView = false
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if self.state == .possible {
self.state = .began
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .recognized
}
override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
return self.isGestureRecognizerAllowed(gr:preventingGestureRecognizer)
}
override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
return !(self.isGestureRecognizerAllowed(gr: preventedGestureRecognizer))
}
override func shouldBeRequiredToFail(by otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return !(self.isGestureRecognizerAllowed(gr: otherGestureRecognizer))
}
override func shouldRequireFailure(of otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func isGestureRecognizerAllowed(gr: UIGestureRecognizer) -> Bool {
return gr.view!.isDescendant(of: self.view!)
}
@objc func __dummyAction() {
// do nothing
// print("dummyAction")
}
}