iOS UITextView或UILabel,带有可单击的动作链接

时间:2013-12-12 11:00:46

标签: ios objective-c

我想制作一个UILabelUITextView,其中包含一些带有2个可点击链接的文字。不是指向网页的链接,但我想将这两个链接与我对UIButton的操作相关联。我见过的所有例子都是webviews的链接,但我不想这样。同样,文本将以其他语言翻译,因此这些位置必须是动态的。

想要这样做:

enter image description here

10 个答案:

答案 0 :(得分:98)

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

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

这是我最终做的事情:

<强>概要

  • 在您的英文消息中有非常基本的自定义标记,以便您可以解析不同的部分
  • 指示您的译员留下标记并翻译其余部分
  • 拥有一个可以作为此消息容器的UIView
  • 将您的英文消息分成几部分,将常规文本与可点击文本分开
  • 为每件作品在容器UIView上创建一个UILabel
  • 对于可点击的片段,设置样式,允许用户交互并创建您的点击手势识别器
  • 做一些非常基本的簿记,把话语完美地排成一行

<强> DETAIL:

在视图控制器中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.")];

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

以下是将消息放在一起的方法 - 见下文。首先,我将#字符(或更确切地说是@"#"字符串)分解为英文消息。这样我就得到了每个我需要单独创建标签的部分。我循环遍历它们并查找<ts><pp>的基本标记,以检测哪些部分是指向哪些部分的链接。如果我正在使用的文本块是一个链接,那么我会设置一个样式并为其设置一个轻敲手势识别器。我当然也删除了标记字符。我认为这是一种非常简单的方法。

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

- (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;
  }
}

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

- (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");
  }
}

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

以下是它在应用中的外观:

Simulator screenshot of the end result

祝你好运! : - )

埃里克

答案 1 :(得分:36)

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

关键原则:

  1. 使用NSAttributedString作为定义点按链接的方式。
  2. 使用UITextViewDelegate来抓住链接的压力。
  3. 定义网址字符串:

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

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

    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)
    

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

    textView.attributedText = attributedOriginalText
    

    实施UITextViewDelegate (这实际上是阻止网址打开某个网站以及您可以在哪里定义自定义操作的关键部分):

    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
    }
    

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

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

    我如何为UILabel实施自定义操作:

    我通常最终使用TTTAttributedLabel

答案 2 :(得分:22)

以下是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
  }
}

答案 3 :(得分:10)

检查这个UILabel课程,这一定会对你有所帮助。我用这个做了同样的事情。

TTTAttributedLabel

答案 4 :(得分:5)

以下是对于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;
                }
            }
        }

答案 5 :(得分:2)

Click Here知道如何为textView设置Listener

并添加

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

中编写您想要执行的操作
 - (void)tapAction:(UITapGestureRecognizer *)sender
{
}

通过

将侦听器添加到视图中
      [self.view addGestureRecognizer:listener];

答案 6 :(得分:1)

我使用了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)

}

答案 7 :(得分:1)

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

第1步:

Delegate "UIGestureRecognizerDelegate" to your viewcontroller.h 

for example: 
  @interface User_mail_List : UIViewController<UIGestureRecognizerDelegate>

第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];

第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];

第4步:

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

感谢,

答案 8 :(得分:1)

我的可点击链接指向行动的解决方案是

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

检查这个UILabel课程,这对你有所帮助。

https://github.com/Krelborn/KILabel

答案 9 :(得分:0)

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

Here就是这样做的一个例子。

也可以试用like this

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

我希望能帮到你很多。