许多应用程序都有文本,文本中是圆角矩形的web超链接,当我点击它们时,UIWebView就会打开。让我困惑的是,他们经常有自定义链接,例如,如果单词以#开头,它也是可点击的,应用程序通过打开另一个视图来响应。我该怎么做呢?是否可以用UILabel或者我需要UITextView或者其他什么?
当前回答
这个通用方法也可以!
func didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange targetRange: NSRange) -> Bool {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
guard let strAttributedText = self.attributedText else {
return false
}
let textStorage = NSTextStorage(attributedString: strAttributedText)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = Constants.lineFragmentPadding
textContainer.lineBreakMode = self.lineBreakMode
textContainer.maximumNumberOfLines = self.numberOfLines
let labelSize = self.bounds.size
textContainer.size = CGSize(width: labelSize.width, height: CGFloat.greatestFiniteMagnitude)
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = gesture.location(in: self)
let xCordLocationOfTouchInTextContainer = locationOfTouchInLabel.x
let yCordLocationOfTouchInTextContainer = locationOfTouchInLabel.y
let locOfTouch = CGPoint(x: xCordLocationOfTouchInTextContainer ,
y: yCordLocationOfTouchInTextContainer)
let indexOfCharacter = layoutManager.characterIndex(for: locOfTouch, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard let strLabel = text else {
return false
}
let charCountOfLabel = strLabel.count
if indexOfCharacter < (charCountOfLabel - 1) {
return NSLocationInRange(indexOfCharacter, targetRange)
} else {
return false
}
}
你可以用
let text = yourLabel.text
let termsRange = (text as NSString).range(of: fullString)
if yourLabel.didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange: termsRange) {
showCorrespondingViewController()
}
其他回答
最简单可靠的方法是使用Kedar Paranjape推荐的UITextView。基于Karl Nosworthy的回答,我最终想出了一个简单的UITextView子类:
class LinkTextView: UITextView, UITextViewDelegate {
typealias Links = [String: String]
typealias OnLinkTap = (URL) -> Bool
var onLinkTap: OnLinkTap?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
isEditable = false
isSelectable = true
isScrollEnabled = false //to have own size and behave like a label
delegate = self
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func addLinks(_ links: Links) {
guard attributedText.length > 0 else {
return
}
let mText = NSMutableAttributedString(attributedString: attributedText)
for (linkText, urlString) in links {
if linkText.count > 0 {
let linkRange = mText.mutableString.range(of: linkText)
mText.addAttribute(.link, value: urlString, range: linkRange)
}
}
attributedText = mText
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
return onLinkTap?(URL) ?? true
}
// to disable text selection
func textViewDidChangeSelection(_ textView: UITextView) {
textView.selectedTextRange = nil
}
}
用法非常简单:
let linkTextView = LinkTextView()
let tu = "Terms of Use"
let pp = "Privacy Policy"
linkTextView.text = "Please read the Some Company \(tu) and \(pp)"
linkTextView.addLinks([
tu: "https://some.com/tu",
pp: "https://some.com/pp"
])
linkTextView.onLinkTap = { url in
print("url: \(url)")
return true
}
请注意,isScrollEnabled默认为false,因为在大多数情况下,我们需要有自己大小且没有滚动的类似标签的小视图。如果你想要一个可滚动的文本视图,就把它设为true。
还要注意,UITextView不像UILabel有默认的文本填充。要删除它,使布局与UILabel相同,只需添加:linkTextView。textContainerInset = . 0
实现onLinkTap闭包是不必要的,没有它url是由UIApplication自动打开的。
由于文本选择在大多数情况下是不可取的,但它不能关闭,它在委托方法中被解散(感谢Carson Vo)
我创建了一个名为ResponsiveLabel的UILabel子类,它是基于ios7中引入的textkit API。它使用了NAlexN建议的相同方法。它提供了在文本中指定搜索模式的灵活性。可以指定应用于这些模式的样式,以及在敲击模式时要执行的操作。
//Detects email in text
NSString *emailRegexString = @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}";
NSError *error;
NSRegularExpression *regex = [[NSRegularExpression alloc]initWithPattern:emailRegexString options:0 error:&error];
PatternDescriptor *descriptor = [[PatternDescriptor alloc]initWithRegex:regex withSearchType:PatternSearchTypeAll withPatternAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]}];
[self.customLabel enablePatternDetection:descriptor];
如果你想让一个字符串可点击,你可以这样做。这段代码将属性应用到字符串“text”的每个出现处。
PatternTapResponder tapResponder = ^(NSString *string) {
NSLog(@"tapped = %@",string);
};
[self.customLabel enableStringDetection:@"text" withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],
RLTapResponderAttributeName: tapResponder}];
对于完全自定义的链接,你需要使用UIWebView -你可以拦截调用,这样当链接被按下时,你可以转到应用程序的其他部分。
这里是一个Swift实现,它是尽可能少的,还包括触摸反馈。警告:
你必须在NSAttributedStrings中设置字体 你只能使用NSAttributedStrings! 你必须确保你的链接不能自动换行(使用不间断的空格:"\u{a0}") 设置文本后,不能更改lineBreakMode或numberolines 通过添加带有.link键的属性来创建链接
.
public class LinkLabel: UILabel {
private var storage: NSTextStorage?
private let textContainer = NSTextContainer()
private let layoutManager = NSLayoutManager()
private var selectedBackgroundView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
textContainer.lineFragmentPadding = 0
layoutManager.addTextContainer(textContainer)
textContainer.layoutManager = layoutManager
isUserInteractionEnabled = true
selectedBackgroundView.isHidden = true
selectedBackgroundView.backgroundColor = UIColor(white: 0, alpha: 0.3333)
selectedBackgroundView.layer.cornerRadius = 4
addSubview(selectedBackgroundView)
}
public required convenience init(coder: NSCoder) {
self.init(frame: .zero)
}
public override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = frame.size
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
setLink(for: touches)
}
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
setLink(for: touches)
}
private func setLink(for touches: Set<UITouch>) {
if let pt = touches.first?.location(in: self), let (characterRange, _) = link(at: pt) {
let glyphRange = layoutManager.glyphRange(forCharacterRange: characterRange, actualCharacterRange: nil)
selectedBackgroundView.frame = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer).insetBy(dx: -3, dy: -3)
selectedBackgroundView.isHidden = false
} else {
selectedBackgroundView.isHidden = true
}
}
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
selectedBackgroundView.isHidden = true
}
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
selectedBackgroundView.isHidden = true
if let pt = touches.first?.location(in: self), let (_, url) = link(at: pt) {
UIApplication.shared.open(url)
}
}
private func link(at point: CGPoint) -> (NSRange, URL)? {
let touchedGlyph = layoutManager.glyphIndex(for: point, in: textContainer)
let touchedChar = layoutManager.characterIndexForGlyph(at: touchedGlyph)
var range = NSRange()
let attrs = attributedText!.attributes(at: touchedChar, effectiveRange: &range)
if let urlstr = attrs[.link] as? String {
return (range, URL(string: urlstr)!)
} else {
return nil
}
}
public override var attributedText: NSAttributedString? {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
textContainer.lineBreakMode = lineBreakMode
if let txt = attributedText {
storage = NSTextStorage(attributedString: txt)
storage!.addLayoutManager(layoutManager)
layoutManager.textStorage = storage
textContainer.size = frame.size
}
}
}
}
翻译@samwize的扩展到Swift 4:
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
guard let attrString = label.attributedText else {
return false
}
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
let textStorage = NSTextStorage(attributedString: attrString)
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
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)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
要设置识别器(一旦你给文本和东西上色):
lblTermsOfUse.isUserInteractionEnabled = true
lblTermsOfUse.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOnLabel(_:))))
...然后是手势识别器:
@objc func handleTapOnLabel(_ recognizer: UITapGestureRecognizer) {
guard let text = lblAgreeToTerms.attributedText?.string else {
return
}
if let range = text.range(of: NSLocalizedString("_onboarding_terms", comment: "terms")),
recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) {
goToTermsAndConditions()
} else if let range = text.range(of: NSLocalizedString("_onboarding_privacy", comment: "privacy")),
recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) {
goToPrivacyPolicy()
}
}
推荐文章
- 如何删除默认的导航栏空间在SwiftUI导航视图
- 如何在iOS中使用Swift编程segue
- Swift -整数转换为小时/分钟/秒
- Swift:声明一个空字典
- 我如何用javascript编程点击链接?
- 在成功提交我的应用程序后,“太多符号文件”
- 首先添加一个UIView,甚至是导航栏
- 我如何改变UIButton标题颜色?
- 在Swift中如何调用GCD主线程上的参数方法?
- NSLayoutConstraints是可动画的吗?
- iOS -构建失败,CocoaPods无法找到头文件
- CFNetwork SSLHandshake iOS 9失败
- 请求失败:不可接受的内容类型:文本/html使用AFNetworking 2.0
- 缺少推荐的图标文件-该包不包含iPhone / iPod Touch的应用程序图标,像素为“120x120”,png格式
- 以编程方式创建segue