16

問題

TextKit がどのように機能し、それを使用してテキスト エディターを作成する方法を理解する必要があります。エンドユーザーが操作する可視テキストのみを描画する方法を理解するか、processEditing メソッドで変更されたテキスト範囲全体に属性を適用せずに、可視テキストのみに属性を遅延適用する方法を決定する必要があります。

バックグラウンド

iOS 7 は TextKit とともに登場しました。私は、TextKit を完全に実装するトークナイザーとコードを持っています (Apple の TextKitDemo プロジェクトを参照してください。リンクは以下に示されています)...そして動作します。しかし、それは本当に遅いです。NSTextStorage によってテキストが解析されると、同じスレッドの processEditing メソッドで、編集されたテキストの範囲全体を色付けする必要があります。スレッドへの作業のオフロードは役に立ちません。それは単に遅すぎる。変更された範囲のみを再帰属できるようになりましたが、範囲が大きすぎるとプロセスが遅くなります。状況によっては、変更が行われた後にドキュメント全体が無効になる可能性があります。

ここに私が持っているいくつかのアイデアがあります。これらのいずれかが機能するかどうか、または正しい方向に私を微調整するかどうかを教えてください.

1) 複数の NSTextContainer

ドキュメントを読むと、NSLayoutManager 内に複数の NSTextContainers を追加できるようです。これを行うことで、NSTextContainer に描画できる行数を定義できるだけでなく、どの NSTextContainer がエンドユーザーに表示されるかを知ることもできるはずです。このルートに行く場合、それが実現可能かどうかを確認するためだけに多くの時間を投資する必要があることを私は知っています. 最初のテストでは、必要な NSTextContainer は 1 つだけであることが示唆されています。したがって、NSLayout をサブクラス化するか、レイアウト マネージャーがどのテキストをどのテキスト コンテナーに入れるかを決定するラッパーを作成する必要があります。うん。また、特定の NSTextContainer を描画する時が来たことを TextKit がどのように知らせてくれるのかわかりません。

2) NSLayoutManager を使用した範囲の無効化

invalidateLayoutForCharacterRange:actualCharacterRange: を使用して、layoutManager を無効にします。しかし、これは実際に何をし、どのようにテキスト帰属フェーズをオフロードするのでしょうか? 特定のテキストを強調表示する必要があることはいつ通知されますか? また、NSLayoutManager が遅延してグリフを描画することがわかります...どのように? いつ?これはどのように役立ちますか? 実際にテキストをレイアウトする前にバッキング文字列に属性を付けることができるように、この呼び出しを利用するにはどうすればよいですか?

3) NSLayoutManager drawGlyphsForGlyphRange:atPoint: メソッドをオーバーライドします。

私は本当にこれをしたくありません。そうは言っても、Mac OS X では、NSAttributedStrings には、スタイル情報が表示のみに使用される一時的な属性という概念があります。これにより、強調表示のプロセスが大幅に高速化されます。問題は、iOS 7 TextKit フレームワークに存在しないことです (または、そこにあり、私はそれについて知りません)。このメソッドをオーバーライドすることで、一時的な属性を使用して得られるのと同じタイプの速度が得られると信じています... NSTextStorage 属性に触れることなく、このメソッドですべてのレイアウト、色、および書式設定の質問に答えることができるためです。ストリング。唯一の問題は、このメソッドが NSLayoutManager クラスで提供されている他のメソッドに関連してどのように機能するかわかりません。幅と高さの状態を保持していますか?小さすぎる場合、NSTextContainer のサイズを変更しますか? また、テキスト バッファーに追加された文字のグリフのみを描画します。画面全体を再描画しません。ごく一部にすぎません...それで問題ありません。これをどのように扱うかについていくつかのアイデアがあります...しかし、グリフをレイアウトしたいという気持ちは本当にありません。これは大変な作業であり、これを行う良い例は見つかりませんでした。

