【问题标题】:UILongPressGestureRecognizer breaks tableView scrollingUILongPressGestureRecognizer 打破 tableView 滚动
【发布时间】:2017-11-29 15:58:12
【问题描述】:

我创建了一个包含 UILongPressGestureRecognizer 的自定义 Label 类,我在 TableViewController 的 tableview 单元格中调用它。长按手势识别器有效(属性字符串中有两个可点击区域),但如果滚动手势从我的 CustomLabel 的 UILongPressGestureRecognizer 区域之一开始,则包含标签的 tableView 不再滚动(平移)。我试过cancelsTouchesInView = false 以及下面的各种回复,但无济于事。任何建议将不胜感激。我花了一个星期来解决这个问题。我的代码如下。

这是 CustomLabel 类:

class CustomLabel: UILabel {

    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: CGSize.zero)
    var textStorage = NSTextStorage() {
        didSet {
            textStorage.addLayoutManager(layoutManager)
        }
    }

    var onCharacterTapped: ((_ label: UILabel, _ characterIndex: Int, _ state: Bool) -> Void)?

    let tapGesture = UILongPressGestureRecognizer()

    override var attributedText: NSAttributedString? {
        didSet {
            if let attributedText = attributedText {

                if attributedText.string != textStorage.string {

                textStorage = NSTextStorage(attributedString: attributedText)

                DispatchQueue.main.async {

                    let characterDelay = TimeInterval(0.01 + Float(arc4random()) /  Float(UInt32.max)) / 100

                    for (index, char) in attributedText.string.characters.enumerated() {

                        DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
                            print("character ch is: \(char) at index: \(index)")
                            super.attributedText = attributedText.attributedSubstring(from: NSRange(location: 0, length: index+1))
                        }
                    }
                }
                }

            } else {
                textStorage = NSTextStorage()
            }
        }
    }

    override var lineBreakMode: NSLineBreakMode {
        didSet {
            textContainer.lineBreakMode = lineBreakMode
        }
    }

    override var numberOfLines: Int {
        didSet {
            textContainer.maximumNumberOfLines = numberOfLines
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUp()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setUp()
    }

    func setUp() {
        isUserInteractionEnabled = true
        layoutManager.addTextContainer(textContainer)
        textContainer.lineFragmentPadding = 0
        textContainer.lineBreakMode = lineBreakMode
        textContainer.maximumNumberOfLines = numberOfLines
        tapGesture.addTarget(self, action: #selector(CustomLabel.labelTapped(_:)))
        tapGesture.minimumPressDuration = 0
        tapGesture.cancelsTouchesInView = false
        //tapGesture.delegate = self.superview
        addGestureRecognizer(tapGesture)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        textContainer.size = bounds.size
    }

    func labelTapped(_ gesture: UILongPressGestureRecognizer) {

        let locationOfTouch = gesture.location(in: gesture.view)
        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        let textContainerOffset = CGPoint(x: (bounds.width - textBoundingBox.width) / 2 - textBoundingBox.minX, y: (bounds.height - textBoundingBox.height) / 2 - textBoundingBox.minY)
        let locationOfTouchInTextContainer = CGPoint(x: locationOfTouch.x - textContainerOffset.x, y: locationOfTouch.y - textContainerOffset.y)
        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        if gesture.state == .began {

            onCharacterTapped?(self, indexOfCharacter, true)

        } else if gesture.state == .ended {

            onCharacterTapped?(self, indexOfCharacter, false)

        }

    }

}

这是cellClass:

class friendTextCell: UITableViewCell {

    @IBOutlet weak var labelText: CustomLabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        self.layoutIfNeeded()
    }

}

这里是来自创建 CustomCells 的 TableViewControllerClass 的选择:

class UsersViewController: UITableViewController, UIGestureRecognizerDelegate {

private func gestureRecognizer(gestureRecognizer: UIPanGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UILongPressGestureRecognizer) -> Bool {return true}


    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return gestureRecognizer === longPressRecognizer &&
            (otherGestureRecognizer.view?.isDescendant(of:tableView) ?? false)
    }

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "friendText", for: indexPath) as! friendTextCell

        print("keyArrrrray is: \(keyArray)")

        if indexPath.section == 0 && indexPath.row < keyArray.count {
            self.removeInstructions()
            cell.labelText.font = cell.labelText.font.withSize(17)
            let text = "> "+namesArray[indexPath.row] + ": " + linkArray[indexPath.row]
            let name = namesArray[indexPath.row]
            let link = linkArray[indexPath.row]
            let imageLink = imageURLArray[indexPath.row]
            let nameChCount = name.characters.count
            let linkChCount = link.characters.count

            let attributedString = NSMutableAttributedString(string: name + ": " + link, attributes: nil)

            let totalChCount = attributedString.string.characters.count

            let linkRange = NSMakeRange(0, nameChCount) // for the word "link" in the string above

            let linkAttributes: [String : AnyObject] = [
                NSForegroundColorAttributeName : UIColor.white, NSUnderlineStyleAttributeName : NSUnderlineStyle.styleSingle.rawValue as AnyObject]
            attributedString.setAttributes(linkAttributes, range:linkRange)

            cell.labelText.attributedText = attributedString

            cell.labelText.onCharacterTapped = { label, characterIndex, state in

                let highlight: [String : AnyObject] = [NSForegroundColorAttributeName : UIColor.black, NSBackgroundColorAttributeName : UIColor.white]

                if state == true {
                    if characterIndex < nameChCount {
                        print("name press began at character \(characterIndex)")
                        attributedString.addAttributes(highlight, range:NSMakeRange(0, nameChCount))
                        cell.labelText.attributedText = attributedString
                    } else if characterIndex > nameChCount {
                        print("link press began at character \(characterIndex)")
                        let startPos = nameChCount + 2
                        let endPos = totalChCount-nameChCount-2
                        attributedString.addAttributes(highlight, range:NSMakeRange(startPos, endPos))
                        cell.labelText.attributedText = attributedString
                    }

                } else if state == false {

                    if characterIndex < name.characters.count {

                        if let userVC:UserViewTableViewController = self.storyboard?.instantiateViewController(withIdentifier: "UserVC") as? UserViewTableViewController {
                            userVC.userName = name
                            userVC.shareLink = link
                            userVC.imageLink = imageLink
                            self.navigationController?.pushViewController(userVC, animated: true)
                        }

                }

                if characterIndex > name.characters.count && characterIndex <= link.characters.count + name.characters.count {

                    //extract link from array
                    let link = self.linkArray[indexPath.row]
                    print("on click link is: \(link)")

                    //Present SafariViewController with link
                    let svc = SFSafariViewController(url: NSURL(string: link)! as URL)
                    self.present(svc, animated: true, completion: nil)

                }
                }

            }

        } else if keyArray.isEmpty && indexPath.section == 0 {

            cell.labelText.text = "..."

        }

        if indexPath.section == 1 && keyArray.count <= 1 {
            let message = "> Press the + button to add more friends."
            cell.labelText.animate(newText: message, characterDelay: TimeInterval(0.01 + Float(arc4random()) /  Float(UInt32.max)) / 200)
        } else if indexPath.section == 1 {
            cell.labelText.text = ""
        }

        return cell

    }

【问题讨论】:

  • 尝试实现名为optional public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -&gt; BoolUIGestureRecognizerDelegate方法返回true
  • 感谢您的回复。我在 CustomCell 类和 tableViewController 类中也试过这个。没用。你会把这个方法放在哪里?
  • 我会发布一些例子
  • @ReinierMelian,我在上面的问题中添加了更多代码,因为我的问题似乎源于无法在 tableViewController 类和 CustomLabel 类之间进行通信......?
  • 问题是tableView.panGestureRecognizer.delegate不能分配给viewController

标签: ios swift uilabel uitapgesturerecognizer


【解决方案1】:

替换

tapGesture.minimumPressDuration = 0

tapGesture.minimumPressDuration = 0.5

识别开始得太早,没有让桌子接触

【讨论】:

  • 感谢您的建议。我试过这个,它导致了相反的问题。 tableview 滚动抓取手势,但 CustomLabel 手势识别器没有响应。我需要 tableView 和 CustomLabel 的手势识别器来接收相同的触发器(理想情况下不会引入延迟)。
  • 所以我猜@reinier-melian 解决方案是最好的
【解决方案2】:

如果滚动手势从 UILongPressGestureRecognizer 之一开始,则包含标签的 tableView 不再滚动(平移)

当您将 UILongPressGestureRecognizer 的最小按下持续时间设置为 0 并开始抓取滚动视图的嵌入式手势识别器时,会出现此问题。您可以通过使用更大的延迟来解决它,如果您需要使用延迟的长按手势识别器,它应该是怎样的。 首先将响应您的表格视图的didSelectRow at,然后延迟您的选择器。 它对我有用,虽然我在声明识别器的选择器方法之前删除了tapGesture.cancelsTouchesInView = false 并添加了属性@objc(如果你用swift 4编写则需要)。

如果您想立即使用UILongPressGestureRecognizer,只需使用UITapGestureRecognizer。在这种情况下,表格视图将滚动,但如果您点击标签,您将无法收到didSelectRow 方法。 表和集合视图手势代表设置为底层滚动视图,因此您不能为此目的在您的UIViewController 上使用UIGestureRecognizerDelegate 方法。 如果您想在识别器触发时在 View Controller 中接收某种回调,您可以通过实现委托方法来抛出回调。

例如创建实现labelTapped 函数的CustomLabel 的委托。在您的选择器中调用该委托函数。使您的单元格符合该委托并在您的单元格中重复该操作以将函数抛出到您的UIViewController。您也可以使用闭包委托模式。

希望对您有所帮助。

更新

因此,解决方案是像您最初所做的那样将 UILongPressGestureRecognizerminimumPressDuration 设为 0,并将委托分配给它的单元格(在我的例子中是 superview)。 在单元格中,您需要覆盖此方法:

gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return gestureRecognizer == yourGesture || otherGestureRecognizer == yourGesture
}

顺便说一句,你不需要像现在那样让你的单元格符合UIGestureRecognizerDelegate

【讨论】:

  • 感谢您的回复。我使用 UILongPress 是因为我想在新闻开始时更改文本的背景颜色,并在新闻结束时将其关闭。据我所知,UITapGesture 不允许我这种可能性。如果我错了,请告诉我。此外,我尝试添加 0.1 的轻微延迟,但它会导致相反的问题,即 tableview 滚动抓取手势并且 CustomLabel 手势识别器没有响应。
  • 我认为您只需要在表格视图上使用didSelectRowdidDeselectRow 方法。然后您可以将调用传递给您的单元格以更新标签的背景。如果要更改单元格上的默认选择样式。使用单元格的 selectionStyle 属性设置为 .none
  • 谢谢。如果我的整个单元格都是按钮,您的解决方案将有效,但我需要在单元格内有两个可点击区域,每个区域都有自己的按下和释放检测。
  • @luciusdegeer 啊哈!所以我找到了解决方案。 TableView 的手势识别器的代表位于它的单元格中,因此您只需要将其设置为您的手势的代表。我会用正确的实现来更新我的帖子。;)
  • 非常感谢您的回复。我试图弄清楚“将代表分配给它的单元格”到底是什么意思。我曾尝试将 LongPress 的委托设置为 tableview,如下所示:cell.labelText.tapGesture.delegate = self。你是这个意思吗?我把它放在 tableView 中的 cellForRowAtIndexPath 方法中
【解决方案3】:

在这种情况下,您需要实现 UIGestureRecognizer 委托。我会使用方法gestureRecognizer(_:shouldRequireFailureOf:) 并返回true。这意味着在所有其他手势识别器(特别是 tableView Pan)都失败之前,您的 longPressGesture 不会触发。

class UIViewController: UIViewController, UIGestureRecognizerDelegate {
  var longPressRecognizer: UILongPressGestureRecognizer!
  var tableView: UITableView!
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
      return gestureRecognizer === longPressRecognizer &&
             (otherGestureRecognizer.view?.isDescendant(of:tableView) ?? false)
  }
}

【讨论】:

  • 对不起,这是参数 2 的内部参数名称。我修复了示例。
  • 回到我的电脑查看更新。在我的操场上编译
  • 我在 TableViewController 和 CustomLabel 类中都试过了,但没有解决问题。鉴于 tableview 和 UILongPressGestureRecognizer 位于单独的类文件中,我的案例是否有额外的维度? (TableViewController 和 CustomLabel)
猜你喜欢
  • 1970-01-01
  • 2012-08-06
  • 1970-01-01
  • 1970-01-01
  • 2021-10-11
  • 1970-01-01
  • 2016-12-17
  • 2015-10-03
  • 1970-01-01
相关资源
最近更新 更多