带有可点击操作链接的 iOS UITextView 或 UILabel

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20541676/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 22:44:54  来源:igfitidea点击:

iOS UITextView or UILabel with clickable links to actions

iosobjective-c

提问by Michael Koper

I want to make a UILabelor UITextViewwith some text with 2 clickable links in it. Not links to webpages but I want to link those 2 links with actions like i would do with an UIButton. All the examples i've seen are links to webviews but I dont want that. As well, the text will be translated in other languages so the positions have to be dynamic.

我想制作一个UILabelUITextView带有 2 个可点击链接的文本。不是网页链接,但我想将这 2 个链接与我对UIButton. 我见过的所有示例都是指向 webviews 的链接,但我不想要那样。同样,文本将被翻译成其他语言,因此职位必须是动态的。

Want to make this:

想做这个:

enter image description here

在此处输入图片说明

回答by Erik van der Neut

I needed to solve this exact same problem: very similar text with those two links in it, over multiple lines, and needing it to be able to be translated in any language (including different word orders, etc). I just solved it, so let me share how I did it.

我需要解决这个完全相同的问题:非常相似的文本,其中有两个链接,多行,并且需要它能够被翻译成任何语言(包括不同的词序等)。我刚刚解决了它,所以让我分享我是如何做到的。

Initially I was thinking that I should create attributed text and then map the tap's touch location to the regions within that text. While I think that is doable, I also think it's a much too complicated approach.

最初我想我应该创建属性文本,然后将点击的触摸位置映射到该文本中的区域。虽然我认为这是可行的,但我也认为这是一种过于复杂的方法。

This is what I ended up doing instead:

这就是我最终做的:

SUMMARY:

概括:

  • Have very basic custom markup in your English message so you can parse out the different pieces
  • Instruct your translators to leave the markup in and translate the rest
  • Have a UIView that can serve as the container of this message
  • Break your English message up in pieces to separate the regular text from the clickable text
  • For each piece create a UILabel on the container UIView
  • For the clickable pieces, set your styling, allow user interaction and create your tap gesture recognizer
  • Do some very basic bookkeeping to place the words perfectly across the lines
  • 在您的英文消息中有非常基本的自定义标记,以便您可以解析不同的部分
  • 指示您的翻译人员留下标记并翻译其余部分
  • 有一个 UIView 可以作为这个消息的容器
  • 将您的英文信息分成几部分,将常规文本与可点击文本分开
  • 对于每一块在容器 UIView 上创建一个 UILabel
  • 对于可点击的部分,设置您的样式,允许用户交互并创建您的点击手势识别器
  • 做一些非常基本的簿记以将单词完美地放在线条上

DETAIL:

细节:

In the view controller's viewDidLoadI placed this:

在视图控制器中,viewDidLoad我放置了这个:

[self buildAgreeTextViewFromString:NSLocalizedString(@"I agree to the #<ts>terms of service# and #<pp>privacy policy#", 
                                                     @"PLEASE NOTE: please translate \"terms of service\" and \"privacy policy\" as well, and leave the #<ts># and #<pp># around your translations just as in the English version of this message.")];

I'm calling a method that will build the message. Note the markup I came up with. You can of course invent your own, but key is that I also mark the ends of each clickable region because they span over multiple words.

我正在调用将构建消息的方法。请注意我提出的标记。你当然可以自己发明,但关键是我还标记了每个可点击区域的末端,因为它们跨越多个单词。

Here's the method that puts the message together -- see below. First I break up the English message over the #character (or rather @"#"string). That way I get each piece for which I need to create a label separately. I loop over them and look for my basic markup of <ts>and <pp>to detect which pieces are links to what. If the chunk of text I'm working with is a link, then I style a bit and set up a tap gesture recogniser for it. I also strip out the markup characters of course. I think this is a really easy way to do it.

