I created a custom Label class that contains a UILongPressGestureRecognizer, and I am invoking it in tableview cells in a TableViewController. The long press gesture recognizer works (two clickable zones in an attributed sting), but the tableView containing the labels no longer scrolls (pans) if the scroll gesture begins in one of the UILongPressGestureRecognizer zones of my CustomLabel. I have tried cancelsTouchesInView = false
as well as the various responses below, to no avail. Any suggestions would be greatly appreciated. I've 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 cellClass:
class friendTextCell: UITableViewCell {
@IBOutlet weak var labelText: CustomLabel!
override func awakeFromNib() {
super.awakeFromNib()
self.layoutIfNeeded()
}
}
And here is selections from the TableViewControllerClass where 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
}
Replace
tapGesture.minimumPressDuration = 0
With
tapGesture.minimumPressDuration = 0.5
The recognition is starting too soon and not getting the table to get touches
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With