2013-02-27 2 views
15

Sto implementando una funzionalità "leggi di più" simile a quella dell'AppStore di Apple. Tuttavia, sto usando una multilinea UILabel. Guardando all'AppStore di Apple, come fanno a ridurre la larghezza dell'ultima linea visibile per adattarsi al testo "più" e troncare ancora la coda (vedi immagine)?Diminuisce la larghezza dell'ultima riga in multilinea UILabel

iBooks example image from AppStore

+0

Penso che sia necessario utilizzare un 'UIWebview' e caricare il proprio html personalizzato per realizzare questo – George

+0

Ok, davvero non voglio farlo. Sembra una brutta soluzione farlo in quel modo. So di poter dimensionare un 'UILabel' e troncarne la coda ... nel peggiore dei casi anche un' UITextView' .. ma non un 'UIWebView'. –

+0

Dove nell'AppStore di Apple vedi cosa hai raffigurato? Quello che vedo è l'etichetta che termina con un'ellissi e "... Altro" sotto il testo, probabilmente in un'etichetta diversa. – rdelmar

risposta

12

Questo sembra funzionare, almeno con la quantità limitata di test che ho fatto. Ci sono due metodi pubblici. Puoi usare quello più corto se hai più etichette tutte con lo stesso numero di linee - basta cambiare il kNumberOfLines in alto per abbinare quello che vuoi. Utilizzare il metodo più lungo se è necessario passare il numero di righe per diverse etichette. Assicurati di cambiare la classe delle etichette che fai in IB su RDLabel. Usa questi metodi invece di setText :. Questi metodi espandono l'altezza dell'etichetta su kNumberOfLines se necessario e, se sono ancora troncati, la espandono per adattarsi all'intera stringa al tocco. Attualmente puoi toccare qualsiasi punto dell'etichetta. Non dovrebbe essere troppo difficile cambiarlo, quindi tocca solo vicino a ... Mer causerebbe l'espansione.

#import "RDLabel.h" 
#define kNumberOfLines 2 
#define ellipsis @"...Mer ▾ " 

@implementation RDLabel { 
    NSString *string; 
} 

#pragma Public Methods 

- (void)setTruncatingText:(NSString *) txt { 
    [self setTruncatingText:txt forNumberOfLines:kNumberOfLines]; 
} 

- (void)setTruncatingText:(NSString *) txt forNumberOfLines:(int) lines{ 
    string = txt; 
    self.numberOfLines = 0; 
    NSMutableString *truncatedString = [txt mutableCopy]; 
    if ([self numberOfLinesNeeded:truncatedString] > lines) { 
     [truncatedString appendString:ellipsis]; 
     NSRange range = NSMakeRange(truncatedString.length - (ellipsis.length + 1), 1); 
     while ([self numberOfLinesNeeded:truncatedString] > lines) { 
      [truncatedString deleteCharactersInRange:range]; 
      range.location--; 
     } 
     [truncatedString deleteCharactersInRange:range]; //need to delete one more to make it fit 
     CGRect labelFrame = self.frame; 
     labelFrame.size.height = [@"A" sizeWithFont:self.font].height * lines; 
     self.frame = labelFrame; 
     self.text = truncatedString; 
     self.userInteractionEnabled = YES; 
     UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(expand:)]; 
     [self addGestureRecognizer:tapper]; 
    }else{ 
     CGRect labelFrame = self.frame; 
     labelFrame.size.height = [@"A" sizeWithFont:self.font].height * lines; 
     self.frame = labelFrame; 
     self.text = txt; 
    } 
} 

#pragma Private Methods 

-(int)numberOfLinesNeeded:(NSString *) s { 
    float oneLineHeight = [@"A" sizeWithFont:self.font].height; 
    float totalHeight = [s sizeWithFont:self.font constrainedToSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping].height; 
    return nearbyint(totalHeight/oneLineHeight); 
} 

-(void)expand:(UITapGestureRecognizer *) tapper { 
    int linesNeeded = [self numberOfLinesNeeded:string]; 
    CGRect labelFrame = self.frame; 
    labelFrame.size.height = [@"A" sizeWithFont:self.font].height * linesNeeded; 
    self.frame = labelFrame; 
    self.text = string; 
} 
+0

Ciao rdelmar, perché kNumberOfLines è necessario? questo può essere ottenuto dal metodo numberOfLinesNeeded? Grazie. – LetBulletFlies

+0

@LetBulletFlies, sono cose diverse. numberOfLinesNeeded calcola il numero di righe necessario per evitare il troncamento della stringa. kNumberOfLines è il numero di righe che vuoi avere nella tua etichetta, che presumibilmente sarebbe un numero fisso. – rdelmar

+0

Il codice sembra fantastico, e ci provo subito. –

