简体   繁体   English

Swift中的UIScrollView中的UIPanGesture

[英]UIPanGesture inside UIScrollView in Swift

The problem in short, related to working with pan gesture inside a scrollView. 简而言之,该问题与在scrollView中使用平移手势有关。

  1. I have a canvas(which is an UIView itself but bigger in size) where i am drawing some UIView objects with pan gesture enabled over each of them(Each little UIView Objects I am talking about, are making using another UIView class). 我有一块画布(它本身是UIView,但是尺寸较大),在其中我绘制了一些UIView对象,并在每个对象上启用了平移手势(我正在谈论的每个小UIView对象都在使用另一个UIView类)。

  2. Now the canvas can be bigger in height and width...which can be changed as per the user input. 现在,画布的高度和宽度可以更大...可以根据用户输入进行更改。

  3. So to achieve that I have placed the canvas inside a UIScrollView. 为此,我将画布放置在UIScrollView中。 Now the canvas is increasing or decreasing smoothly. 现在,画布正在平滑地增加或减少。

  4. Those tiny UIView objects on the canvas can be rotated also. 画布上的那些微小的UIView对象也可以旋转。

Now the problem. 现在的问题。

  1. If I am not changing the canvas size(static) ie if its not inside the scrollview then each UIView objects inside the canvas are moving superbly and everything is working just fine with the following code. 如果我不更改画布的大小(静态),即如果它不在scrollview内,则画布内的每个UIView对象都将运动良好,并且使用以下代码可以正常工作。

  2. If the canvas is inside the UIScrollView then the canvas can be scrollable right? 如果画布位于UIScrollView内,那么画布可以滚动吗? Now inside the scrollview if I am panning the UIView objects on the canvas then those little UIView objects are not following the touch of the finger rather than its moving on another point when touch is moving on the canvas. 现在,如果在滚动视图内部平移画布上的UIView对象,则那些小的UIView对象不会跟随手指的触摸,而不会跟随手指在画布上移动时在另一点上的移动。

NB - Somehow I figured out that I need to disable the scrolling of the scrollview when any of the subviews are getting touch. 注意:以某种方式我发现,当任何子视图接触时,需要禁用滚动视图的滚动。 For that thing I have implemented NSNotificationCenter to pass the signal to the parent viewController. 为此,我实现了NSNotificationCenter以将信号传递给父viewController。

Here is the code. 这是代码。

This functions are defined inside the parent viewController class 此函数在父viewController类中定义

func canvusScrollDisable(){
    print("Scrolling Off")
    self.scrollViewForCanvus.scrollEnabled = false
}
func canvusScrollEnable(){
    print("Scrolling On")
    self.scrollViewForCanvus.scrollEnabled = true
}

override func viewDidLoad() {
    super.viewDidLoad()
    notificationUpdate.addObserver(self, selector: "canvusScrollEnable", name: "EnableScroll", object: nil)
    notificationUpdate.addObserver(self, selector: "canvusScrollDisable", name: "DisableScroll", object: nil)
 }

This is the Subview class of the canvas 这是画布的Subview类

import UIKit

class ViewClassForUIView: UIView {
let notification: NSNotificationCenter = NSNotificationCenter.defaultCenter()

var lastLocation: CGPoint = CGPointMake(0, 0)
var lastOrigin = CGPoint()
var myFrame = CGRect()
var location = CGPoint(x: 0, y: 0)
var degreeOfThisView = CGFloat()
override init(frame: CGRect){
    super.init(frame: frame)

    let panRecognizer = UIPanGestureRecognizer(target: self, action: "detectPan:")
    self.backgroundColor = addTableUpperViewBtnColor
    self.multipleTouchEnabled = false
    self.exclusiveTouch = true
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func detectPan(recognizer: UIPanGestureRecognizer){
    let translation = recognizer.translationInView(self.superview!)
    self.center = CGPointMake(lastLocation.x + translation.x, lastLocation.y + translation.y)
    switch(recognizer.state){
    case .Began:
    break
    case .Changed:
    break
    case .Ended:
        notification.postNotificationName("EnableScroll", object: nil)
    default: break
    }
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    notification.postNotificationName("DisableScroll", object: nil) 
    self.superview?.bringSubviewToFront(self)
    lastLocation = self.center
    lastOrigin = self.frame.origin
    let radians:Double = atan2( Double(self.transform.b), Double(self.transform.a))
    self.degreeOfThisView = CGFloat(radians) * (CGFloat(180) / CGFloat(M_PI) )
    if self.degreeOfThisView != 0.0{
        self.transform = CGAffineTransformIdentity
        self.lastOrigin = self.frame.origin
        self.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))            
    }
    myFrame = self.frame
  }

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    notification.postNotificationName("EnableScroll", object: nil)
  }
   }

Now the scrollView is disabling its scroll perfectly whenever one of the UIView object is receiving touch over the canvas which is inside the scrollview but sometimes those UIView objects are not properly following the touch location over the canvas/screen. 现在,每当UIView对象之一在scrollview内的画布上接受触摸时,scrollView就会完全禁用其滚动,但是有时那些UIView对象未正确跟随画布/屏幕上的触摸位置。

