私は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
}
誰かが私のコードを変更または書き直すのを手伝ってくれるなら、それは素晴らしいことです、事前に感謝します!
デフォルトでは、ビュー上の1つのジェスチャレコグナイザーがジェスチャの処理を開始した後、他のレコグナイザーは無視されます。この動作は、メソッドをオーバーライドすることで制御できます(Swiftの場合)。
ジェスチャレコグナイザー(_: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;
}
使いやすいクラスを書いています。
基本的な使用法:
// 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のイベントを食べる可能性があり、snapchatの例では、ジェスチャはツールバーのイベントを食べるため、ツールバーに触れている場合、ジェスチャがスーパービューから認識されないようにする必要があります。
アイデアは、ツールバーに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")
}
}