这是将消息放在一起的方法——见下文。首先,我在#字符(或者更确切地说是@"#"字符串)上分解了英文消息。这样我就得到了需要单独创建标签的每一件作品。我遍历它们并查找我的基本标记<ts><pp>检测哪些部分是什么的链接。如果我正在处理的文本块是一个链接,那么我会设计一些样式并为其设置点击手势识别器。当然,我也会去掉标记字符。我认为这是一种非常简单的方法。

Note some subtleties like how I handle spaces: I simply take the spaces from the (localised) string. If there are no spaces (Chinese, Japanese), then there won't be spaces between the chunks either. If there are spaces, then those automatically space out the chunks as needed (e.g. for English). When I have to place a word at the start of a next line though, then I do need to make sure that I strip of any white space prefix from that text, because otherwise it doesn't align properly.

注意一些微妙之处,比如我如何处理空格:我只是从(本地化的)字符串中取出空格。如果没有空格(中文、日文),那么块之间也不会有空格。如果有空格,则那些会根据需要自动将块隔开(例如,对于英语)。但是,当我必须在下一行的开头放置一个单词时,我确实需要确保从该文本中删除任何空格前缀,否则它不会正确对齐。

- (void)buildAgreeTextViewFromString:(NSString *)localizedString
{
  // 1. Split the localized string on the # sign:
  NSArray *localizedStringPieces = [localizedString componentsSeparatedByString:@"#"];

  // 2. Loop through all the pieces:
  NSUInteger msgChunkCount = localizedStringPieces ? localizedStringPieces.count : 0;
  CGPoint wordLocation = CGPointMake(0.0, 0.0);
  for (NSUInteger i = 0; i < msgChunkCount; i++)
  {
    NSString *chunk = [localizedStringPieces objectAtIndex:i];
    if ([chunk isEqualToString:@""])
    {
      continue;     // skip this loop if the chunk is empty
    }

    // 3. Determine what type of word this is:
    BOOL isTermsOfServiceLink = [chunk hasPrefix:@"<ts>"];
    BOOL isPrivacyPolicyLink  = [chunk hasPrefix:@"<pp>"];
    BOOL isLink = (BOOL)(isTermsOfServiceLink || isPrivacyPolicyLink);

    // 4. Create label, styling dependent on whether it's a link:
    UILabel *label = [[UILabel alloc] init];
    label.font = [UIFont systemFontOfSize:15.0f];
    label.text = chunk;
    label.userInteractionEnabled = isLink;

    if (isLink)
    {
      label.textColor = [UIColor colorWithRed:110/255.0f green:181/255.0f blue:229/255.0f alpha:1.0];
      label.highlightedTextColor = [UIColor yellowColor];

      // 5. Set tap gesture for this clickable text:
      SEL selectorAction = isTermsOfServiceLink ? @selector(tapOnTermsOfServiceLink:) : @selector(tapOnPrivacyPolicyLink:);
      UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                   action:selectorAction];
      [label addGestureRecognizer:tapGesture];

      // Trim the markup characters from the label:
      if (isTermsOfServiceLink) 
        label.text = [label.text stringByReplacingOccurrencesOfString:@"<ts>" withString:@""];
      if (isPrivacyPolicyLink)  
        label.text = [label.text stringByReplacingOccurrencesOfString:@"<pp>" withString:@""];
    }
    else
    {
      label.textColor = [UIColor whiteColor];
    }

    // 6. Lay out the labels so it forms a complete sentence again:

    // If this word doesn't fit at end of this line, then move it to the next
    // line and make sure any leading spaces are stripped off so it aligns nicely:

    [label sizeToFit];

    if (self.agreeTextContainerView.frame.size.width < wordLocation.x + label.bounds.size.width)
    {
      wordLocation.x = 0.0;                       // move this word all the way to the left...
      wordLocation.y += label.frame.size.height;  // ...on the next line

      // And trim of any leading white space:
      NSRange startingWhiteSpaceRange = [label.text rangeOfString:@"^\s*"
                                                          options:NSRegularExpressionSearch];
      if (startingWhiteSpaceRange.location == 0)
      {
        label.text = [label.text stringByReplacingCharactersInRange:startingWhiteSpaceRange
                                                         withString:@""];
        [label sizeToFit];
      }
    }

    // Set the location for this label:
    label.frame = CGRectMake(wordLocation.x,
                             wordLocation.y,
                             label.frame.size.width,
                             label.frame.size.height);
    // Show this label:
    [self.agreeTextContainerView addSubview:label];

    // Update the horizontal position for the next word:
    wordLocation.x += label.frame.size.width;
  }
}

