Dan*_*ray 12 xcode nsattributedstring uilabel ios swift4
编辑
请参阅我的答案以获得完整的解决方案:
我设法通过使用UITextView而不是a 来解决这个问题UILabel.我写了一个类,使UITextView行为像一个UILabel但具有完全准确的链接检测.
我已经成功设置了链接的样式而没有使用问题,NSMutableAttributedString但我无法准确地检测到哪个字符已被点击.我已经尝试了这个问题中的所有解决方案(我可以转换为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)
}
Run Code Online (Sandbox Code Playgroud)
小智 10
如果你不介意重写你的代码,你应该使用UITextView而不是UILabel.
您可以轻松地通过设置检测链路UITextView的dataDetectorTypes贯彻委托函数来获取你的点击网址.
func textView(_ textView: UITextView, shouldInteractWith URL: URL,
in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
Run Code Online (Sandbox Code Playgroud)
https://developer.apple.com/documentation/uikit/uitextviewdelegate/1649337-textview
我设法通过使用UITextView而不是来解决此问题UILabel。我本来不想使用a,UITextView因为我需要元素的行为像a UILabel和a 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
}
}
Run Code Online (Sandbox Code Playgroud)
函数hitTestget被多次调用,但绝不会导致问题,因为clickedLink()每次单击只会调用一次。我尝试禁用isUserInteractionEnabled不同的视图,但是这样做并没有帮助,也没有必要。
要使用该类,只需将其添加到中UITextView。如果您autoLayout在Xcode编辑器中使用,请在编辑器中将其禁用Scrolling Enabled,UITextView以避免出现布局警告。
在Swift包含与xib文件一起使用的代码的文件中(在我的情况下,该类为的类UITableViewCell,您需要为可点击的textView设置以下变量:
ranges -每个可点击链接的开始和结束索引,带有 UITextViewpage-一个String以确定包含的页面或视图UITextViewparagraph-如果您有多个可点击的按钮UITextView,请为每个按钮分配一个数字delegate -将点击事件委托给您能够处理的地方。然后,您需要为您创建一个协议delegate:
protocol DelegateName {
func clickedLink(page: String, paragraph: Int?, linkNo: Int?)
}
Run Code Online (Sandbox Code Playgroud)
传入的变量为clickedLink您提供了您需要知道单击哪个链接的所有信息。
我想避免发布答案,因为它更多的是对 Dan Bray 自己的答案的评论(由于缺乏代表而无法发表评论)。不过,我仍然认为值得分享。
为了方便起见,我对丹·布雷的答案做了一些小的(我认为是)改进:
textLink存储链接字符串及其各自目标的字典替换了该部分。实现 viewController 只需要设置它来初始化 textView。delegate因为linkDelegateUITextViews 已经有一个委托了。文本视图:
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
}
}
Run Code Online (Sandbox Code Playgroud)
代表:
import Foundation
protocol LinkTextViewDelegate {
func didTap(text: String, withLink link: String, inTextView textView: LinkTextView)
}
Run Code Online (Sandbox Code Playgroud)
实现视图控制器:
override func viewDidLoad() {
super.viewDidLoad()
myLinkTextView.linkDelegate = self
myLinkTextView.textLinks = [
"click here" : "https://wwww.google.com",
"or here" : "#myOwnAppHook"
]
}
Run Code Online (Sandbox Code Playgroud)
最后但并非最不重要的一点是非常感谢 Dan Bray,这毕竟是他的解决方案!
| 归档时间: |
|
| 查看次数: |
2040 次 |
| 最近记录: |