HTMLは文章の装飾やリンクなどの設定がとても手軽にできます。
その恩恵を受けつつ、iOSでも似たような挙動をさせることができます。
UITextView *textView = [[UITextView alloc] init];
// ベースとなるHTMLテキスト
NSString *html = @"これは<a href=\"http://css-eblog.com/\"><strong>リンク</strong></a>の<strong>テスト</strong>です。これは<a href=\"http://css-eblog.com/\"><strong>リンク</strong></a>の<strong>テスト</strong>です。これは<a href=\"http://css-eblog.com/\"><strong>リンク</strong></a>の<strong>テスト</strong>です。これは<a href=\"http://css-eblog.com/\"><strong>リンク</strong></a>の<strong>テスト</strong>です。これは<a href=\"http://css-eblog.com/\"><strong>リンク</strong></a>の<strong>テスト</strong>です。";
// Attributeのオプション
NSDictionary *options = @{ NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding) };
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding]
options:options
documentAttributes:nil
error:nil];
CGFloat baseLineHeight = 19.8;
NSMutableParagraphStyle *para = [[NSMutableParagraphStyle alloc] init];
para.minimumLineHeight = baseLineHeight;
para.maximumLineHeight = baseLineHeight;
[text addAttribute:NSParagraphStyleAttributeName
value:para
range:NSMakeRange(0, text.length)];
textView.editable = NO;
textView.attributedText = text;
CGFloat width = self.view.bounds.size.width - 20;
CGSize rangeSize = CGSizeMake(width, MAXFLOAT);
CGRect boundingRect = [text boundingRectWithSize:rangeSize
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
boundingRect.size.width = width;
boundingRect.size.height += baseLineHeight; // line heightなどの余白を考慮して調整
boundingRect.origin.x = 10;
boundingRect.origin.y = 100;
textView.frame = boundingRect;
textView.backgroundColor = UIColor.redColor;
[self.view addSubview:textView];
普通にHTMLを解析して、それぞれ適切に変換してくれます。(リンクも!)
さらにはstyle
属性でのスタイリングもできるので重宝しそうです。
※ ただ、本機能はiOS7以降のものです。
[追記]
NSForegroundColorAttributeName
を変更してもリンクの色が変更できなかったんですが、どうやらUITextView
にlinkTextAttributes
というプロパティを通して設定するようです。
aTextView.linkTextAttributes = @{NSForegroundColorAttributeName:UIColor.redColor};
リンクテキスト部分以外のタップイベントを透過的にさせる
上記のリンク機能などを使うと、a
タグを使ってリンクを設定することができました。
が、リンクを有効にするとリンクされていないテキストもタップを検知するようになり、結果としてUITableViweCellなどに置いた際に、セルが反応しない、という問題が出てしまいます。
それを回避する方法としてUITextViewのhitTest:withEvent:
メソッドを使ってタップの検出をスキップするかをハンドリングすることが出来ます。
具体的には以下のようにします。
// リンクテキスト以外のタッチイベントを透過させる
- (UIView *)hitTest:(CGPoint)point
withEvent:(UIEvent *)event
{
// Inset分位置を補正
point.x -= self.textContainerInset.left;
point.y -= self.textContainerInset.top;
// タッチ位置から該当テキストのindexを得る
NSInteger index = [self.layoutManager characterIndexForPoint:point inTextContainer:self.textContainer fractionOfDistanceBetweenInsertionPoints:NULL];
// タッチ位置にある文字と同種の属性が含まれる文字列の範囲(HTMLのタグの影響範囲のような感じ?)のポインタ
NSRange effectiveRange = NSMakeRange(0, 0);
// タッチ位置の文字インデックスの属性と、その属性範囲を取得
NSDictionary *attributes = [self.textStorage attributesAtIndex:index effectiveRange:&effectiveRange];
// 属性がリンクテキストでなかった場合はスルー(イベントを透過するには`nil`を返す)
if (!attributes[NSLinkAttributeName]) {
return nil;
}
__block BOOL isLink = NO;
// 文字列インデックスからグリフのインデックスを得る
NSInteger glyphIndex = [self.layoutManager glyphIndexForCharacterAtIndex:index];
// グリフのrangeから該当文字列の矩形を得る?(ちょっとここは分かりません)
[self.layoutManger enumerateLineFragmentsForGlyphRange:NSMakeRange(glyphIndex, 1) usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {
// タッチ位置がusedRectの中にある=リンクテキストをタップした
if (CGRectContainsPoint(usedRect, p)) {
isLink = YES;
*stop = YES;
}
}];
// リンクテキストをタップしていたら自身を、そうでない場合はnilを返す
return isLink ? self : nil;
}