2014-12-14 8 views
8

Come faccio a rendere una parte specifica di un UILabel un blocco o una riga verticale sul lato sinistro del testo? TextKit sarebbe arrivato qui? Se é cosi, come?Come faccio a rendere visivamente una parte di un UILabel una citazione di blocco?

Mail.app significa questo (vedi le porzioni colorate e la linea sul lato di loro):

enter image description here

Come faccio a replicare questo effetto senza l'utilizzo multiplo UILabel s (che come sto crearlo dinamicamente sarebbe piuttosto grossolano)?

risposta

2

Se stai puntando a iOS inferiore a 7, puoi fare qualcosa di simile usando Core Text, ma poiché Core Text è una specie di vecchia implementazione di tipi opachi di C, ti suggerisco di usare DTCoreText.
Se si utilizza> = iOS7, è possibile utilizzare la stringa NSAttribuita e NSXMLDocument. Anche se le string attribuite sono disponibili da 3.x, le hanno solo aggiunte negli oggetti UIKIT in ios6 e cambiato radicalmente il comportamento di UIKit nella loro gestione in iOS7.
NSXMLDocument è utile perché è possibile eseguire il rendering della stringa che li rappresenta come HTML.

+0

Come utilizzerei NSAttributedString per questo? –

+0

Sfortunatamente non ho uno snippet per questo, il problema principale sarebbe con la linea di bordo colorata che puoi usare un allegato di immagine check qui http://www.objc.io/issue-5/getting-to-know-textkit .html – Andrea

2

Prova questo?

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

e il risultato è come questo.

+0

La parte chiave è purtroppo quella riga sul lato del testo rientrato. Puoi replicarlo? –

+0

Ah! I confini non sono possibili con questo metodo purtroppo. –

2

Questo può sembrare controintuitivo, ma avete preso in considerazione l'idea di scattare tutto in una tabellaView? è possibile sfruttare l'indentLevelAtIndexPath: roba ....

+0

Prestazioni che non sarebbero preferibili. –

+1

um ..... UITableView è estremamente performante! Non so quante migliaia di righe di testo stai guardando qui, ma con il riciclo cellulare ecc. Sono stupito che tu pensi in questo modo ... Comunque, ognuno per conto proprio, buona fortuna con questo :) – Jef

5

XIB file

creare una vista (XIB) con questo layout generale, come l'immagine qui sopra. C'è un UILabel, un UITextView e un UIView (il rettangolo blu è un UIView con il set di colori di sfondo). Chiamiamolo ThreadView.xib. Collega l'etichetta, la vista testo e visualizza come proprietà alla vista.

Possiamo quindi avere un metodo per generare una di queste visualizzazioni da utilizzare e un metodo per aggiungere più visualizzazioni di discussione come visualizzazioni secondarie in base al numero di commenti/risposte di un post.

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

Ora, nel controller della vista principale, siamo in grado di creare e aggiungere questi punti di vista con solo questi due metodi chiamate:

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

questo ci darà un risultato come nella foto qui sotto. Questo codice potrebbe utilizzare alcuni refactoring/ristrutturazione, ma questo dovrebbe iniziare. È anche possibile mescolare l'uso dell'autolayout e/o impostare i frame delle viste.

Final result

+0

SI! ! vincola l'altezza della vista 'stripe' a quella di textView, chiama a sizeToFit dopo setText: e tira su il fratello di tuo padre. Molto intelligente – Jef

2

Questo può essere fatto facilmente con Text Kit. Faccio cose del genere nella mia app. La differenza è che utilizzo le caselle (nidificate se necessario) per contrassegnare ciascun blocco di testo. Ecco cosa si dovrebbe fare:

  1. analizzare HTML stringa (o qualsiasi altra cosa si utilizza per contrassegnare il testo), contrassegnare ogni citazione blocco di testo con un attributo personalizzato, come MyTextBlockAttribute, salvare intervalli di ciascun blocco di testo (vale a direblock quote) e aggiungilo come attributo al relativo intervallo della stringa attribuita (costruisci questa stringa attribuita dal tuo contenuto) e l'elenco allegato al contenuto. Chiamiamo questo elenco MyTextBlockList.

  2. disegnare testo con Text Kit da soli. per prima cosa disegna lo sfondo (colore bianco, colore grigio chiaro, ecc., ecc.), quindi disegna testo o linee verticali. Poiché è possibile ottenere l'intervallo di ciascun blocco di testo eseguendo un ciclo attraverso l'elenco, è possibile ottenere il limite di definizione di questi blocchi con il metodo [NSLayoutManager range: inTextContainer:textContainer].

Ecco il codice che ho usato nella mia app:

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

Nel tuo caso, non si disegna scatole, peschi sinistra invece confini. La tecnica è la stessa, spero che questo possa aiutarti!

Problemi correlati