The handling of rich text is not easy. We may consider a lot of things like fonts, characters, glyphs, emojis, images, ligatures, etc. In this talk, I will show you the basics of laying out text and how to handle complex text layouts in Apple's OS.
Handling rich text in Swift
Objective-Cでも使える話です。iOSにおけるテキストレイアウトについて。
これは1分で作ったアプリです
違和感を感じるところが幾つもあると思います。デザイナーさんが発狂するのではないでしょうか。
ずれていたり
切れていたり
行間が詰まっていたり
何も考えずに作るとこのようになってしまいます。
デザイナの方がイラストレータで作ったものを実装するときに、iOSのラベルだとこれは無理だと言ってしまうようなことがよくあると思います。
ここではiOSのテキストがどのように描画されてレイアウトされているかを解説します。
謎のマージンやうまくいかない行間、真ん中に合わせたはずなのに寄ってしまう罠などを実例で示しながら解決していきます。
タイポグラフィの基礎を理解するとデザイナーさんと話が通じるようになる、そうなるとアプリ開発のチームがひとつ上のレベルに上がると思います。
iOSの7からはテキストのレイアウトの仕組みが変わってTextKitが導入されました。知らず知らずのうちに使っている、ふつうにやるとTextKitで描画されるので、TextKitについて理解できるように、基礎と少しだけ応用に触れていきたいと思います。
TextKitが導入されました。
フレームワークでは無いです。iOS6の仕組みと区別してTextKitと呼ばれています。
TextKitを使わないものとしてはWebViewやWebKitがある。今日はそちらには触れません。
大事なのはCoteTextベースで、UIKitと統合されているということ。そのため、CoreTextレイヤーのことを直接触る必要はほとんど無い。独自ラベルを作ったりしないかぎり。
UILabelなどを普通に使えばTextKitを使っていることになります。
同じ文章を表示していて、上下で長さや高さが違います。これはフォントの指定が異なります。
SystemFontを指定した場合欧文はサンフランシスコ、和文はヒラギノになっている。ヒラギノを指定した場合欧文の部分がHiraginoになる。
ここで問題が発生しています。
複数行でも発生しています
ベースラインより下にはみ出るアルファベットの小文字が切れてしまっています。
純粋な日本語だけなら下のほうが好みだという方もおられるかもしれません。
いずれにせよ何も考えずにテキストをセットしてSizeToFitすると、ヒラギノは中身のテキストによっては切れてしまうことが置きます。
どうしてこういうことが起きるのでしょうか
行の高さはフォントが持っている高さの情報で決まる。
日本語においてはこれは必ずしも適切ではないが、iOSのテキストレンダリングシステムはこれで動いている。iOSのテキストのドキュメントはすごいアップデートされていなくて、これはMacの資料です。
重要なところに印をつけました。
これはちょっと間違っていてLineHeight はAscent+Descent。
ベースラインがアルファベットでは一番下になる。
これがFontファイルそれぞれに書いてあって、iOSがそれを元に行の高さを決定する。
フォントの情報から線を引くとこのようになります。
SystemFontを使用しているとyまで収まっているがヒラギノではyが切れている。
iOSに付属しているヒラギノはAscentとDescentが狭い。iOSの標準日本語フォントファイルはこうなっています。游明朝体とかも。
参考に、Notoではちゃんと設定されている。
バッドノウハウだが、困ったら上下にDescentを補います。上下に二倍にすると何故かぴったり収まるようになります。
基本的にはヒラギノを明示的には指定しないことをおすすめします。デザイン指示書にヒラギノXptとか書かれているが、本当にヒラギノを使いたいのか、確認したほうが良いと思います。欧文フォントでもヒラギノを利用したいのか、これはシステムフォントですよね?と確認するのはプログラマがやるべきことだと思います。
環境によって中国語フォントにしたくないからヒラギノを指定したいということはあるかもしれません。しかし、フォールバックのコントロールはかのうなので、そのような工夫をしたほうが良いと思います。
欧文もヒラギノで、と指定されてあ場合は先程のような罠に気をつける必要があります。
NSAttributedStringにフォールバック先のフォントを指定する方法
サイズの計算
テキストがどの領域にどの高さで表示されるかを知ることが出来ます。
普通に使うとこうなります
ヒラギノは随分高さが大きい。
複数行になるともう少し面白い結果になる。
上下に8ポイントのデフォルトマージンがある。左右にはデフォルトで5ポイントのマージンが設定されている。
ヒラギノを指定した場合
leadingというプロパティの値が利用される。
描画をコントロールするには、デフォルトのスタイルをリセットしていきます。何処がどのように影響しているのでしょう。
leadingを取り除く
なぜデフォルトTrueか不明、UIで使うにはfalseが良いとヘッダーに書いてある。 行間が空きすぎるので基本的にはfalseが良いと思います。
↑ただし一番下のleadingはboundingRectに含まれないようなので保管が必要
というわけでリセットに成功しました
これをもとに
行間に関わる部分だけ
なぜLineHeightとSpacingが別々にあるのか、それは複数のフォントが混在する場合の最大値や最小値のためにある。
これは一つのStringで構成されています
上と箇条書き部分を別々に設定するのも有りですが、全部一括で描画させることも出来ます。
こういったこともStringとAttributedStringだけで可能になる。
Q&A
ヒラギノと指定されて実はSystemFontでよかったということはある。でもSystemFontにすると同じフォントサイズでヒラギノの日本語が小さくなる、SystemFontを使いたいが日本語は大きくしたい時どのように対処したら良いか
不可能ではないが難しい。基本的にはSystemFontで指定した場合の大きさを基準にデザイナーの方と作っていくのが良い。それが出来ない場合はAttributedStringでアルファベットと日本語の部分で別途サイズを指定していくようなやり方になる。ただしそれはコストが大きすぎる、と思います。
デザイナーさんがおもったより小さいと言われるのは、PhotoshopやIllustratorでみたものより小さく描画されるのに驚かれた。SystemFontに相当するものをデザイン用のアプリケーションで使えたら良いのですが、PhotoshopやIllustrator側を合わせることは出来るのか
MacとSan Franciscoの組み合わせはおなじになるのではないかと思うが、San Franciscoと一緒に利用した時に小さくなるのは本来のAscentとDiscentに合わせるから小さくなるわけで、Macで利用するとおなじになるのではないかと思いますが、後で私も調べてみたいと思います。
感想
twitter.comiOSのテキストUIについて。あるあるあるある超共感。 #iosdc #a
— とってぃ (@tottytk) August 20, 2016
twitter.comTextKit、初めて聞いた。勉強になります! #iosdc #a
— とってぃ (@tottytk) August 20, 2016
— 労働者 (@naoty_k) August 20, 2016twitter.com
twitter.com#iosdc #a ヒラギノの闇、まさに丁度先日ぶち当たったところでした… 全部システムフォントに置き換えたけど、XcodeだとIBで指定されているヒラギノがうまく検索できなかった(単純な文字列検索ではない模様)ので、grep等で探す必要ありそうです。
— takasek (@takasek) August 20, 2016
twitter.comタイポグラフィの基礎を学ぶ。iOSでも重要なことなんですね。 #iosdc #a
— Codenberg.io (@Codenberg_io) August 20, 2016
まとめ
twitter.com「テキストのUIを最適にするには…? -Handling rich text in Swift- #iosdc #a」をトゥギャりました。 https://t.co/Ar3VBUlJeK
— トゥギャッター開発まとめ (@tg__dev) August 20, 2016