关于ios:如何在视觉上使UILabel的一部分成为块引用?

How do I make a part of a UILabel visually be a block quote?

如何使UILabel的特定部分看起来像是块引用,还是在文本的左侧有垂直线? TextKit会进来吗?如果是这样,怎么办?

Mail.app可以做到这一点(请参见彩色部分及其侧面的线):

enter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+ (instancetype)threadViewWithLabelText:(NSString *)labelText
                           textViewText:(NSString *)textViewText
                                  color:(UIColor *)color
{
    ThreadView *threadView = [[[NSBundle mainBundle] loadNibNamed:@"ThreadView"
                                                            owner:self
                                                          options:nil] firstObject];
    if (threadView) {
        threadView.label.text = labelText;
        threadView.textView.text = textViewText;
        threadView.colorView.backgroundColor = color;
    }
    return threadView;
}

- (void)addCommentView:(ThreadView *)threadView
      toViewController:(UIViewController *)viewController
{
    threadView.frame = CGRectMake(self.frame.origin.x + 25,
                                  self.textView.frame.origin.y + self.textView.frame.size.height,
                                  self.frame.size.width - (self.frame.origin.x + 10),
                                  self.frame.size.height - (self.textView.frame.origin.y + self.textView.frame.size.height));
    [viewController.view addSubview:threadView];
}

现在,在主视图控制器中,我们可以仅通过以下两个方法调用来创建和添加这些视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)viewDidLoad
{
    [super viewDidLoad];

    // Load the first post
    ThreadView *originalPost = [ThreadView  threadViewWithLabelText:@"10 Some Words 2014 More Words"
                                                       textViewText:loremIpsum
                                                              color:[UIColor blueColor]];
    originalPost.frame = CGRectMake(self.view.frame.origin.x + 8,
                                    self.view.frame.origin.y + 15,
                                    self.view.frame.size.width - 8,
                                    self.view.frame.size.height - 15);
    [self.view addSubview:originalPost];

    // Load a comment post
    ThreadView *commentPost = [ThreadView threadViewWithLabelText:@"12 December 2014 Maybe A Username"
                                                     textViewText:loremIpsum
                                                            color:[UIColor greenColor]];
    [originalPost addCommentView:commentPost
                toViewController:self];
}

这将为我们提供如下图所示的结果。这段代码可以使用一些重构/重组,但这应该可以帮助您入门。您还可以混合使用自动布局和/或设置视图的框架。

Final


