Text Kitによるテキストレイアウト(前編)
UIKit徹底解説 iOSユーザーインターフェイスの開発
iPhone/iPadアプリのUI開発テクニックを完全網羅!この記事は、書籍『UIKit徹底解説 iOSユーザーインターフェイスの開発』の内容を、Think IT向けに特別公開しているものです。
これまでに解説した内容は、フォントまたはテキストレベルでのアピアランス設定方法です。さらに高いレベルでテキストコンテンツのアピアランスを調整するには、Text Kitのメイン機能であるNSTextContainer(テキストコンテナ)、NSLayoutManager(レイアウトマネージャ)、そしてNSTextStorage(テキストストレージ)を利用します。
Text Kitの構造
Text Kitとは、UIKitの一部として構築されているテキスト表現のためのフレームワークを指します。Text KitはCore Text上に位置し、Core Textが持つ機能をUIKit内で簡単に利用できる構造となっています。
Text Kitが導入されたiOS 7では、UILabelやUITextField、UITextViewが、Text Kitを使用するように再構築されています。そのため、本章で既に解説してきた内容も実際には、Text Kitの一部を利用していることになります。
Text Kitの主機能は、NSTextContainer、NSLayoutManager、NSTextStorageという3つのクラスを利用したUITextViewへのテキスト表示です。
まず、これらの3クラスの役割を説明します。
NSTextContainer(テキストコンテナ)
NSTextContainerは、UITextView上に表示するテキストの座標と形状を定義します。
1つのUITextViewは、1つのテキストコンテナを持ちます。テキストコンテナは1ページを表現しているともいえるので、1ページに入らない長いテキストコンテンツは、複数のテキストコンテナに分割して管理することができます。
また、exclusionPathsを設定することで、テキストの非表示領域も指定できます。テキストの非表示領域に画像を貼り付ければ、いわゆるテキストの回り込み処理が可能です。
NSLayoutManager(レイアウトマネージャ)
NSLayoutManagerは、UITextView上にテキスト表示する際に、レイアウトとレンダリングに関する調整を行います。テキストを表示すべき座標と形状はテキストコンテナから、実際に表示するテキストコンテンツは、次に説明するテキストストレージから受け取ります。
1つのレイアウトマネージャは、1つまたは複数のテキストコンテナを持ち、1つのテキストストレージを持ちます。レイアウトマネージャが持つテキストコンテナの数は、テキストコンテンツの長さに依存します。テキストストレージが持つ全コンテンツを収容できるだけのテキストコンテナを管理する必要があります。
また、レイアウトに関する様々な情報を持っています。例えば、UITextView上の特定の座標に表示されている文字またはグリフのインデックスを取得することもできます。
NSTextStorage(テキストストレージ)
NSTextStorageはNSMutableAttributedStringのサブクラスです。テキスト表現としては、前項で解説しているNSAttributedStringと同じ機能を持っています。テキストの内容に変更があると、前述のレイアウトマネージャに通知します。レイアウトマネージャはこの通知を受けて、テキストを再レイアウトを行います。
なお、1つのテキストストレージは、1つまたは複数のレイアウトマネージャを持つことができます。テキストストレージが複数のレイアウトマネージャを持つことは、異なるレイアウトに同一コンテンツを表示できることを意味します。この際、一方のレイアウトでテキストを変更したら、もう一方のレイアウトには即座に反映されます。
また、NSTextStorageはクラスクラスタです。サブクラスを作成する際には独自のストレージが必要となるほか、以下に示すプリミティブメソッドをすべてオーバーライドする必要があります(ソースコード37、38)。なお、クラスクラスタに関する詳細は、Appleの「Objective-C プログラミング概念」などを参照してください。
- (NSString *)string; - (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRangel;
- (void)replaceCharactersInRange:(NSRange)aRange withString:(NSString *)aString; - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)aRange;