【问题标题】:iOS UITextView or UILabel with clickable links to actions [duplicate]iOS UITextView 或 UILabel 带有可点击的动作链接[重复]
【发布时间】:2013-12-12 11:00:45
【问题描述】:

我想制作一个UILabelUITextView,其中包含一些带有2 个可点击链接的文本。不是指向网页的链接,但我想将这两个链接与我对UIButton 所做的操作链接起来。我见过的所有示例都是指向 webviews 的链接,但我不希望这样。此外,文本将被翻译成其他语言,因此位置必须是动态的。

想做这个:

【问题讨论】:

  • 您可以简单地将透明按钮重叠在带下划线的文本上。
  • 你也可以做手势
  • 我不能在文本上重叠透明按钮,因为它会被翻译成其他语言,所以位置会改变。
  • 为什么不想使用网络视图?
  • 因为条款页面上的文字不多,我希望它对用户来说很活泼。同样,只想知道是否可能。最后我可能不得不做网络视图。

标签: ios objective-c


【解决方案1】:

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

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

这就是我最终所做的:

总结:

  • 在您的英文信息中包含非常基本的自定义标记,以便您可以解析出不同的部分
  • 指示您的翻译人员保留标记并翻译其余部分
  • 有一个 UIView 可以作为这个消息的容器
  • 将您的英文信息分成几部分,将常规文本与可点击文本分开
  • 为每个部分在容器 UIView 上创建一个 UILabel
  • 对于可点击的部分,设置您的样式,允许用户交互并创建您的点击手势识别器
  • 做一些非常基本的簿记,以完美地跨行放置单词

详情:

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

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

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

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

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

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

这是它在应用中的外观:

祝你好运! :-)

埃里克

【讨论】:

  • 不错的解决方案。如果我想强调隐私和条款文本而不是让它变得丰富多彩怎么办?
  • 给单词加下划线的最好方法是设置一个属性字符串。您可以在属性字符串中指定下划线,并且有一些不同的下划线选项可供选择。与例如自己绘制下划线相比,这样做的好处是,下划线将整齐地在“g”、“j”和“y”等低垂字母上被打破。
  • 例如,要设置一个漂亮的粗下划线,你可以这样做: NSMutableAttributedString *underlinedString = [[NSMutableAttributedString alloc] initWithString:label.text]; [underlinedString addAttribute:(NSString *)kCTUnderlineStyleAttributeName value:[NSNumber numberWithInt:kCTUnderlineStyleThick] range:(NSRange){prefix.length, word.length}]; label.attributedText = underlinedString;
  • 当您输入时,当您开始输入“kCTUnderline”时,您会看到许多不同的下划线样式选项 - 只需从列表中选择您想要的样式:虚线,实心、双倍、厚实等。
  • 下划线摘要:遵循与我在解决方案中发布的相同代码,但用通过 NSMutableAttributedString 设置下划线替换为单词着色的逻辑。
【解决方案2】:

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

关键原则:

  1. 使用NSAttributedString 定义要点击的链接。
  2. 使用UITextViewDelegate 抓住链接的点击率。

定义一个 URL 字符串:

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(这确实是防止 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
}

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

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

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

我通常最终使用TTTAttributedLabel

【讨论】:

  • 这是 IMO 的最佳答案。很简单
  • 重要提示:文本视图中的链接只有在文本视图可选但不可编辑时才具有交互性。也就是说,如果UITextView selectable属性的值为YES,isEditable属性的值为NO。
  • UIButton可以这样做吗?
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    TTTAttributedLabel

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

        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

        【讨论】:

        • 我使用了这个库,但是这些案例并没有在那个库中处理;例如:特殊字符上的标签中断,以“@”开头的字符串,但它不是真正的标签。等等等等
        【解决方案7】:

        Click Here知道如何为textView设置Listener

        并添加

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

        中写下你想要做的动作
         - (void)tapAction:(UITapGestureRecognizer *)sender
        {
        }
        

        将监听器添加到视图中

              [self.view addGestureRecognizer:listener];
        

        【讨论】:

        • 别忘了将userInteractionEnabled设置为YES
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          您可以使用以下代码在 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
          }
          

          谢谢,

          【讨论】:

            【解决方案10】:

            您可以使用多个重叠的UILabeluserInteractionEnabled = YES,并在该标签上添加不同粗体字体的UITapGestureRecognizer

            Here 就是这样一个例子。

            like this的东西也可以试试。

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

            希望对你有很大帮助。

            【讨论】:

              猜你喜欢
              • 2020-05-02
              • 2015-09-16
              • 1970-01-01
              • 1970-01-01
              • 2015-11-28
              • 1970-01-01
              • 2014-07-04
              • 2013-11-20
              相关资源
              最近更新 更多