编辑
有关完整的工作解决方案,请参阅我的答案:
我自己设法通过使用UITextView
而不是UILabel
解决了这个问题。我写了一个类,它使UITextView
的行为像UILabel
一样,但具有完全准确的链接检测。
我已经设法样式的链接没有问题使用NSMutableAttributedString
,但我不能准确地检测到哪个字符已被点击。我已经尝试了this question中的所有解决方案(我可以将其转换为Swift 4代码),但没有成功。
下面的代码可以工作,但无法准确检测到被单击的字符,并且无法获得错误的链接位置:
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
print(indexOfCharacter)
return NSLocationInRange(indexOfCharacter, targetRange)
}
发布于 2018-05-31 21:59:30
通过使用UITextView
而不是UILabel
,我设法解决了这个问题。我最初不想使用UITextView
,因为我需要元素的行为像UILabel
,而UITextView
可能会导致滚动问题,它的预期用途是可编辑的文本。我编写的以下类使UITextView
的行为类似于UILabel
,但具有完全准确的点击检测,并且没有滚动问题:
import UIKit
class ClickableLabelTextView: UITextView {
var delegate: DelegateForClickEvent?
var ranges:[(start: Int, end: Int)] = []
var page: String = ""
var paragraph: Int?
var clickedLink: (() -> Void)?
var pressedTime: Int?
var startTime: TimeInterval?
override func awakeFromNib() {
super.awakeFromNib()
self.textContainerInset = UIEdgeInsets.zero
self.textContainer.lineFragmentPadding = 0
self.delaysContentTouches = true
self.isEditable = false
self.isUserInteractionEnabled = true
self.isSelectable = false
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startTime = Date().timeIntervalSinceReferenceDate
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let clickedLink = clickedLink {
if let startTime = startTime {
self.startTime = nil
if (Date().timeIntervalSinceReferenceDate - startTime <= 0.2) {
clickedLink()
}
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var location = point
location.x -= self.textContainerInset.left
location.y -= self.textContainerInset.top
if location.x > 0 && location.y > 0 {
let index = self.layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
var count = 0
for range in ranges {
if index >= range.start && index < range.end {
clickedLink = {
self.delegate?.clickedLink(page: self.page, paragraph: self.paragraph, linkNo: count)
}
return self
}
count += 1
}
}
clickedLink = nil
return nil
}
}
函数hitTest
get被多次调用,但这从来不会导致问题,因为每次单击clickedLink()
都只会被调用一次。我试着为不同的视图禁用isUserInteractionEnabled
,但是没有效果,也没有必要。
要使用这个类,只需将它添加到您的UITextView
。如果您在Xcode编辑器中使用autoLayout
,那么在编辑器中为UITextView
禁用Scrolling Enabled
,以避免布局警告。
在包含与xib
文件配套的代码的Swift
文件中(在我的示例中,是UITableViewCell
的类),您需要为可单击的textView设置以下变量:
ranges
-每个带有UITextView
page
的可点击链接的开始和结束索引-用于标识包含UITextView
paragraph
的页面或视图的numberdelegate
-如果您有多个可点击的UITextView
,请为每个可点击的are分配一个are,以便将点击事件委派到您可以处理它们的任何位置。然后,您需要为delegate
创建一个协议
protocol DelegateName {
func clickedLink(page: String, paragraph: Int?, linkNo: Int?)
}
传递到clickedLink
中的变量为您提供了所需的所有信息,以便知道哪个链接被单击了。
发布于 2018-05-27 15:17:51
如果您不介意重写代码,那么您应该使用UITextView
而不是UILabel
。
您可以通过设置UITextView
的dataDetectorTypes
来轻松地检测链接,并实现委托函数来检索您单击的urls。
func textView(_ textView: UITextView, shouldInteractWith URL: URL,
in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
https://developer.apple.com/documentation/uikit/uitextviewdelegate/1649337-textview
发布于 2018-06-15 18:19:01
我想避免发布答案,因为它更多的是对Dan Bray自己的答案的评论(由于缺乏代表而无法评论)。然而,我仍然认为这是值得分享的。
为了方便起见,我对Dan Bray的回答做了一些小的(我认为是)改进:
textLink
字典替换了这一部分。实现viewController只需要设置它来初始化textView。TextView:
import UIKit
class LinkTextView: UITextView {
private var callback: (() -> Void)?
private var pressedTime: Int?
private var startTime: TimeInterval?
private var initialized = false
var linkDelegate: LinkTextViewDelegate?
var textLinks: [String : String] = Dictionary() {
didSet {
initialized = false
styleTextLinks()
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.textContainerInset = UIEdgeInsets.zero
self.textContainer.lineFragmentPadding = 0
self.delaysContentTouches = true
self.isEditable = false
self.isUserInteractionEnabled = true
self.isSelectable = false
styleTextLinks()
}
private func styleTextLinks() {
guard !initialized && !textLinks.isEmpty else {
return
}
initialized = true
let alignmentStyle = NSMutableParagraphStyle()
alignmentStyle.alignment = self.textAlignment
let input = self.text ?? ""
let attributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.foregroundColor : self.textColor!,
NSAttributedStringKey.font : self.font!,
.paragraphStyle : alignmentStyle
]
let attributedString = NSMutableAttributedString(string: input, attributes: attributes)
for textLink in textLinks {
let range = (input as NSString).range(of: textLink.0)
if range.lowerBound != NSNotFound {
attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue, range: range)
}
}
attributedText = attributedString
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startTime = Date().timeIntervalSinceReferenceDate
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let callback = callback {
if let startTime = startTime {
self.startTime = nil
if (Date().timeIntervalSinceReferenceDate - startTime <= 0.2) {
callback()
}
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var location = point
location.x -= self.textContainerInset.left
location.y -= self.textContainerInset.top
if location.x > 0 && location.y > 0 {
let index = self.layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
for textLink in textLinks {
let range = ((text ?? "") as NSString).range(of: textLink.0)
if NSLocationInRange(index, range) {
callback = {
self.linkDelegate?.didTap(text: textLink.0, withLink: textLink.1, inTextView: self)
}
return self
}
}
}
callback = nil
return nil
}
}
代理:
import Foundation
protocol LinkTextViewDelegate {
func didTap(text: String, withLink link: String, inTextView textView: LinkTextView)
}
实现viewController:
override func viewDidLoad() {
super.viewDidLoad()
myLinkTextView.linkDelegate = self
myLinkTextView.textLinks = [
"click here" : "https://wwww.google.com",
"or here" : "#myOwnAppHook"
]
}
最后但并非最不重要的是,非常感谢Dan Bray,毕竟这是他的解决方案!
https://stackoverflow.com/questions/50505334
复制相似问题