UILongPressGestureRecognizer 中断 tableView 滚动
UILongPressGestureRecognizer breaks tableView scrolling
我创建了一个包含 UILongPressGestureRecognizer 的自定义标签 class,我在 TableViewController 的表格视图单元格中调用它。长按手势识别器工作(属性字符串中的两个可点击区域),但如果滚动手势从我的 CustomLabel 的 UILongPressGestureRecognizer 区域之一开始,则包含标签的 tableView 不再滚动(平移)。我试过 cancelsTouchesInView = false
以及下面的各种回复,但都无济于事。任何建议将不胜感激。我在这个问题上花了一个星期。我的代码如下。
这是自定义标签 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)
}
}
}
这里是单元类:
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
}
替换
tapGesture.minimumPressDuration = 0
有
tapGesture.minimumPressDuration = 0.5
识别开始得太快,没有得到 table 来触摸
在这种情况下,您需要执行 UIGestureRecognizer delegate. I would use the method gestureRecognizer(_:shouldRequireFailureOf:) 和 return 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)
}
}
tableView containing the labels no longer scrolls (pans) if the scroll gesture begins in one of the UILongPressGestureRecognizer
当您将 UILongPressGestureRecognizer 的最短按压持续时间设置为 0 并且它开始抓取滚动视图的嵌入式手势识别器时,会出现此问题。你可以通过使用更大的延迟来解决它,如果你需要延迟使用长按手势识别器,它应该是怎样的。
首先将响应您的 table 视图的 didSelectRow at
,然后延迟您的选择器。
它对我有用,尽管我在声明识别器的选择器方法之前删除了 tapGesture.cancelsTouchesInView = false
并添加了属性 @objc
(如果您在 swift 4 中写入则需要)。
如果您想立即使用 UILongPressGestureRecognizer
,只需使用 UITapGestureRecognizer
。在这种情况下,table 视图将滚动,但如果您点击标签,则无法接收 didSelectRow
方法。
Table 和集合视图手势委托设置为基础滚动视图,因此您不能为此目的在 UIViewController
上使用 UIGestureRecognizerDelegate
方法。
如果你想在识别器触发时在你的视图控制器中接收某种回调,你可以通过实现委托方法来抛出回调。
例如,创建 CustomLabel
的委托来实现 labelTapped
函数。在您的选择器中调用该委托函数。使您的单元格符合该委托并在您的单元格中重复该操作以将函数抛出给您的 UIViewController
。您也可以使用闭包委托模式。
希望对您有所帮助。
更新
所以解决方案是像您最初所做的那样将 UILongPressGestureRecognizer
的 minimumPressDuration
设置为 0 并将委托分配给它的单元格(在我的例子中是超级视图)。
在单元格中,您需要覆盖此方法:
gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer == yourGesture || otherGestureRecognizer == yourGesture
}
顺便说一句,您不需要让您的单元格符合 UIGestureRecognizerDelegate
,因为它已经符合
我创建了一个包含 UILongPressGestureRecognizer 的自定义标签 class,我在 TableViewController 的表格视图单元格中调用它。长按手势识别器工作(属性字符串中的两个可点击区域),但如果滚动手势从我的 CustomLabel 的 UILongPressGestureRecognizer 区域之一开始,则包含标签的 tableView 不再滚动(平移)。我试过 cancelsTouchesInView = false
以及下面的各种回复,但都无济于事。任何建议将不胜感激。我在这个问题上花了一个星期。我的代码如下。
这是自定义标签 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)
}
}
}
这里是单元类:
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
}
替换
tapGesture.minimumPressDuration = 0
有
tapGesture.minimumPressDuration = 0.5
识别开始得太快,没有得到 table 来触摸
在这种情况下,您需要执行 UIGestureRecognizer delegate. I would use the method gestureRecognizer(_:shouldRequireFailureOf:) 和 return 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)
}
}
tableView containing the labels no longer scrolls (pans) if the scroll gesture begins in one of the UILongPressGestureRecognizer
当您将 UILongPressGestureRecognizer 的最短按压持续时间设置为 0 并且它开始抓取滚动视图的嵌入式手势识别器时,会出现此问题。你可以通过使用更大的延迟来解决它,如果你需要延迟使用长按手势识别器,它应该是怎样的。
首先将响应您的 table 视图的 didSelectRow at
,然后延迟您的选择器。
它对我有用,尽管我在声明识别器的选择器方法之前删除了 tapGesture.cancelsTouchesInView = false
并添加了属性 @objc
(如果您在 swift 4 中写入则需要)。
如果您想立即使用 UILongPressGestureRecognizer
,只需使用 UITapGestureRecognizer
。在这种情况下,table 视图将滚动,但如果您点击标签,则无法接收 didSelectRow
方法。
Table 和集合视图手势委托设置为基础滚动视图,因此您不能为此目的在 UIViewController
上使用 UIGestureRecognizerDelegate
方法。
如果你想在识别器触发时在你的视图控制器中接收某种回调,你可以通过实现委托方法来抛出回调。
创建 CustomLabel
的委托来实现 labelTapped
函数。在您的选择器中调用该委托函数。使您的单元格符合该委托并在您的单元格中重复该操作以将函数抛出给您的 UIViewController
。您也可以使用闭包委托模式。
希望对您有所帮助。
更新
所以解决方案是像您最初所做的那样将 UILongPressGestureRecognizer
的 minimumPressDuration
设置为 0 并将委托分配给它的单元格(在我的例子中是超级视图)。
在单元格中,您需要覆盖此方法:
gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer == yourGesture || otherGestureRecognizer == yourGesture
}
顺便说一句,您不需要让您的单元格符合 UIGestureRecognizerDelegate
,因为它已经符合