这可以通过Text Kit轻松完成。我在我的应用程序中做了类似的事情。区别在于我使用方框(如有需要可嵌套)来标记每个文本块。这是您应该执行的操作:

  • 解析html字符串(或用于标记文本的任何内容),使用自定义属性(例如MyTextBlockAttribute)标记每个文本块引用,保存每个文本块的范围(即块引用)并将其作为属性添加到属性字符串的相关范围(从您的内容构造此属性字符串)和附加到内容的列表。让我们将此列表称为MyTextBlockList

  • Text Kit自己绘制文本。首先绘制背景(白色,浅灰色等。等等),然后绘制文本或垂直线。由于您可以循环遍历列表来获取每个文本块的范围,因此可以使用方法[NSLayoutManager range: inTextContainer:textContainer]获得这些块的边界矩形。

  • 这是我在应用中使用的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    // subclass of NSTextContainer
    #import"MyTextContainer.h"
    #import"MyBlockAttribute.h"
    @interface MyTextContainer ()
    @property (nonatomic) BOOL isBlock;
    @end

    @implementation MyTextContainer
    - (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect
                                      atIndex:(NSUInteger)characterIndex
                             writingDirection:(NSWritingDirection)baseWritingDirection
                                remainingRect:(CGRect *)remainingRect {

        CGRect output = [super lineFragmentRectForProposedRect:proposedRect
                                                       atIndex:characterIndex
                                              writingDirection:baseWritingDirection
                                                 remainingRect:remainingRect];

        NSUInteger length = self.layoutManager.textStorage.length;

        MyTextBlockAttribute *blockAttribute;
        if (characterIndex < length) {
            blockAttribute = [self.layoutManager.textStorage attribute:MyTextBlockAttributeName atIndex:characterIndex effectiveRange:NULL]; // MyTextBlockAttributeName is a global NSString constant
        }

        if (blockAttribute) { // text block detected, enter"block" layout mode!
            output = CGRectInset(output, blockAttribute.padding, 0.0f); // set the padding when  constructing the attributed string from raw html string, use padding to control nesting, inner boxes have bigger padding, again, this is done in parsing pass
            if (!self.isBlock) {
                self.isBlock = YES;
                output = CGRectOffset(output, 0.0f, blockAttribute.padding);
            }
        } else if (self.isBlock) {
            self.isBlock = NO; // just finished a block, return back to the"normal" layout mode
        }

        // no text block detected, not just finished a block either, do nothing, just return super implementation's output

        return output;
    }


    @end


    // drawing code, with drawRect: or other drawing technique, like drawing into bitmap context, doesn't matter
    - (void)drawBlockList:(NSArray *)blockList content:(MyContent *)content {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetLineWidth(context, 0.5f);
        [[UIColor colorWithWhite:0.98f alpha:1.0f] setFill];
        CGContextSaveGState(context);
        MyTextContainer *textContainer = content.textContainer;

        // since I draw boxes, I have to draw inner text block first, so use reverse enumerator
        for (MyTextBlockAttribute *blockAttribute in [blockList reverseObjectEnumerator]) {
            if (blockAttribute.noBackground) { // sometimes I don't draw boxes in some conditions
                continue;
            }

            CGRect frame = CGRectIntegral([content.layoutManager boundingRectForGlyphRange:blockAttribute.range inTextContainer:textContainer]);
            frame.size.width = textContainer.size.width - 2 * (blockAttribute.padding - MyDefaultMargin); // yeah... there is some margin around the boxes, like html's box model, just some simple math to calculate the accurate rectangles of text blocks
            frame.origin.x = blockAttribute.padding - MyDefaultMargin;
            frame = CGRectInset(frame, 0, -MyDefaultMargin);
            if (blockAttribute.backgroundColor) { // some text blocks may have specific background color
                CGContextSaveGState(context);
                [blockAttribute.backgroundColor setFill];
                CGContextFillRect(context, frame);
                CGContextRestoreGState(context);
            } else {
                CGContextFillRect(context, frame);
            }

            CGContextStrokeRect(context, frame); // draw borders of text blocks in the last
        }
        CGContextRestoreGState(context);
    }


    - (UIImage *)drawContent:(MyContent *)content {
        UIImage *output;
        UIGraphicsBeginImageContextWithOptions(content.bounds.size, YES, 0.0f); // bounds is calculated in other places
        [[UIColor whiteColor] setFill];
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:content.bounds];
        [path fill];

        [self drawBlockList:content.blockList content:content]; // draw background first!
        [content.layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, content.textStorage.length) atPoint:CGPointZero]; // every content object has a set of Text Kit core objects, textStorage, textContainer, layoutManager

        output = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        return output;
    }

    在您的情况下,您不绘制框,而是绘制左边框。技术是一样的,希望对您有帮助!


    尝试这个?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    NSString *html =[NSString stringWithFormat:
    @"<html>"
    "  <head>"
    "    <style type='text/css'>"
    "ul"
    "{"
    "    list-style-type: none;"
    "}"
    "    </style>"
    "  </head>"
    "  <body>"
    "%@ - PARENT"
    "
    <ul>
    "

    "
    <li>
    "

    "%@ - CHILD 1"
    "
    </li>
    "

    "
    <li>
    "

    "%@ - CHILD 2"
    "
    </li>
    "

    "
    </ul>
    "

    "</body>"
    "</html>"
    ,@"Parent Title", @"Child Description 1", @"Child Description 2"];


    NSError *err = nil;
    _label.attributedText =
    [[NSAttributedString alloc]
     initWithData: [html dataUsingEncoding:NSUTF8StringEncoding]
     options: @{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType }
     documentAttributes: nil
     error: &err];
    if(err)
        NSLog(@"Unable to parse label text: %@", err);

    Result

    结果就是这样。


    这听起来似乎违反直觉,但是您是否考虑过将所有内容都弹出到tableView中?您可以利用indentLevelAtIndexPath:stuff ....


    如果您的目标iOS版本低于7,则可以使用Core Text做类似的事情,但是由于Core Text是一种旧的C opaque类型实现,因此我建议您使用DTCoreText。
    如果使用> = iOS7,则可以使用NSAttributed字符串和NSXMLDocument。即使3.x提供了属性字符串,他们也仅将其添加到了iOS6的UIKIT对象中,并从根本上改变了将UIKit管理到iOS7中的行为。
    NSXMLDocument很有用,因为您可以将表示它们的字符串呈现为HTML。