ビギグラマーのノート

自作PCやプログラミングについてのブログです。

【Swift 4】NSAttributedString ⇆ HTML の変換とUITextViewへの表示

 やりたいこと

 NSAttributedStringとUITextViewを使えばリッチなテキスト表現ができることはご存知かと思います。今回、なんらかのデータファイルからUITextViewへ表示し編集、それを保存する必要があったのでどうにかしようと思ったのです。

How to do

HTML → NSAttributedString

 let html = "<html>ほにゃらら〜</html>"

        

let encoded = html.data(using: String.Encoding.utf8)!

let attributedOptions : [NSAttributedString.DocumentReadingOptionKey : Any] = [

            .documentType : NSAttributedString.DocumentType.html,

]

let attributedTxt = try! NSAttributedString(data: encoded,

                                                              options: attributedOptions,

                                                              documentAttributes: nil)

textView.attributedText = attributedTxt

 tryを使っているので実装ではdo-catchが必須になります。

f:id:BegiGrammer:20180315033947p:plain

NSAttributedString → HTML

let attT = textView.attributedText!

let documentAttributes = [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.html]

let htmlData = try! attT.data(from: NSRange(location: 0, length: attT.length),

                                          documentAttributes: documentAttributes)

let html = String(data: htmlData, encoding: .utf8)

print(html ?? "Empty")

結果

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<meta http-equiv="Content-Style-Type" content="text/css">

<title></title>

<meta name="Generator" content="Cocoa HTML Writer">

<style type="text/css">

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px '.SF UI Display'; color: #e60012}

p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px '.SF UI Text'}

p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px '.SF UI Text'; color: #009944}

span.s1 {font-family: '.SFUIDisplay'; font-weight: normal; font-style: normal; font-size: 25.00pt}

span.s2 {font-family: '.SFUIText'; font-weight: normal; font-style: normal; font-size: 12.00pt}

span.s3 {font-family: '.SFUIText'; font-weight: normal; font-style: normal; font-size: 10.00pt}

</style>

</head>

<body>

<p class="p1"><span class="s1">Tomato</span></p>

<p class="p2"><span class="s2">English</span></p>

<p class="p3"><span class="s3">Royal blueberry blue and white.<span class="Apple-converted-space"> </span></span></p>

</body>

</html>

ご丁寧に全文描いてくれいています。

愚痴

 File → NSAttributedString → UITextView(編集) → なんらかのData → File

 こういうことをやりたいが為にこの一週間XMLでフォントスタイルを定義しそれを解読する奴を必死に作っていましたが、ひょんなことでこのような楽な方法を見つけてしまいました。わだじの いっじゅうかん かえじで!

参考

stackoverflow.com

stackoverflow.com

追記 2018/05/13

 上記のコードは変換する度にフォントサイズが大きくなって行きます。原因はスタイルシートのフォントサイズを指定する単位のptからpxへの変換の際にズレが生じているためです。なのでHTMLを出力する際にptをpxへreplaceする正規表現のパッチをつけて見ました。

 追記のためコードは下に移動 

 上記の出力されたHTMLを見てもらうとわかるのですがpタグのスタイルとspanタグのスタイルのフォントサイズの指定は数字こそ同じですが単位がpxとptになっています。このせいでNSAttributedString ⇄ HTMLと変換する度にサイズが大きくなっていってしまうのです。なので上記のコードではNSAttributedStringからHTMLへ出力された際にptをpxに単純に置き換えています。同様にHTMLからの変換の際にもスタイルシートのptをpxに置き換えています。なので外部からHTMLを持ってきてこれで変換した場合pt指定の箇所が崩れます。しかし、私の用途では外部から誰かの書いたHTMLを持ってくることはないのでpt-pxの数値計算を伴う変換は行なっていません。完全にApp内だけでHTMLを回すならばHTMLの出力の際に直しておくだけで良いのですが。

 あとUNIX系はHTMLのpt-pxの倍率が1になっていると聞いたのですが、iOSはどうやら違うみたいですね。

参考 -2

Swiftの正規表現はそんなに使わなくて毎回忘れてしまう orz

qiita.com

追記 2018/06/12

 改行のみの行の変換で<p>タグと<br>が同時使用され行が2倍になるのを発見したのでそれの修正コードをあげて置きます。見た感じではAttributedString -> HTMLの際のオプション NSAttributedString.DocumentAttributeKey には改行の際の不具合を治すためのものなどはなさそうなので、思い切ってCSSでbrタグを無効化しました。私はHTMLに明るくないのですがbrタグは最近嫌われているそうなので。

簡単な修正方法などがありましたら教えてください。お願いします。

 再々追記のためコードは下記

 これだけNSAttributedString ⇄ String の不具合?があるのはどうなんですかね?もしかしていい感じにHTMLを編集、描写できるTextViewライクなLibraryがあったりするのでしょうか。

追記 2018/08/10

 日本語を入力してHTMLに変換後、AttributedStringに再度変換するとLineHeightがおかしなことになっている不具合の修正をしてみました。

f:id:BegiGrammer:20180810153030p:plain

もともと入力した際はこのような間隔ですが、

AttributedString -> HTML -> AttributedString の操作をするとこうなります。

f:id:BegiGrammer:20180810153224p:plain

これは日本語を含むHTMLをAttributedStringに変換した際にLineHeightのMaximun = 0 Minimun = 23 になってしまう事が原因の一つです。これをこのようにするFixです。よくみてみるとOriginとは若干間隔が異なりますが、こっちの方が見やすいのでここで止めておきます。いくら調べても改善策が見付からなかったので放置した訳では断じてありません。()

f:id:BegiGrammer:20180810153400j:plain