Text Kitによるテキストレイアウト(前編)
これらのクラス関係の例を下図に示します(図16)。この例は、テキストストレージが持つテキストコンテンツを、2ページ(もしくは2カラム)に分割して表示する場合のクラス関連図です。そのため、レイアウトマネージャが2つ存在しています。
UITextViewのインスタンスを生成すると、そのUITextViewに関連付けされたNSTextContainer、NSLayoutManager、NSTextStorageのインスタンスが生成され、プロパティを通してアクセス可能となります。ただし、これらのプロパティはreadonlyであるため、生成後に変更することはできません。
テキストビューに自前のテキストコンテナを対応付けるには、以下のメソッドを利用してコードからUITextViewのインスタンスを生成します。
- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer;
レイアウトマネージャを利用すると、位置座標から表示されている文字を取得することが可能です。これをヒットテストと呼びます。例えば、タップ位置(タッチ終了位置)に表示されている文字は、下記のコードで取得できます。
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint locationInView = [touch locationInView:self.textView]; NSTextContainer *textContainer = self.textView.textContainer; NSLayoutManager *layoutManager = self.textView.layoutManager; // テキストコンテナの座標系に変換 CGPoint locationInContainer = CGPointMake(locationInView.x - self.textView.textContainerInset.left, locationInView.y - self.textView.textContainerInset.top); // 文字インデックスを取得 NSUInteger charIndex = [layoutManager characterIndexForPoint:locationInContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; // テキストストレージから該当文字を取得 NSString *charString = ; // 画像の表示位置 CGRect imageRect = CGRectMake(100, 100, imageView.image.size.width, imageView.image.size.height); imageView.frame = imageRect; [self.textView addSubview:imageView]; // 非表示領域の計算(画像の表示位置をテキストコンテナの座標に変換) CGRect exclusionRect = CGRectOffset(imageView.frame, -self.textView.textContainerInset.left, -self.textView.textContainerInset.top); // 非表示領域の設定 UIBezierPath *path = [UIBezierPath bezierPathWithRect:exclusionRect]; self.textView.textContainer.exclusionPaths = @[path];
上記の通り、非表示領域を設定してその位置に画像を表示すれば、テキストの回り込みが実現できます。exclusionRectとimageRectは異なることに注意してください。exclusionRectはNSTextContainerの座標系、imageRectはUITextViewの座標系で示されます。
NSTextContainerによる表示領域は、デフォルトでsizeプロパティにより示されるサイズを持った矩形領域となっています。これに対して、より複雑な領域を表示領域として設定するには、NSTextContainerをサブクラス化して以下のメソッドをオーバーライドします。
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(CGRect *)remainingRect;
テキストコンテナが示す表示領域の概要を示します(図17)。
lineFragmentRectForProposedRect...メソッドに渡されるproposedRectは、レイアウトマネージャーが計算した行の表示領域で、実際にテキストを表示すべき領域を返します。また、exclusionPathsによる非表示領域があると、非表示領域で分割された領域がremainingRectとして渡されます。そのため、テキストの分割位置もこのメソッドで調整します。
なお、指定したlineFragmentRectに対して、実際にテキストが表示される領域は、lineFragmentPaddingを除いた内側になります。lineFragmentPaddingはNSTextContainerのプロパティで、デフォルト値として0.5が設定されています。