I am using Swift 2.1 with Xcode 7 but anyone can tell me the missing things of mine or the solution using Objective-c/Swift? 我在Xcode 7上使用Swift 2.1,但是谁能告诉我我遗失的东西或使用Objective-c / Swift的解决方案?

Where do you set the lastLocation ? 您在哪里设置lastLocation I think it would be better for you to use locationInView and compute the translation by yourself. 我认为最好使用locationInView并自己计算翻译。 Then save the lastLocation on every event that triggers the method. 然后在触发该方法的每个事件上保存lastLocation

Also you might want to handle the Cancel state as well to turn the scrolling back on. 另外,您可能还想处理“取消”状态以重新打开滚动。

All of this does seem a bit messy though. 尽管所有这些看起来确实有些混乱。 The notifications are maybe not the best idea in your case nor is putting the gesture recognizers on the subviews. 通知可能不是您的最佳选择,也不是将手势识别器放在子视图上。 I think you should have a view which handles all those small views; 我认为您应该有一个可以处理所有这些小视图的视图; it should also have a gesture recognizer that can simultaneously interact with other recognizers. 它还应具有可以同时与其他识别器交互的手势识别器。 When the gesture is recognized it should check if any of the subviews are hit and decide if any of them should be moved. 识别手势后,应检查是否击中了任何子视图,并决定是否应移动它们。 If it should be moved then use the delegate to report that the scrolling must be disabled. 如果应移动它,则使用委托报告必须禁用滚动。 If not then cancel the recognizer (disable+enable does that). 如果没有,则取消识别器(禁用+启用该功能)。 Also in most cases where you put something movable on the scrollview you usually want a long press gesture recognizer and not a pan gesture recognizer. 同样,在大多数情况下,在滚动视图上放置可移动的内容时,通常需要长按手势识别器而不是平移手势识别器。 Simply use that one and set some very small minimum press duration. 只需使用那一个,并设置一些很小的最小印刷持续时间即可。 Note that this gesture works exactly the same as the pan gesture but can have a small delay to be detected. 请注意,此手势与平移手势完全相同,但检测到的延迟可能很小。 It is very useful in these kind of situations. 在这种情况下,它非常有用。

Update (The architecture): 更新(架构):

The hierarchy should be: 层次结构应为:

View controller -> Scrollview -> Canvas view -> Small views 视图控制器->滚动视图->画布视图->小视图

The canvas view should contain the gesture recognizer that controls the small views. 画布视图应包含控制小视图的手势识别器。 When the gesture begins you should check if any of the views are hit by its location by simply iterating through the subviews and check if their frame contains a point. 手势开始时,您应该通过简单地遍历子视图并检查其框架是否包含一个点来检查是否有任何视图被其位置击中。 If so it should start moving the hit small view and it should notify its delegate that it has began moving it. 如果是这样,它应该开始移动命中的小视图,并且应该通知其代表它已经开始移动它。 If not it should cancel the gesture recognizer. 如果不是,则应取消手势识别器。

As the canvas view has a custom delegate it is the view controller that should implement its protocol and assign itself to the canvas view as a delegate. 由于画布视图具有自定义委托,因此视图控制器应实现其协议,并将其自身作为委托分配给画布视图。 When the canvas view reports that the view dragging has begin it should disable the scrollview scrolling. 当画布视图报告视图拖动已开始时,应禁用滚动视图滚动。 When the canvas view reports it has stopped moving the views it should reenable the scrolling of the scroll view. 当画布视图报告其已停止移动视图时,应重新启用滚动视图的滚动。

  • Create this type of view hierarchy 创建这种类型的视图层次结构
  • Create a custom protocol of the canvas view which includes "did begin dragging" and "did end dragging" 创建画布视图的自定义协议,其中包括“已开始拖动”和“已结束拖动”
  • When the view controller becomes active assign self as a delegate to the canvas view. 当视图控制器变为活动状态时,将自己分配为画布视图的委托。 Implement the 2 methods to enable or disable the scrolling of the scroll view. 实现这两种方法来启用或禁用滚动视图的滚动。
  • The canvas view should add a gesture recognizer to itself and should contain an array of all the small movable subviews. 画布视图应在其自身上添加一个手势识别器,并应包含所有小型可移动子视图的数组。 The recognizer should be able to interact with other recognizers simultaneously which is done through its delegate. 识别器应该能够与其他识别器同时进行交互,这是通过其委托完成的。
  • The Canvas gesture recognizer target should on begin check if any of the small views are hit and save it as a property, it should also save the current position of the gesture. Canvas手势识别器目标应在开始检查是否击中了任何小视图并将其保存为属性后,还应保存手势的当前位置。 When the gesture changes it should move the grabbed view depending on the last and current gesture location and re-save the current location to the property. 当手势更改时,它应根据上一个和当前手势位置移动捕获的视图,并将当前位置重新保存到属性。 When the gesture ends it should clear the currently dragged view. 手势结束后,应清除当前拖动的视图。 On begin and end it should call the delegate to notify the change of the state. 在开始和结束时,应调用委托以通知状态更改。
  • Disable or enable the scrolling in the view controller depending on the canvas view reporting to delegate. 根据要委派的画布视图报告,禁用或启用视图控制器中的滚动。

I think this should be all. 我认为这应该是全部。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM