UILongPressGestureRecognizer Aborts Table Scrolling

I created my own Label class containing the UILongPressGestureRecognizer, and I call it in the tableview cells in the TableViewController. The long gesture recognition tool works (two clickable zones in the sting attribute), but the tableView containing the labels no longer scrolls (brackets) if the scroll gesture starts in one of the UILongPressGestureRecognizer zones of my CustomLabel. I tried cancelsTouchesInView = false , as well as the various answers below, to no avail. Any suggestions would be appreciated. I spent a week on this problem. My code is below.

Here is the CustomLabel class:

 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) } } } 

Here is the cell:

 class friendTextCell: UITableViewCell { @IBOutlet weak var labelText: CustomLabel! override func awakeFromNib() { super.awakeFromNib() self.layoutIfNeeded() } } 

And here is the selection from the TableViewControllerClass where the CustomCells are created:

 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 } 
+5
source share
3 answers

a tableView containing labels no longer scrolls (brackets) if the scroll gesture starts in one of the UILongPressGestureRecognizer

This problem occurs when the minimum duration for pressing UILongPressGestureRecognizer is 0, and it begins to grab the built-in scroll gesture recognizer. You can enable it using a large delay if you need to use a long gesture recognizer for the delay, as it should be. First answer your table view didSelectRow at and after the delay your selector. This worked for me, although I removed tapGesture.cancelsTouchesInView = false and added the @objc attribute before declaring your select method to the resolver (required if you are writing in swift 4).

If you want to use UILongPressGestureRecognizer without delay, just use UITapGestureRecognizer . In this case, the table view will scroll, but you will not be able to get the didSelectRow method if you click on the label. Table and collection views are displayed as scroll views, so you cannot use the UIGestureRecognizerDelegate methods on your UIViewController for this purpose. If you want to receive some kind of callback in your view of the Controller, when this recognizer is triggered, you can transfer the call using the delegation methods.

Create a CustomLabel delegate that implements the labelTapped function, for example. In your selector, the delegate function is called. Make your cell appropriate for this delegate and repeat what is in your cell to pass the function to your UIViewController . You can also use the closure delegation pattern.

Hope this helps.

Update

So the solution is to make the UILongPressGestureRecognizer minimumPressDuration equal to 0, as you did before, and assign the delegate to its cell (supervisor in my case). In the cell, you need to override this method:

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

By the way, you do not need your cell to match UIGestureRecognizerDelegate , as it already does

0
source

Replace

 tapGesture.minimumPressDuration = 0 

WITH

 tapGesture.minimumPressDuration = 0.5 

Recognition starts too soon and does not make the table touch

0
source

In this case, you need to implement the UIGestureRecognizer delegate. I would use the gestureRecognizer (_: shouldRequireFailureOf :) method and return true. This means that your longPressGesture will not fire until all other gesture recognizers (in particular TableView Pan) have worked.

 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) } } 
0
source

Source: https://habr.com/ru/post/1273746/


All Articles