2013-11-01 10 views
15

ProblemaCreazione di un editor di testo per iOS 7

ho bisogno di capire come funziona TextKit e come posso usarlo per costruire un editor di testo. Ho bisogno di capire come disegnare SOLO il testo visibile che l'utente finale interagisce o determinare come farei per applicare pigramente gli attributi al testo visibile solo senza applicare attributi all'intero intervallo di testo modificato nel metodo processEditing.

Sfondo

iOS 7 è uscito con TextKit. Ho un tokenizer e codice che implementa completamente TextKit (fai riferimento al progetto TextKitDemo di Apple - un collegamento è fornito di seguito) ... e funziona. Tuttavia, è VERAMENTE lento. Poiché il testo viene analizzato da NSTextStorage, è necessario colorare TUTTA la gamma del testo modificato, nel metodo processEditing, sullo stesso thread. Offload del lavoro su una discussione non aiuta. È semplicemente troppo lento. Sono arrivato al punto in cui posso re-attribuire solo gli intervalli modificati, ma se l'intervallo è troppo grande il processo sarà lento. In alcune condizioni l'intero documento potrebbe essere invalidato dopo che è stata apportata una modifica.

Ecco alcune idee che ho. Per favore fatemi sapere se qualcuno di questi funzionerà o forse mi spingerà nella giusta direzione.

1) NSTextContainers multipli

Leggendo la documentazione risulta che posso aggiungere più NSTextContainers all'interno di un NSLayoutManager. Presumo che in questo modo dovrei essere in grado di definire non solo il numero di righe che possono essere disegnate in NSTextContainer, ma dovrei anche essere in grado di sapere quale NSTextContainer è visibile all'utente finale. So che se seguirò questa strada avrò bisogno di investire molto tempo solo per vedere se è fattibile. I test iniziali suggeriscono che è necessario un solo NSTextContainer. Quindi dovrei sottoclasse NSLayout o creare un wrapper in cui il gestore del layout determini quale testo debba entrare in quale contenitore di testo. Che schifo. Inoltre, non ho idea di come TextKit mi faccia sapere che è ora di disegnare un particolare NSTextContainer ... forse non è così che funziona!

2) invalidando Intervalli w/NSLayoutManager

Annullamento della LayoutManager utilizzando l'invalidateLayoutForCharacterRange: actualCharacterRange :. Ma cosa fa effettivamente questo e come scaricherà la fase di attribuzione del testo? Quando mi fa sapere che un particolare testo deve essere evidenziato? Inoltre, vedo che NSLayoutManager disegnerà pigramente glifi ... come? quando? Come mi aiuta? Come posso sfruttare questa chiamata in modo da poter attribuire la stringa di accompagnamento prima che il testo venga effettivamente pubblicato?

3) Over-riding del NSLayoutManager drawGlyphsForGlyphRange: atPoint: metodo.

Davvero non voglio farlo. Detto questo, in Mac OS X, NSAttributedStrings ha questo concetto di attributi temporanei in cui le informazioni di stile vengono utilizzate solo per la presentazione. Questo accelera il processo di evidenziazione GRANDE! Il problema è che non esiste nel framework TextKit di iOS 7 (o è lì e io non ne so nulla). Credo che superando questo metodo mi darà lo stesso tipo di velocità che otterresti usando gli attributi temporanei ... potrei rispondere a tutte le domande di layout, colore e formattazione in questo metodo senza mai toccare il NSTextStorage attribuito stringa. L'unico problema è che non so come questo metodo funzioni in relazione agli altri metodi forniti nella classe NSLayoutManager. Mantiene lo stato della larghezza e dell'altezza? Modifica la dimensione del NSTextContainer quando è troppo piccolo? Inoltre, disegna solo glifi per i caratteri che sono stati aggiunti nel buffer di testo. Non rielabora l'intero schermo. solo una piccola parte di esso ... e va benissimo.Ho alcune idee su come potrei lavorare con questo ... ma non ho alcun desiderio di impaginare i glifi. Questo è MODO troppo lavoro e non ho trovato un buon esempio che faccia questo.

Apprezzerei molto l'aiuto che hai da offrire.

Come ringraziamento, sto elenca tutti i quadri e dei riferimenti che ho usato nel corso degli ultimi anni, che mi hanno aiutato ad arrivare dove sono ora, nella speranza che essi sono utili a voi.

Sintassi quadri che evidenziano:

Risorse:

La maggior parte di queste strutture è la stessa. Non tengono conto del cambio di contesto (o del wrapper per fornire il contesto) per gli intervalli o non riparano gli intervalli di contesto mentre un utente modifica il testo (come stringhe, commenti su più righe, ecc.). L'ultimo requisito è MOLTO importante. Perché se un tokenizzatore non è in grado di determinare quali intervalli sono interessati da una modifica, si finirà per dover analizzare e attribuire nuovamente l'intera stringa. L'unica eccezione a questo è il Crimson Editor. Il problema con questo tokenizer è che non salva stato al momento della tokenizzazione. Al momento del disegno, l'algoritmo utilizza i token per determinare lo stato del disegno. Inizia dalla parte superiore del documento, fino a raggiungere l'intervallo di testo visibile. Inutile dire che l'ho ottimizzato mettendo in cache lo stato del documento in alcune parti del documento.

L'altro problema è che i framework non seguono lo stesso pattern MVC di Apple, il che è normale. I framework che hanno un editor di lavoro completo usano tutti gli hook, forniti dall'API su cui sono costruiti (es. GTK, Windows, ecc.), Che fornisce loro informazioni su dove e quando disegnare su quale parte dello schermo. Nel mio caso, TextKit sembra richiedere l'attribuzione dell'intero intervallo modificato in processEditing.

