Swift 中的 UIScrollView 内的 UIPanGesture

Posted

技术标签:

【中文标题】Swift 中的 UIScrollView 内的 UIPanGesture【英文标题】:UIPanGesture inside UIScrollView in Swift 【发布时间】:2016-02-26 10:11:26 【问题描述】:

简而言之,这个问题与在滚动视图中使用平移手势有关。

    我有一个画布(它本身是一个 UIView,但尺寸更大),我正在绘制一些 UIView 对象,每个对象都启用了平移手势(我正在谈论的每个小 UIView 对象,正在使用另一个UIView 类)。

    现在画布的高度和宽度可以更大...可以根据用户输入进行更改。

    为了实现这一点,我将画布放置在 UIScrollView 中。现在画布正在平滑地增加或减少。

    画布上那些微小的 UIView 对象也可以旋转。

现在问题来了。

    如果我没有更改画布大小(静态),即如果它不在滚动视图内,那么画布内的每个 UIView 对象都在移动,并且使用以下代码一切正常。

    如果画布在 UIScrollView 内,那么画布可以滚动吗?现在在滚动视图内部,如果我在画布上平移 UIView 对象,那么这些小 UIView 对象不会跟随手指的触摸,而不是在触摸在画布上移动时移动到另一个点。

注意- 不知何故,我发现当任何子视图接触时,我需要禁用滚动视图的滚动。为此,我实现了 NSNotificationCenter 以将信号传递给父 viewController。

这是代码。

这些函数在父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)
 

这是画布的 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)
  
   

现在,只要 UIView 对象之一在滚动视图内的画布上接收触摸,scrollView 就会完美地禁用其滚动,但有时这些 UIView 对象未正确跟随画布/屏幕上的触摸位置。

我正在使用带有 Xcode 7 的 Swift 2.1,但任何人都可以告诉我我缺少的东西或使用 Objective-c/Swift 的解决方案吗?

【问题讨论】:

试试this ? @zcui93 感谢伙伴的帮助...让我尝试让您知道..:) 【参考方案1】:

你在哪里设置lastLocation?我认为您最好使用locationInView 并自己计算翻译。然后在触发该方法的每个事件上保存lastLocation

您可能还想处理取消状态以重新打开滚动。

不过,所有这些似乎都有些混乱。在您的情况下,通知可能不是最好的主意,也不是将手势识别器放在子视图上。我认为您应该有一个可以处理所有这些小视图的视图;它还应该有一个手势识别器,可以同时与其他识别器交互。当手势被识别时,它应该检查是否有任何子视图被击中并决定是否应该移动它们。如果应该移动它,则使用委托报告必须禁用滚动。如果不是,则取消识别器(禁用+启用)。同样在大多数情况下,在滚动视图上放置可移动的东西时,您通常需要长按手势识别器而不是平移手势识别器。只需使用那个并设置一些非常小的最小按下持续时间。请注意,此手势的工作原理与平移手势完全相同,但检测到的延迟可能很小。在这种情况下非常有用。

更新(架构):

层次结构应该是:

视图控制器 -> 滚动视图 -> 画布视图 -> 小视图

画布视图应包含控制小视图的手势识别器。当手势开始时,您应该通过简单地遍历子视图并检查它们的框架是否包含一个点来检查是否有任何视图被其位置击中。如果是这样,它应该开始移动命中的小视图,并且它应该通知它的委托它已经开始移动它。如果不是,它应该取消手势识别器。

由于画布视图有一个自定义委托,因此视图控制器应该实现其协议并将自己作为委托分配给画布视图。当画布视图报告视图拖动已经开始时,它应该禁用滚动视图滚动。当画布视图报告它已停止移动视图时,它应该重新启用滚动视图的滚动。

创建这种类型的视图层次结构 创建画布视图的自定义协议,其中包括“开始拖动”和“结束拖动” 当视图控制器变为活动状态时,将 self 作为代理分配给画布视图。实现 2 种方法来启用或禁用滚动视图的滚动。 画布视图应为其自身添加一个手势识别器,并应包含所有小型可移动子视图的数组。识别器应该能够通过其委托同时与其他识别器交互。 Canvas 手势识别器目标应在开始检查是否有任何小视图被击中并将其保存为属性,它还应保存手势的当前位置。当手势改变时,它应该根据最后和当前的手势位置移动抓取的视图,并将当前位置重新保存到属性中。当手势结束时,它应该清除当前拖动的视图。在开始和结束时,它应该调用委托来通知状态的变化。 根据要委托的画布视图报告禁用或启用视图控制器中的滚动。

我想这应该就是全部了。

【讨论】:

感谢您的想法..是的!这些子视图位于称为画布的超级视图类型中。画布在滚动视图内...如果可能的话,您能否给出一些解释或一些 sudo 类型代码,或者可能有点笨拙... 酷...!!!!!!只是酷伙伴..您的努力无话可说...顺便说一句,我已经解决了有关视图的一般问题,即没有通过使用您的想法来跟踪屏幕上的触摸“您在哪里设置 lastLocation?我认为这会更好供您使用 locationInView 并自行计算翻译。然后将 lastLocation 保存在触发该方法的每个事件上。”......因为酷而清晰的想法,所以只给出答案 +1......:)

以上是关于Swift 中的 UIScrollView 内的 UIPanGesture的主要内容,如果未能解决你的问题,请参考以下文章

Swift - UIScrollView 内的 UICollectionView - 自动布局

如何在 swift 中设置 UIScrollView 内的全宽图像

Swift AutoLayout:UIScrollView 内的 UITextViews 让人头疼

分页 UIScrollView 内的 UIScrollView

swift 3中的错误UIScrollView

UIScrollView 中的 Swift-Xcode UILabel