How can I make a UILabel part visually block quote?

How do I make a specific part of a UILabel look like a UILabel , or is there a vertical line on the left side of the text? Will TextKit come here? If so, how?

Mail.app does this (see the colored parts and the line on their side):

enter image description here

How would I replicate this effect without using multiple UILabel (which, since I create it dynamically, will be pretty rude)?

+8
ios objective-c cocoa-touch uilabel textkit
source share
5 answers

Xib file

Create a view (XIB) with this shared layout, as shown in the image above. There is UILabel, UITextView and UIView (the blue rectangle is a UIView with a set of background colors). Let me call it ThreadView.xib. Include the shortcut, text view, and view as presentation properties.

Then we can create a way to generate one of these views for use and a method to add more ThreadView threads as subtasks based on how many comments / replies the message has.

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

Now, in the main view controller, we can create and add these views only with these two method calls:

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

This will give us the result, as in the figure below. This code may use some refactoring / restructuring, but this should get you started. You can also mix use of autorun and / or set viewing frames.

Final result

+5
source share

If you focus on iOS below 7, you can do something similar using Core Text, but since Core Text is a kind of old implementation of opaque C types, I suggest you use DTCoreText .
If you use> = iOS7, you can use the string NSAttributed and NSXMLDocument. Even if the attribute string is accessible from 3.x, they only added them to UIKIT objects in ios6 and radically changed the behavior of UIKit when managing them in iOS7.
NSXMLDocument is useful because you can display your own string representing them as HTML.

+2
source share

Give it a try

 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

And the result will be like that.

+2
source share

This may seem counterintuitive, but do you think this is all in the tableView? you can use indentLevelAtIndexPath: stuff ....

+2
source share

This can be easily done using the Text Kit . I do as in my application. The difference is that I use fields (nested if necessary) to mark each text block. Here is what you should do:

  • Parse html string (or what you use to label text), mark each text block quote with a custom attribute, such as MyTextBlockAttribute , save the ranges of each text block (e.g. block quote) and add it as an attribute of the associated range of the attribute string (create this attribute string from your content) and a list attached to the content. Lets call this list MyTextBlockList .

  • draw text using the Text Kit yourself. first draw a background (white, light gray, etc., etc.), draw text or vertical lines. Since you can get each range of text blocks in a loop through the list, you can get the bounding box of these blocks using the [NSLayoutManager range: inTextContainer:textContainer] .

Here is the code I used in my application:

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

In your case, you are not drawing boxes; instead, you are drawing left borders. The technique is the same, I hope this helps you!

+2
source share

All Articles