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 }