And here are my methods that handle the detected taps on those links.

这是我处理这些链接上检测到的点击的方法。

- (void)tapOnTermsOfServiceLink:(UITapGestureRecognizer *)tapGesture
{
  if (tapGesture.state == UIGestureRecognizerStateEnded)
  {
    NSLog(@"User tapped on the Terms of Service link");
  }
}


- (void)tapOnPrivacyPolicyLink:(UITapGestureRecognizer *)tapGesture
{
  if (tapGesture.state == UIGestureRecognizerStateEnded)
  {
    NSLog(@"User tapped on the Privacy Policy link");
  }
}

Hope this helps. I'm sure there are much smarter and more elegant ways to do this, but this is what I was able to come up with and it works nicely.

希望这可以帮助。我确信有更聪明、更优雅的方法来做到这一点,但这是我能够想出的,而且效果很好。

Here's how it looks in the app:

这是它在应用程序中的外观:

Simulator screenshot of the end result

最终结果的模拟器截图

Good luck! :-)

祝你好运!:-)

Erik

埃里克

回答by kgaidis

How I implement custom text actions (like a button) for UITextView:

我如何实现自定义文本操作(如按钮)UITextView

The key principles:

关键原则:

  1. Use NSAttributedStringas a way of defining a link to tap.
  2. Use UITextViewDelegateto catch the press of the link.
  1. 使用NSAttributedString为定义链接轻敲的方法。
  2. 使用UITextViewDelegate捕捉链接的新闻。

Define a URL string:

定义一个 URL 字符串:

private let kURLString = "https://www.mywebsite.com"

Add a link to your attributed string:

添加指向属性字符串的链接:

let originalText = "Please visit the website for more information."
let attributedOriginalText = NSMutableAttributedString(string: originalText)

let linkRange = attributedOriginalText.mutableString.range(of: "website")
attributedOriginalText.addAttribute(.link, value: kURLString, range: linkRange)

Assign attributed string to a text view:

将属性字符串分配给文本视图:

textView.attributedText = attributedOriginalText

Implement UITextViewDelegate(this is really the key piece a prevents the URL from opening some website and where you can define your custom action instead):

实施UITextViewDelegate(这确实是防止 URL 打开某些网站以及您可以在其中定义自定义操作的关键部分):

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
    if (URL.absoluteString == kURLString) {
        // Do whatever you want here as the action to the user pressing your 'actionString'
    }
    return false
}

You can also customize how your link looks:

您还可以自定义链接的外观:

textView.linkTextAttributes = [
    NSAttributedStringKey.foregroundColor.rawValue : UIColor.red,
    NSAttributedStringKey.underlineStyle.rawValue : NSUnderlineStyle.styleSingle]

How I implement custom actions for UILabel:

我如何实现自定义操作UILabel

I usually end up using TTTAttributedLabel.

我通常最终使用TTTAttributedLabel

回答by Paulo Henrique Nonaka

Here is a complete example made in Swift 2 without pods.

这是一个在 Swift 2 中制作的完整示例,没有 pod。

import UIKit

class SomeViewController: UIViewController, UITextViewDelegate {
  @IBOutlet weak var terms: UITextView!

  let termsAndConditionsURL = "http://www.example.com/terms";
  let privacyURL = "http://www.example.com/privacy";

