如何将UILabel的一部分直观地作为块引用?

时间:2014-12-14 07:44:39

标签: ios objective-c cocoa-touch uilabel textkit

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

Mail.app执行此操作(请参阅彩色部分及其侧面的行):

enter image description here

如何在不使用多个UILabel的情况下复制此效果(因为我动态创建它会相当粗糙)?

5 个答案:

答案 0 :(得分:6)

XIB file

使用如上图所示的常规布局创建视图(XIB)。有一个UILabel,一个UITextView和一个UIView(蓝色矩形是一个背景颜色设置的UIView)。我们称之为ThreadView.xib。将标签,textview和视图作为属性连接到视图。

然后我们可以有一个方法来生成其中一个视图供我们使用,一个方法根据帖子有多少评论/回复添加更多ThreadViews作为子视图。

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

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

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

答案 1 :(得分:3)

试试这个?

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

结果是这样的。

答案 2 :(得分:3)

使用Text Kit可以轻松完成此操作。我在我的应用程序中做了这样的事情。区别在于我使用框(如果需要,嵌套)来标记每个文本块。这是你应该做的:

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

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

  3. 以下是我在我的应用中使用的代码:

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

    在您的情况下,您不绘制框,而是绘制左边框。技术是一样的,希望这可以帮到你!

答案 3 :(得分:2)

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

答案 4 :(得分:2)

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