如何为NSAttributedString的不同部分添加不同的行间距?

时间:2022-02-15 20:45:42

I want to have a single NSAttributedString contain multiple messages. If a single message has a long text and it wraps around, I want to have a line spacing of, say, 5. Because I have a single NSAttributedString contain multiple messages, I want to have a bigger line spacing between each message; let's say 20.

我想让一个NSAttributedString包含多个消息。如果一条消息有一个长文本并且它包裹着,我希望有一个行间距,比方说,5。因为我有一个NSAttributedString包含多个消息,我想在每个消息之间有一个更大的行间距;让我们说20。

What I want

如何为NSAttributedString的不同部分添加不同的行间距?

The 'I see' is one message. The 'I'd think it'd be both...' is one message, although it wraps down to two lines and 'Like a one way chat' is one message.

'我看'是一条信息。 “我认为这两者都是......”是一条消息,虽然它包含了两行,而“像单向聊天”是一条消息。

Notice how the line spacing between the 2nd and 3rd is smaller than the 1st and 2nd and between the 3rd and 4th.

注意第2和第3之间的行间距小于第1和第2以及第3和第4之间的行距。

What I've tried

I am appending a \n to the end of each message and I've tried using NSParagraphStyle which gives me control of the line spacing, but it seems to be all or nothing:

我在每条消息的末尾附加一个\ n,我尝试使用NSParagraphStyle来控制行间距,但它似乎全有或全无:

        // index is the index of the group of messages as I iterate through them
        // contentText is an NSMutableAttributedString
        if index != messages.count - 1 {
            let style = NSMutableParagraphStyle()
            style.lineSpacing = 40.0

            let lineReturn = NSMutableAttributedString(string: "\n")
            contentText.appendAttributedString(lineReturn)

            if index == 0 {
                contentText.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSMakeRange(contentText.length-lineReturn.length, lineReturn.length))
            } else {
                contentText.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSMakeRange(contentText.length-lineReturn.length-1, lineReturn.length+1))
            }
        }

If I add the line spacing to the beginning it will set the line spacing for the entire label.

如果我将行间距添加到开头,它将设置整个标签的行间距。

        if index == 0 {
            let style = NSMutableParagraphStyle()
            style.lineSpacing = 40.0
            contentText.addAttribute(NSParagraphStyleAttributeName, value: style1, range: NSMakeRange(start, 1))
        }

(This is really only my latest try.)

(这真的只是我最近的尝试。)

Thanks for any help! :)

谢谢你的帮助! :)

1 个解决方案

#1


1  

Details

  • 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

    指示您的翻译人员留下标记并翻译其余部分有一个UIView可以作为此消息的容器

  • 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

    为每件作品在容器UIView上创建一个UILabel

  • 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

    做一些非常基本的簿记,把话语完美地排成一行

    For Understand.

In the view controller's viewDidLoad I 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.

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

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

if you want to use gesture then use this method.

如果你想使用手势,那么使用此方法。

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

希望这可以帮助。我确信有更聪明,更优雅的方法可以做到这一点,但这是我能够想到的,它运作良好。

this answer display output like following screen shot...but you got idea from this answer.

这个答案显示输出像下面的屏幕截图...但你从这个答案得到了想法。

如何为NSAttributedString的不同部分添加不同的行间距?

#1


1  

Details

  • 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

    指示您的翻译人员留下标记并翻译其余部分有一个UIView可以作为此消息的容器

  • 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

    为每件作品在容器UIView上创建一个UILabel

  • 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

    做一些非常基本的簿记,把话语完美地排成一行

    For Understand.

In the view controller's viewDidLoad I 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.

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

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

if you want to use gesture then use this method.

如果你想使用手势,那么使用此方法。

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

希望这可以帮助。我确信有更聪明,更优雅的方法可以做到这一点,但这是我能够想到的,它运作良好。

this answer display output like following screen shot...but you got idea from this answer.

这个答案显示输出像下面的屏幕截图...但你从这个答案得到了想法。

如何为NSAttributedString的不同部分添加不同的行间距?