许多应用程序都有文本,文本中是圆角矩形的web超链接,当我点击它们时,UIWebView就会打开。让我困惑的是,他们经常有自定义链接,例如,如果单词以#开头,它也是可点击的,应用程序通过打开另一个视图来响应。我该怎么做呢?是否可以用UILabel或者我需要UITextView或者其他什么?
当前回答
以下是基于@Luca Davanzo的回答,重写touchesBegan事件而不是轻触手势:
import UIKit
public protocol TapableLabelDelegate: NSObjectProtocol {
func tapableLabel(_ label: TapableLabel, didTapUrl url: String, atRange range: NSRange)
}
public class TapableLabel: UILabel {
private var links: [String: NSRange] = [:]
private(set) var layoutManager = NSLayoutManager()
private(set) var textContainer = NSTextContainer(size: CGSize.zero)
private(set) var textStorage = NSTextStorage() {
didSet {
textStorage.addLayoutManager(layoutManager)
}
}
public weak var delegate: TapableLabelDelegate?
public override var attributedText: NSAttributedString? {
didSet {
if let attributedText = attributedText {
textStorage = NSTextStorage(attributedString: attributedText)
} else {
textStorage = NSTextStorage()
links = [:]
}
}
}
public override var lineBreakMode: NSLineBreakMode {
didSet {
textContainer.lineBreakMode = lineBreakMode
}
}
public override var numberOfLines: Int {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
public override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = bounds.size
}
/// addLinks
///
/// - Parameters:
/// - text: text of link
/// - url: link url string
public func addLink(_ text: String, withURL url: String) {
guard let theText = attributedText?.string as? NSString else {
return
}
let range = theText.range(of: text)
guard range.location != NSNotFound else {
return
}
links[url] = range
}
private func setup() {
isUserInteractionEnabled = true
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let locationOfTouch = touches.first?.location(in: self) else {
return
}
textContainer.size = bounds.size
let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)
for (urlString, range) in links {
if NSLocationInRange(indexOfCharacter, range), let url = URL(string: urlString) {
delegate?.tapableLabel(self, didTapUrl: urlString, atRange: range)
}
}
}}
其他回答
翻译@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()
}
}
标签# Swift2.0
我从- excellent - @NAlexN的回答中获得灵感,我决定自己写一个UILabel的包装器。 我还尝试了TTTAttributedLabel,但我不能使它工作。
希望你能欣赏这段代码,欢迎任何建议!
import Foundation
@objc protocol TappableLabelDelegate {
optional func tappableLabel(tabbableLabel: TappableLabel, didTapUrl: NSURL, atRange: NSRange)
}
/// Represent a label with attributed text inside.
/// We can add a correspondence between a range of the attributed string an a link (URL)
/// By default, link will be open on the external browser @see 'openLinkOnExternalBrowser'
class TappableLabel: UILabel {
// MARK: - Public properties -
var links: NSMutableDictionary = [:]
var openLinkOnExternalBrowser = true
var delegate: TappableLabelDelegate?
// MARK: - Constructors -
override func awakeFromNib() {
super.awakeFromNib()
self.enableInteraction()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.enableInteraction()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func enableInteraction() {
self.userInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("didTapOnLabel:")))
}
// MARK: - Public methods -
/**
Add correspondence between a range and a link.
- parameter url: url.
- parameter range: range on which couple url.
*/
func addLink(url url: String, atRange range: NSRange) {
self.links[url] = range
}
// MARK: - Public properties -
/**
Action rised on user interaction on label.
- parameter tapGesture: gesture.
*/
func didTapOnLabel(tapGesture: UITapGestureRecognizer) {
let labelSize = self.bounds.size;
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSizeZero)
let textStorage = NSTextStorage(attributedString: self.attributedText!)
// configure textContainer for the label
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = self.lineBreakMode
textContainer.maximumNumberOfLines = self.numberOfLines
textContainer.size = labelSize;
// configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = tapGesture.locationInView(self)
let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer,
inTextContainer:textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
for (url, value) in self.links {
if let range = value as? NSRange {
if NSLocationInRange(indexOfCharacter, range) {
let url = NSURL(string: url as! String)!
if self.openLinkOnExternalBrowser {
UIApplication.sharedApplication().openURL(url)
}
self.delegate?.tappableLabel?(self, didTapUrl: url, atRange: range)
}
}
}
}
}
下面是超链接UILabel的示例代码: 来源:http://sickprogrammersarea.blogspot.in/2014/03/adding-links-to-uilabel.html
#import "ViewController.h"
#import "TTTAttributedLabel.h"
@interface ViewController ()
@end
@implementation ViewController
{
UITextField *loc;
TTTAttributedLabel *data;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(5, 20, 80, 25) ];
[lbl setText:@"Text:"];
[lbl setFont:[UIFont fontWithName:@"Verdana" size:16]];
[lbl setTextColor:[UIColor grayColor]];
loc=[[UITextField alloc] initWithFrame:CGRectMake(4, 20, 300, 30)];
//loc.backgroundColor = [UIColor grayColor];
loc.borderStyle=UITextBorderStyleRoundedRect;
loc.clearButtonMode=UITextFieldViewModeWhileEditing;
//[loc setText:@"Enter Location"];
loc.clearsOnInsertion = YES;
loc.leftView=lbl;
loc.leftViewMode=UITextFieldViewModeAlways;
[loc setDelegate:self];
[self.view addSubview:loc];
[loc setRightViewMode:UITextFieldViewModeAlways];
CGRect frameimg = CGRectMake(110, 70, 70,30);
UIButton *srchButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
srchButton.frame=frameimg;
[srchButton setTitle:@"Go" forState:UIControlStateNormal];
[srchButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
srchButton.backgroundColor=[UIColor clearColor];
[srchButton addTarget:self action:@selector(go:) forControlEvents:UIControlEventTouchDown];
[self.view addSubview:srchButton];
data = [[TTTAttributedLabel alloc] initWithFrame:CGRectMake(5, 120,self.view.frame.size.width,200) ];
[data setFont:[UIFont fontWithName:@"Verdana" size:16]];
[data setTextColor:[UIColor blackColor]];
data.numberOfLines=0;
data.delegate = self;
data.enabledTextCheckingTypes=NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber;
[self.view addSubview:data];
}
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url
{
NSString *val=[[NSString alloc]initWithFormat:@"%@",url];
if ([[url scheme] hasPrefix:@"mailto"]) {
NSLog(@" mail URL Selected : %@",url);
MFMailComposeViewController *comp=[[MFMailComposeViewController alloc]init];
[comp setMailComposeDelegate:self];
if([MFMailComposeViewController canSendMail])
{
NSString *recp=[[val substringToIndex:[val length]] substringFromIndex:7];
NSLog(@"Recept : %@",recp);
[comp setToRecipients:[NSArray arrayWithObjects:recp, nil]];
[comp setSubject:@"From my app"];
[comp setMessageBody:@"Hello bro" isHTML:NO];
[comp setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:comp animated:YES completion:nil];
}
}
else{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:val]];
}
}
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{
if(error)
{
UIAlertView *alrt=[[UIAlertView alloc]initWithTitle:@"Erorr" message:@"Some error occureed" delegate:nil cancelButtonTitle:@"" otherButtonTitles:nil, nil];
[alrt show];
[self dismissViewControllerAnimated:YES completion:nil];
}
else{
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber
{
NSLog(@"Phone Number Selected : %@",phoneNumber);
UIDevice *device = [UIDevice currentDevice];
if ([[device model] isEqualToString:@"iPhone"] ) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tel:%@",phoneNumber]]];
} else {
UIAlertView *Notpermitted=[[UIAlertView alloc] initWithTitle:@"Alert" message:@"Your device doesn't support this feature." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[Notpermitted show];
}
}
-(void)go:(id)sender
{
[data setText:loc.text];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Reached");
[loc resignFirstResponder];
}
在Swift 3中工作,将整个代码粘贴在这里
//****Make sure the textview 'Selectable' = checked, and 'Editable = Unchecked'
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
@IBOutlet var theNewTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
//****textview = Selectable = checked, and Editable = Unchecked
theNewTextView.delegate = self
let theString = NSMutableAttributedString(string: "Agree to Terms")
let theRange = theString.mutableString.range(of: "Terms")
theString.addAttribute(NSLinkAttributeName, value: "ContactUs://", range: theRange)
let theAttribute = [NSForegroundColorAttributeName: UIColor.blue, NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue] as [String : Any]
theNewTextView.linkTextAttributes = theAttribute
theNewTextView.attributedText = theString
theString.setAttributes(theAttribute, range: theRange)
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if (URL.scheme?.hasPrefix("ContactUs://"))! {
return false //interaction not allowed
}
//*** Set storyboard id same as VC name
self.navigationController!.pushViewController((self.storyboard?.instantiateViewController(withIdentifier: "TheLastViewController"))! as UIViewController, animated: true)
return true
}
}
最简单可靠的方法是使用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)
推荐文章
- 如何删除默认的导航栏空间在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