私はあなたが提供しなければならない助けを大いに感謝します.

感謝の気持ちを込めて、ここ数年で使用したすべてのフレームワークとリファレンスをリストします。それらがあなたの役に立てば幸いです。

構文強調表示フレームワーク:

資力:

これらのフレームワークのほとんどは同じです。範囲のコンテキスト切り替えを考慮しない (またはコンテキストを提供するためにラッパーを作成する必要がある) か、ユーザーがテキスト (文字列、複数行のコメントなど) を変更してもコンテキスト範囲を修復しません。最後の要件は非常に重要です。トークナイザーが変更の影響を受ける範囲を特定できない場合、文字列全体を再度解析して属性を付与する必要があるためです。これに対する唯一の例外は、Crimson Editor です。このトークナイザーの問題は、トークン化時に状態を保存しないことです。描画時に、アルゴリズムはトークンを使用して描画の状態を判断します。ドキュメントの先頭から始まり、テキストの表示範囲に到達するまで。言うまでもなく、

もう 1 つの問題は、フレームワークが Apple と同じ MVC パターンに従っていないことです。これは当然のことです。完全に機能するエディターを持つフレームワークはすべて、フレームワークが構築されている API (つまり、GTK、Windows など) によって提供されるフックを使用します。これにより、画面のどこにいつ描画するかに関する情報が提供されます。私の場合、TextKit では、変更された範囲全体を processEditing に帰属させる必要があるようです。

多分私の観察は間違っています。(そうであることを願っています!!)おそらく、たとえばParseKitは、私が必要としている目的で機能しますが、その使用方法がわかりません。もしそうなら、私に知らせてください!そして、ありがとう!

4

1 に答える 1

9

私はそれを考え出した。上記の提案はどれも使用しませんでした。そうは言っても、私が今得ているパフォーマンスは単に信じられないほどです. YMMVに注意してください。文字列に関するメタデータをトークン化してキャッシュする方法は、私とは異なる場合があります。しかし、1400 行の PHP ファイルを入力することができ、1 つの変更を完了するのに 0.015 秒しかかかりませんでした。単に信じられないほどです。

これが私が取ったアプローチです:

私の UIViewController は、UITextViewDelegate と UIScrollViewDelegate へのデリゲートです。

UITextViewDelegate.textViewDidChange: が呼び出されると、現在エンドユーザーに表示されているテキストの範囲を決定します。これを行うには、既存のサブクラス化された UITextView を使用し、このメソッドを追加します。

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

その後、範囲をサブクラス化された NSTextStorage オブジェクトに渡します。そこで、強調表示する必要がある行を決定する魔法が実行されます。

同じことが UIScollViewDelegate メソッドの呼び出しにも当てはまります。ビューのどの部分が表示されているかに応じて、可視範囲をサブクラス化された NSTextStorage 呼び出しに渡し、行が既に属性付けされているかどうかなどを判断します。

多くのことを読者に委ねていることに気づきました。私は現在持っているものを使用することになり、上記の実装で動作するように少し調整しました。

これを実装しているときに興味深い発見をいくつか共有したいと思いました。

1) カーソルが置かれている現在の行より上の任意のテキストを強調表示しようとすると、カーソルがビュー内で「ジャンプ」し、元の位置に戻ることがあります。これは NSTextStorage.processEditing メソッドの呼び出しが原因であるとほぼ確信しています。システムが変更された行のみを強調表示する場所に到達できたので、この問題はなくなりました。

2)もともと、カーソルが飛び回るのを防ぐためにこれを行いました:

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

それはうまくいきました...しかし、[textView setScrollEnabled:NO]呼び出しはパフォーマンスに大きな打撃を与えました。そのコマンドだけで 1400 行のファイルを完了するのに約 3/4 秒かかりました。何が遅いのかはわかりませんが、言及する価値があると思いました。

于 2013-11-13T05:52:48.890 に答える