iOS UITextView или UILabel с кликабельными ссылками на действия [дубликат]


этот вопрос уже есть ответ здесь:

  • Создать tap-способные "ссылки" в NSAttributedString UILabel? 26 ответов

Я хочу сделать UILabel или UITextView с некоторым текстом с 2 кликабельными ссылками в нем. Не ссылки на веб-страницы, но я хочу связать эти 2 ссылки с действиями, как я бы сделала с UIButton. Все примеры я видел ссылки на этого, но я не хочу этого. Также этот текст будет переведен на другие языки, поэтому позиции должны быть динамичными.

хотят сделать так:

10 67

10 ответов:

мне нужно было решить эту точно такую же проблему: очень похожий текст с этими двумя ссылками в нем, по нескольким строкам, и он должен быть переведен на любой язык (включая разные порядки слов и т. д.). Я просто решил это, так что позвольте мне поделиться, как я это сделал.

Первоначально я думал, что я должен создать приписанный текст, а затем сопоставить местоположение касания крана с регионами в этом тексте. Хотя я думаю, что это выполнимо, я также думаю, что это слишком сложно подход.

это то, что я в конечном итоге сделал вместо этого:

резюме:

  • есть очень простой пользовательской разметки в вашем английском сообщении, так что вы можете разобрать различные части
  • попросите ваших переводчиков оставить разметку и перевести остальные
  • есть UIView, который может служить контейнером этого сообщения
  • разбейте ваше английское сообщение на части, чтобы отделить обычный текст от кликабельный текст
  • для каждой части создайте UILabel на контейнере UIView
  • для кликабельных частей установите свой стиль, разрешите взаимодействие с пользователем и создайте распознаватель жестов крана
  • сделайте некоторые очень простой бухгалтерии, чтобы разместить слова идеально через линии

деталь:

в контроллере вида 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> to определите, какие части являются ссылками на то, что. Если кусок текста, с которым я работаю, является ссылкой, то я немного стилизую и настраиваю распознаватель жестов для него. Я также вычеркиваю символы разметки, конечно. Я думаю, что это очень простой способ сделать это.

обратите внимание на некоторые тонкости, такие как то, как я обрабатываю пробелы: я просто беру пробелы из (локализованной) строки. Если нет пробелов (китайский, японский), то между кусками также не будет пробелов. Если есть пробелы, то те автоматически пробел из кусков по мере необходимости (например, для английского языка). Когда мне нужно поместить слово в начале следующей строки, Мне нужно убедиться, что я удаляю любой префикс пробела из этого текста, потому что в противном случае он не выравнивается должным образом.

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

удачи! : -)

Ерик

как я реализую пользовательские текстовые действия (например, кнопка) для 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)

присвоить строку атрибутов a просмотр текста:

textView.attributedText = attributedOriginalText

реализовать UITextViewDelegate (это действительно ключевая часть a предотвращает открытие 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.

вот полный пример, сделанный в 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
  }
}

проверьте этот класс UILabel, это, безусловно, поможет вам . Я сделал то же самое, используя это .

TTTAttributedLabel

вот переведенная версия принятого ответа на C# для 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;
                }
            }
        }

Нажмите Здесь чтобы знать, как установить прослушиватель для textView

и добавить

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

напишите действие, которое вы хотите сделать в пределах

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

добавить слушателя в представление с помощью

      [self.view addGestureRecognizer:listener];

я использовал решение Эрика, но нужно было сделать это с помощью 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)

}

вы можете использовать ниже код, чтобы добавить жест крана на 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
}

спасибо,

мое решение для кликабельных ссылок на действие это,

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

вы можете использовать несколько перекрывающихся UILabel с userInteractionEnabled = YES на нем и добавить UITapGestureRecognizer на этой этикетке с разными жирными шрифтами.

здесь является одним из таких примеров этого.

что-то такой также можно попробовать.

Если вы хотите рабочее решение этого, то вы можете попробовать "Fancy-Label". Найдите в этой ссылке текст "вот моя реализация" и нажмите на него. Вы получите готовый к использованию продукт. Не забудьте нажмите кнопку "переключатель" на приложение, которое вы запускаете с помощью приведенного выше примера.

Я надеюсь, что это поможет вам много.