Forse le mie osservazioni sono errate. (Spero che lo siano !!) Forse, ParseKit per esempio, funzionerà per quello che mi serve e semplicemente non capisco come usarlo. Se è così, per favore fatemelo sapere! E grazie ancora!

+0

Se qualcuno tenta di implementare i suggerimenti di cui sopra mi farà sapere come è andata? Mi piacerebbe sentire le tue scoperte! Grazie! – PeqNP

+0

Ho dimenticato di aggiungere una cosa. Ho anche provato a sovrascrivere gli attributi NSLayoutManagerAtIndex: effectiveRange :. Presumo che questo restituisca un NSDictionary con coppie chiave/valore costituite da NSForegroundColorAttributeName/UIColor, ecc. Tuttavia, ogni volta che tento di restituire un dizionario _basic_ con questa coppia chiave/valore, il programma si blocca ... ma, questo potrebbe essere il luogo in cui potrei, potenzialmente, fornire pigramente gli attributi ... questo presuppone che gli attributi vengano interrogati pigramente. Posso solo supporre che avrei bisogno di invalidare l'intervallo di testo che è cambiato in modo che anche questo funzioni. – PeqNP

+0

Penso che potrei averlo; se riesco a determinare l'intervallo di testo visibile all'utente, potrei applicare SOLO attributi a quell'intervallo in processEditing e contrassegnare gli intervalli invalidati, prima e dopo il testo visibile, con un attributo personalizzato (o un elenco di NSRanges, che mai). Quindi scaricherò il lavoro rimanente, solo per il testo invalidato, in un processo separato. – PeqNP

risposta

9

L'ho capito. Non ho usato nessuno dei suggerimenti sopra. Detto questo, la performance che sto ottenendo ora è semplicemente incredibile. Tieni presente che YMMV. Il modo in cui si esegue il tokenize e si memorizzano nella cache i metadati sulla stringa potrebbero essere diversi da me. Tuttavia, sono stato in grado di digitare un file PHP da 1400 linee e ci sono voluti solo 0,015 secondi per completare una modifica. Semplicemente incredibile.

Ecco l'approccio che ho preso:

mio UIViewController è un delegato per UITextViewDelegate e UIScrollViewDelegate.

Quando UITextViewDelegate.textViewDidChange: viene chiamato, determina quale intervallo di testo è attualmente visibile all'utente finale. Ho fatto questo utilizzando il mio UITextView sottoclasse esistente e l'aggiunta di questo metodo ad esso:

- (NSRange)visibleRangeOfText 
{ 
    CGRect bounds = self.bounds; 
    UITextPosition *start = [self characterRangeAtPoint:bounds.origin].start; 
    UITextPosition *end = [self characterRangeAtPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))].end; 
    return NSMakeRange([self offsetFromPosition:self.beginningOfDocument toPosition:start], 
        [self offsetFromPosition:start toPosition:end]); 
} 

Dopo di che, passo la gamma a un oggetto NSTextStorage sottoclasse, dove sarà quindi eseguire la magia per determinare quali linee hanno bisogno essere evidenziato.

Lo stesso vale per le chiamate al metodo UIScollViewDelegate. A seconda di quale parte della vista viene visualizzata, passerò nell'intervallo visibile alla mia chiamata NSTextStorage sottoclasse e determina se le linee sono già state attribuite, ecc.

Mi rendo conto che sto lasciando molto al lettore . Ho finito per utilizzare ciò che avevo attualmente e l'ho ottimizzato un po 'per lavorare con l'implementazione di cui sopra.

ho voluto condividere alcune delle mie scoperte che ho trovato interessante, mentre l'attuazione del presente:

1) Se si tenta di evidenziare qualsiasi testo sopra la linea attuale, in cui il cursore si riposa, si può vedere che il cursore "salta" all'interno della vista, quindi torna alla posizione in cui era originariamente. Sono quasi sicuro che ciò sia causato dalla chiamata al metodo NSTextStorage.processEditing. Sono stato in grado di arrivare a dove il sistema evidenzia solo la linea che è stata modificata ... quindi questo problema è andato ora.

2) In origine ho fatto questo per evitare che il cursore da saltare:

NSRange selectedRange = [textView selectedTextRange]; 
[textView setScrollEnabled:NO]; 
NSRange visibleRange = [textView visibleRangeOfText]; 
[textStorage applyAttributesToRange:visibleRange]; 
[textView setScrollEnabled:YES]; 

ha funzionato ... ma la [textView setScrollEnabled: NO] chiamata ha fatto un enorme successo per le prestazioni. Ci sono voluti quasi 3/4 di secondo per quel comando da solo per finire su un file di 1400 linee. Non sono sicuro di quale sia la causa del rallentamento, ma ho pensato che fosse degno di nota.

+0

+1 per la condivisione :) –

+0

! Avviso di avviso senza vergogna! Se qualcuno è interessato, ho creato un framework che fornisce l'evidenziazione della sintassi e un editor quasi completo che puoi facilmente integrare nella tua app iOS chiamata SchemeKit. Puoi acquistarlo su http://www.binpress.com/app/schemekit/1981?ad=31477 – PeqNP

+0

Sto implementando una simile textview di evidenziazione della sintassi e mi chiedevo se potessi aiutarmi. In questo momento tutto funziona perfettamente, ad eccezione del layout manager che impiega molto tempo per essere caricato. Cosa hai fatto per impedirlo? Come hai determinato ciò che era già stato attribuito? Grazie! Inoltre, la tua biblioteca è ancora disponibile? – Jacob