3

Ci sono diversi modi per fare questo, con il più elegante è quello di utilizzare esclusivamente CoreText dato che si ottiene il controllo completo su come visualizzare il testo.

Questa è un'opzione ibrida in cui utilizziamo CoreText per ricreare l'etichetta, determinare dove finisce e quindi tagliare la stringa di testo dell'etichetta nel punto corretto.

NSMutableAttributedString *atrStr = [[NSAttributedString alloc] initWithString:label.text]; 
NSNumber *kern = [NSNumber numberWithFloat:0]; 
NSRange full = NSMakeRange(0, [atrStr string].length); 
[atrStr addAttribute:(id)kCTKernAttributeName value:kern range:full]; 

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)atrStr); 

CGMutablePathRef path = CGPathCreateMutable(); 
CGPathAddRect(path, NULL, label.frame); 
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); 

CFArrayRef lines = CTFrameGetLines(frame); 
CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, label.numberOfLines-1); 
CFRange r = CTLineGetStringRange(line); 

Questo ti dà l'intervallo dell'ultima riga del testo dell'etichetta. Da lì, è banale tagliarlo e mettere i puntini di sospensione dove vuoi.

La prima parte crea una stringa attribuita con le proprietà necessarie per replicare il comportamento di UILabel (potrebbe non essere 100% ma dovrebbe essere abbastanza vicino). Quindi creiamo un frame e un frame e prendiamo tutte le linee del frame, da cui estraiamo l'intervallo dell'ultima linea prevista dell'etichetta.

Questo è chiaramente una specie di trucco e, come ho detto, se si desidera avere il controllo completo su come il testo appare, si sta meglio con un'implementazione di CoreText pura.

+0

Grazie. Sembra un po 'complesso ... ma non c'è altra via per provare questo ... altro poi lo faccio solo. Ci provo. –

+1

Il tuo codice non funziona. Il metodo addAttribute non è riconosciuto da 'NSAttributedString'. Ottieni l'errore 'Nessuna interfaccia @ visibile per 'NSAttributedString' dichiara il selettore 'addAttribute: value: range:'' –

+0

Ha dovuto usare 'NSMutableAttributedString', ovviamente. –

0

ResponsiveLabel è una sottoclasse di UILabel che permette di aggiungere token di troncamento personalizzato che risponde al tatto.

3

Poiché questo post è del 2013, volevo dare alla mia implementazione Swift la soluzione molto bella di @rdelmar.

Considerando stiamo usando una sottoclasse di UILabel:

private let kNumberOfLines = 2 
private let ellipsis = " MORE" 

private var originalString: String! // Store the original text in the init 

private func getTruncatingText() -> String { 
    var truncatedString = originalString.mutableCopy() as! String 

    if numberOfLinesNeeded(truncatedString) > kNumberOfLines { 
     truncatedString += ellipsis 

     var range = Range<String.Index>(
      start: truncatedString.endIndex.advancedBy(-(ellipsis.characters.count + 1)), 
      end: truncatedString.endIndex.advancedBy(-ellipsis.characters.count) 
     ) 

     while numberOfLinesNeeded(truncatedString) > kNumberOfLines { 
      truncatedString.removeRange(range) 

      range.startIndex = range.startIndex.advancedBy(-1) 
      range.endIndex = range.endIndex.advancedBy(-1) 
     } 
    } 

    return truncatedString 
} 

private func getHeightForString(str: String) -> CGFloat { 
    return str.boundingRectWithSize(
     CGSizeMake(self.bounds.size.width, CGFloat.max), 
     options: [.UsesLineFragmentOrigin, .UsesFontLeading], 
     attributes: [NSFontAttributeName: font], 
     context: nil).height 
} 

private func numberOfLinesNeeded(s: String) -> Int { 
    let oneLineHeight = "A".sizeWithAttributes([NSFontAttributeName: font]).height 
    let totalHeight = getHeightForString(s) 
    return Int(totalHeight/oneLineHeight) 
} 

func expend() { 
    var labelFrame = self.frame 
    labelFrame.size.height = getHeightForString(originalString) 
    self.frame = labelFrame 
    self.text = originalString 
} 

func collapse() { 
    let truncatedText = getTruncatingText() 
    var labelFrame = self.frame 
    labelFrame.size.height = getHeightForString(truncatedText) 
    self.frame = labelFrame 
    self.text = truncatedText 
} 

differenza della soluzione precedente, questo funziona anche per qualsiasi tipo di attributo di testo (come NSParagraphStyleAttributeName).

Sentitevi liberi di criticare e commentare. Grazie ancora a @rdelmar.

+0

Bello! Grazie per la condivisione. Mi piacerà di più usarlo in futuro, e poi testarlo pure. –