  override func viewDidLoad() {
    super.viewDidLoad()

    self.terms.delegate = self
    let str = "By using this app you agree to our Terms and Conditions and Privacy Policy"
    let attributedString = NSMutableAttributedString(string: str)
    var foundRange = attributedString.mutableString.rangeOfString("Terms and Conditions")
    attributedString.addAttribute(NSLinkAttributeName, value: termsAndConditionsURL, range: foundRange)
    foundRange = attributedString.mutableString.rangeOfString("Privacy Policy")
    attributedString.addAttribute(NSLinkAttributeName, value: privacyURL, range: foundRange)
    terms.attributedText = attributedString
  }

  func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
    if (URL.absoluteString == termsAndConditionsURL) {
      let myAlert = UIAlertController(title: "Terms", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
      myAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
      self.presentViewController(myAlert, animated: true, completion: nil)
    } else if (URL.absoluteString == privacyURL) {
      let myAlert = UIAlertController(title: "Conditions", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
      myAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
      self.presentViewController(myAlert, animated: true, completion: nil)
    }
    return false
  }
}

回答by kshitij godara

Check this UILabel class ,this will surely help you . I did this same thing by using this .

检查这个 UILabel 类,这肯定会帮助你。我通过使用 this 做了同样的事情。

TTTAttributedLabel

TTT属性标签

回答by Lereveme

Here is a translated version of the accepted answer to C# for Xamarin for anyone who will find it useful:

以下是 Xamarin 的 C# 的已接受答案的翻译版本,供任何会发现它有用的人使用:

        var str = "Or, #<li>log in# to see your orders."; 
        var strParts = str.Split('#');
        var ptWordLocation = new PointF (0, 0);

        if (strParts.Length > 1) {
            //Loop the parts of the string
            foreach (var s in strParts) {
                //Check for empty string
                if (!String.IsNullOrEmpty (s)) {
                    var lbl = new UILabel ();
                    lbl.Font = lbl.Font.WithSize (15);
                    lbl.TextColor = cpAppConstants.TextColorMessage;
                    lbl.UserInteractionEnabled = s.Contains ("<li>");
                    lbl.Text = s.Replace ("<li>", "");

                    if (s.Contains ("<li>")) {
                        lbl.TextColor = UIColor.FromRGB (200, 95, 40);

                        //Set tap gesture for this clickable text:
                        var gesture = new UITapGestureRecognizer ();
                        gesture.AddTarget(() => buildLoginLabel_onTap(gesture));
                        lbl.AddGestureRecognizer (gesture);
                    }

                    lbl.SizeToFit ();

                    //Lay out the labels so it forms a complete sentence again
                    if (vw.Frame.Width < ptWordLocation.X + lbl.Bounds.Size.Width) {
                        ptWordLocation.X = 0f;
                        ptWordLocation.Y += lbl.Frame.Size.Height;
                        lbl.Text.Trim ();
                    }

                    lbl.Frame = new RectangleF (ptWordLocation.X, ptWordLocation.Y, lbl.Frame.Size.Width, lbl.Frame.Size.Height);
                    vw.AddSubview (lbl);

                    //Update the horizontal width
                    ptWordLocation.X += lbl.Frame.Size.Width;
                }
            }
        }

回答by AlgoCoder

Click Hereto know how to set Listener for textView

单击此处了解如何为 textView 设置侦听器

and Add

并添加

     UITapGestureRecognizer *listener = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapAction:)];

Write the action you want to do within the

写下你想做的动作

 - (void)tapAction:(UITapGestureRecognizer *)sender
{
}

Add the listener to the view by

通过以下方式将侦听器添加到视图

      [self.view addGestureRecognizer:listener];

回答by Vinicius Carvalho

My solution for clickable links to action is this,

我的可点击操作链接的解决方案是这样的,

myLabel.automaticLinkDetectionEnabled = YES;
myLabel.urlLinkTapHandler = ^(KILabel *myLabel, NSString *string, NSRange range) {
            [self attemptOpenURL:[NSURL URLWithString:string]];
            NSLog(@"URL tapped %@", string);
        };

Check this UILabel class too, this will help you.

也检查这个 UILabel 类,这会对你有所帮助。

