2014-12-16 10 views
6

Voglio calcolare i limiti minimi per un rettangolo necessario per adattare una stringa (linea multipla) con un carattere specifico.Stringa: Limiti minimi per l'altezza massima

Dovrebbe essere qualcosa di simile:

FROM: 

---------------------------------- 
|Sent when the application is about| 
|to move from active to inactive | 
|state.       | 
---------------------------------- 

TO: 

------------------------- 
|Sent when the application| 
|is about to move from | 
|active to inactive state.| 
------------------------- 

Come si può vedere, l'altezza rimane lo stesso, i cambiamenti di larghezza al valore minimo necessario.

Inizialmente avrei potuto usare boundingRectWithSize (vincolo alla larghezza massima) per ottenere prima l'altezza minima necessaria e quindi chiamare boundingRectWithSize (vincolo all'altezza calcolata) ottenere la larghezza. Ma questo produce risultati errati quando si calcola la larghezza nel secondo passaggio. Non considera l'altezza massima ma calcola semplicemente la larghezza per una singola stringa di linea.

Dopo che ho trovato un modo per ottenere il risultato corretto, ma l'esecuzione di questo codice impiega davvero tanto tempo che lo rende di alcuna utilità per me:

Prima calcolare il rettangolo necessario per la larghezza vincolo:

var objectFrame = Class.sizeOfString(string, font: objectFont, width: Double(width), height: DBL_MAX) 

la larghezza:

objectFrame.size.width = Class.minWidthForHeight(string, font: objectFont, objectFrame.size.height) 

utilizzando:

class func minWidthForHeight(string: NSString, font: UIFont, height: CGFloat) -> CGFloat 
{ 
    let deltaWidth: CGFloat = 5.0 
    let neededHeight: CGFloat = rect.size.height 
    var testingWidth: CGFloat = rect.size.width 

    var done = false 
    while (done == false) 
    { 
     testingWidth -= deltaWidth 

     var newSize = Class.sizeOfString(string, font: font, width: Double(testingWidth), height: DBL_MAX) 

     if (newSize.height > neededHeight) 
     { 
      testingWidth += deltaWidth 
      done = true 
     } 
    } 
    return testingWidth 
} 

class func sizeOfString(string: NSString, font: UIFont, width: Double, height: Double) -> CGRect 
{ 
    return string.boundingRectWithSize(CGSize(width: width, height: height), 
     options: NSStringDrawingOptions.UsesLineFragmentOrigin, 
     attributes: [NSFontAttributeName: font], 
     context: nil) 
} 

Calcola gradualmente l'altezza per una determinata larghezza (- 5,0 pixel ogni nuovo passo) e verifica se l'altezza rimane uguale. Non appena l'altezza cambia, restituisce la larghezza del passaggio precedente. Quindi ora abbiamo un rettangolo di delimitazione in cui la stringa per un certo carattere si adatta perfettamente senza alcuno spazio sprecato.

Ma come ho detto, ci vuole molto tempo per calcolare, specialmente quando lo si fa per molte stringhe diverse contemporaneamente.

C'è un modo migliore e più veloce per fare questo?

+1

Non so una soluzione su come calcolare questo con un passaggio, ma forse è possibile sostituire la ricerca lineare per la larghezza migliore con una ricerca binaria. Prendi due valori, con uno più piccolo troppo stretto e uno più largo sufficientemente largo. Controlla la larghezza a metà tra questi valori. Se è ancora buono, imposta il valore più grande sulla larghezza testata, se è troppo piccolo, regola il valore più piccolo. Ripetere. Non è O (1) ma O (log n) invece di O (n). A seconda del numero di tentativi che devi effettuare per ogni stringa, questo potrebbe già accelerare le cose. – Nero

+1

Ovviamente ciò richiede ipotesi abbastanza intelligenti (non perfette) per i valori iniziali, in particolare quello più piccolo, che dovrebbe essere troppo piccolo. Più lungo è il testo, più questo numero può essere scelto, penso. Forse il numero più piccolo può essere impostato su '(1 - 1/numberOfLinesWithMaxWidth) * maxWidth' inizialmente. – Nero

+0

Funziona piuttosto bene. Ci vuole ancora molto tempo per calcolare, ma quel tempo è stato significativamente ridotto. – freshking

risposta

-1

Quindi questo codice di seguito è il mio ultimo approccio alla domanda in questione. È abbastanza veloce e funziona bene per me. Grazie a @Nero per avermi trovato sulla strada giusta.

class func minFrameWidthForHeight(string: NSString, font: UIFont, rect: CGRect) -> CGFloat 
{ 
    if (string.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).count <= 1) 
    { 
     return rect.size.width 
    } 

    var minValue: CGFloat = rect.size.width/2 
    var maxValue: CGFloat = rect.size.width 

    var testingWidth: CGFloat = rect.size.width 
    var lastTestingWidth: CGFloat = testingWidth 
    let neededHeight: CGFloat = rect.size.height 

    var newSize = rect 

    while (newSize.height <= neededHeight) 
    { 
     lastTestingWidth = testingWidth 

     testingWidth = (maxValue + minValue)/2 

     newSize = CalculationHelper.sizeOfString(string, font: font, width: Double(testingWidth), height: DBL_MAX) 

     if (newSize.height <= neededHeight) 
     { 
      maxValue = testingWidth 
     } 
     else 
     { 
      minValue = testingWidth 
     } 
    } 

    return lastTestingWidth 
} 
+0

Questo in realtà non risolve il problema, poiché gli utenti non avranno la classe helper di calcolo. –

+0

Inoltre, l'algoritmo sottostante sarà sicuramente più veloce, oltre che più preciso. –

+0

Per favore guarda il mio post originale, c'è la classe a cui ti riferisci. È semplicemente una chiamata 'boundingRectWithSize'. Presto testerò anche il tuo codice. Fino ad allora, volevo solo condividere il mio approccio. – freshking