https://github.com/Krelborn/KILabel

https://github.com/Krelborn/KILabel

回答by leafcutter

I used Erik's solution but needed to do it with Swift. After converting I found a small problem where if you have a lot of text (more than a single line) before you got to a link then it wasn't getting wrapped properly so I added a function to fit the text.

我使用了 Erik 的解决方案,但需要使用 Swift 来完成。转换后,我发现了一个小问题,如果您在到达链接之前有很多文本(多于一行),那么它没有被正确包装,所以我添加了一个适合文本的函数。

func setText(newText:String){

    // 1. Split the localized string on the # sign:
    let localizedStringPieces:NSArray = newText.componentsSeparatedByString("#")

    // 2. Loop through all the pieces:
    var msgChunkCount:Int = localizedStringPieces.count

    var wordLocation:CGPoint = CGPointMake(0.0, 0.0)

    for (var i:Int = 0; i < msgChunkCount; i++){

        let chunk:String = localizedStringPieces[i] as! String

        if chunk == ""{
            continue;     // skip this loop if the chunk is empty
        }

        // 3. Determine what type of word this is:
        let isTermsOfServiceLink:Bool = chunk.hasPrefix("<ts>")
        let isPrivacyPolicyLink:Bool  = chunk.hasPrefix("<pp>")
        let isLink:Bool = (Bool)(isTermsOfServiceLink || isPrivacyPolicyLink)


        var remainingText:String = chunk

        while count(remainingText)>0{

            // 4. Create label, styling dependent on whether it's a link:
            let label:UILabel = UILabel()
            label.font = UIFont.systemFontOfSize(methodFontSize)
            label.text = remainingText
            label.userInteractionEnabled = isLink

            if (isLink){
                label.textColor = UIColor(red: 110/255, green: 181/255, blue: 229/255, alpha: 1.0)
                label.highlightedTextColor = UIColor.yellowColor()

                // 5. Set tap gesture for this clickable text:
                var selectorAction:Selector =  isTermsOfServiceLink ? "tapOnTermsOfServiceLink" : "tapOnPrivacyPolicyLink"

                let tapGesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: selectorAction)

                label.addGestureRecognizer(tapGesture)

                // Trim the markup characters from the label:
                if (isTermsOfServiceLink){
                    label.text = label.text?.stringByReplacingOccurrencesOfString("<ts>", withString: "", options: nil, range: nil)
                }
                if (isPrivacyPolicyLink){
                    label.text = label.text?.stringByReplacingOccurrencesOfString("<pp>", withString: "", options: nil, range: nil)
                }
            }else{
                label.textColor = UIColor.whiteColor()
            }

            // If this chunk of text doesn't fit at end of this line, then move it to the next
            // line and make sure any leading spaces are stripped off so it aligns nicely:

            label.sizeToFit()

            let labelHeight = label.frame.size.height

            var leftOverText:String = fitLabelToWidth(label, width: self.textContainer.frame.size.width - wordLocation.x)

            // if we can't fit anything onto this line then drop down
            if label.text == "" {
                //drop to a new line
                wordLocation.x = 0.0                       // move this word all the way to the left...

                wordLocation.y += labelHeight;  // ...on the next line.  (Have to use a constant here because if label has no text it also has no height)

                // refit the text
                label.text = remainingText
                leftOverText = fitLabelToWidth(label, width: self.textContainer.frame.size.width - wordLocation.x)

                //NB WE ARE ASSUMING HERE THAT AFTER DROPPING DOWN AT LEAST SOME OF THIS TEXT WILL FIT
                // IF THIS ISN'T THE CASE THEN THE LINE WOULD ALWAYS BE TOO BIG AND WE WOULD NEVER BE ABLE TO FIT IT ON ANYWAY!
            }

            // Set the location for this label:
            label.frame = CGRectMake(wordLocation.x, wordLocation.y, label.frame.size.width, label.frame.size.height)

            // Show this label:
            self.textContainer.addSubview(label)

            // Update the horizontal position for the next word:
            wordLocation.x += label.frame.size.width;

            // update our remaining text and get ready to go again
            remainingText = leftOverText
        }

    }

}

// fit the text label (formatted externally) to the desired with, chopping off text to make it so
// return the remaining text that didn't make the cut as a string
func fitLabelToWidth(label:UILabel, width:CGFloat)->String{
    let startingText:String = label.text!
    println("Trying to fit ::\(startingText)::")


    // if the string is null then we are done
    if startingText == ""{
        return ""
    }

    // if this fits already then we are done
    label.sizeToFit()
    if label.frame.size.width <= width{
        return ""
    }

    // so now we have to loop round trying to get this to fit
    var cutRange:Range<String.Index> = Range<String.Index>(start: startingText.startIndex, end: startingText.startIndex)
    var searchRange:Range<String.Index>

    var startSearchIndex:String.Index = startingText.startIndex
    var lastSearchIndex:String.Index = startSearchIndex

    var testText:String = ""
    var lastText:String = ""
    label.text = testText
    label.sizeToFit()

    while label.frame.size.width <= width{

        // store off the last used text as this might be as far as we go
        lastText = testText
        lastSearchIndex = startSearchIndex

        // set up the search range so we look for spaces missing out any previous ones
        searchRange = Range<String.Index>(start: startSearchIndex, end: startingText.endIndex)

        // cut out a range with the next occurrence of spaces
        cutRange = startingText.rangeOfString(" ", options: NSStringCompareOptions.CaseInsensitiveSearch, range: searchRange, locale: nil)!

        // get some text from the start of the string to our cut point (start)
        testText = startingText.substringToIndex(cutRange.startIndex)

        // move the search start to the point after the end of the spaces we just found
        startSearchIndex = cutRange.endIndex

        // try this in our label to see if it sizes ok
        label.text = testText
        label.sizeToFit()


    }

    // we leave the while when the string gets too big
    label.text = lastText
    label.sizeToFit()

    return startingText.substringFromIndex(lastSearchIndex)

}

回答by Kupendiran iOS

You can use below code to add tap gesture on UILable :-

您可以使用以下代码在 UILable 上添加点击手势:-

Step 1:

第1步:

Delegate "UIGestureRecognizerDelegate" to your viewcontroller.h 

for example: 
  @interface User_mail_List : UIViewController<UIGestureRecognizerDelegate>

Step 2:

第2步:

//create you UILable
UILabel *title_lbl= [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)];
[title_lbl setText:@"u&me"];
[title_lbl setUserInteractionEnabled:YES];
[yourView addSubview:title_lbl];

Step 3:

第 3 步:

UITapGestureRecognizer *tap= [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(Prof_lbl_Pressed:)];//your action selector
[tap setNumberOfTapsRequired:1];
title_lbl.userInteractionEnabled= YES;
[title_lbl addGestureRecognizer:tap];

Step 4:

第四步:

-(void)Prof_lbl_Pressed:(id)sender{
   //write your code action
}

thanks,

谢谢,

回答by Balram Tiwari

You can Use multiple overlapping UILabelwith userInteractionEnabled = YESon it & add a UITapGestureRecognizeron that label with different bold fonts.

您可以在其上使用多个重叠UILabel并使用不同的粗体在该标签上userInteractionEnabled = YES添加一个UITapGestureRecognizer

Hereis one such example of doing it.

是一个这样的例子。

Something like thiscan also be tried.

也可以尝试这样的事情。

If you want a working solution of this then you can try "Fancy-Label". Search in that link for text "Here's my implementation" & Click it. You will get ready to use product. Don't forget to click the "Switch" button on the app that you run using the above sample.

如果你想要一个可行的解决方案,那么你可以尝试"Fancy-Label"。在该链接中搜索文本“这是我的实现”并单击它。您将准备好使用产品。不要忘记单击使用上述示例运行的应用程序上的“切换”按钮。

I hope that help you a lot.

我希望对你有很大帮助。