#iosdc 2016 A-1 Handling rich text in Swift

twitter.com

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.

iosdc.jp

speakerdeck.com

Handling rich text in Swift

f:id:niwatako:20160820102049j:plain

Objective-Cでも使える話です。iOSにおけるテキストレイアウトについて。

f:id:niwatako:20160820102104j:plain

これは1分で作ったアプリです

f:id:niwatako:20160820102110j:plain

違和感を感じるところが幾つもあると思います。デザイナーさんが発狂するのではないでしょうか。

ずれていたり

f:id:niwatako:20160820102142j:plain

切れていたり

f:id:niwatako:20160820102147j:plain

行間が詰まっていたり

f:id:niwatako:20160820102154j:plain

何も考えずに作るとこのようになってしまいます。

デザイナの方がイラストレータで作ったものを実装するときに、iOSのラベルだとこれは無理だと言ってしまうようなことがよくあると思います。

ここではiOSのテキストがどのように描画されてレイアウトされているかを解説します。

謎のマージンやうまくいかない行間、真ん中に合わせたはずなのに寄ってしまう罠などを実例で示しながら解決していきます。

f:id:niwatako:20160820102333j:plain

タイポグラフィの基礎を理解するとデザイナーさんと話が通じるようになる、そうなるとアプリ開発のチームがひとつ上のレベルに上がると思います。

iOSの7からはテキストのレイアウトの仕組みが変わってTextKitが導入されました。知らず知らずのうちに使っている、ふつうにやるとTextKitで描画されるので、TextKitについて理解できるように、基礎と少しだけ応用に触れていきたいと思います。

f:id:niwatako:20160820102519j:plain

TextKitが導入されました。

f:id:niwatako:20160820102523j:plain

フレームワークでは無いです。iOS6の仕組みと区別してTextKitと呼ばれています。

f:id:niwatako:20160820102600j:plain

TextKitを使わないものとしてはWebViewやWebKitがある。今日はそちらには触れません。

大事なのはCoteTextベースで、UIKitと統合されているということ。そのため、CoreTextレイヤーのことを直接触る必要はほとんど無い。独自ラベルを作ったりしないかぎり。

UILabelなどを普通に使えばTextKitを使っていることになります。

f:id:niwatako:20160820102721j:plain

f:id:niwatako:20160820102754j:plain

同じ文章を表示していて、上下で長さや高さが違います。これはフォントの指定が異なります。

f:id:niwatako:20160820102813j:plain

SystemFontを指定した場合欧文はサンフランシスコ、和文はヒラギノになっている。ヒラギノを指定した場合欧文の部分がHiraginoになる。

ここで問題が発生しています。

f:id:niwatako:20160820102938j:plain

複数行でも発生しています

f:id:niwatako:20160820102944j:plain

ベースラインより下にはみ出るアルファベットの小文字が切れてしまっています。

f:id:niwatako:20160820103006j:plain

純粋な日本語だけなら下のほうが好みだという方もおられるかもしれません。

f:id:niwatako:20160820103024j:plain

いずれにせよ何も考えずにテキストをセットしてSizeToFitすると、ヒラギノは中身のテキストによっては切れてしまうことが置きます。

どうしてこういうことが起きるのでしょうか

f:id:niwatako:20160820103112j:plain

行の高さはフォントが持っている高さの情報で決まる。

f:id:niwatako:20160820103120j:plain

日本語においてはこれは必ずしも適切ではないが、iOSのテキストレンダリングシステムはこれで動いている。iOSのテキストのドキュメントはすごいアップデートされていなくて、これはMacの資料です。

重要なところに印をつけました。

f:id:niwatako:20160820103223j:plain

これはちょっと間違っていてLineHeight はAscent+Descent。

ベースラインがアルファベットでは一番下になる。

これがFontファイルそれぞれに書いてあって、iOSがそれを元に行の高さを決定する。

f:id:niwatako:20160820103350j:plain

フォントの情報から線を引くとこのようになります。

SystemFontを使用しているとyまで収まっているがヒラギノではyが切れている。

f:id:niwatako:20160820103442j:plain

f:id:niwatako:20160820103446j:plain

iOSに付属しているヒラギノはAscentとDescentが狭い。iOSの標準日本語フォントファイルはこうなっています。游明朝体とかも。

参考に、Notoではちゃんと設定されている。

f:id:niwatako:20160820103546j:plain

バッドノウハウだが、困ったら上下にDescentを補います。上下に二倍にすると何故かぴったり収まるようになります。

f:id:niwatako:20160820103611j:plain

f:id:niwatako:20160820103651j:plain

f:id:niwatako:20160820103702j:plain

f:id:niwatako:20160820103709j:plain

基本的にはヒラギノを明示的には指定しないことをおすすめします。デザイン指示書にヒラギノXptとか書かれているが、本当にヒラギノを使いたいのか、確認したほうが良いと思います。欧文フォントでもヒラギノを利用したいのか、これはシステムフォントですよね?と確認するのはプログラマがやるべきことだと思います。

環境によって中国語フォントにしたくないからヒラギノを指定したいということはあるかもしれません。しかし、フォールバックのコントロールはかのうなので、そのような工夫をしたほうが良いと思います。

欧文もヒラギノで、と指定されてあ場合は先程のような罠に気をつける必要があります。

NSAttributedStringにフォールバック先のフォントを指定する方法

f:id:niwatako:20160820103858j:plain

サイズの計算

f:id:niwatako:20160820103923j:plain

f:id:niwatako:20160820103944j:plain

テキストがどの領域にどの高さで表示されるかを知ることが出来ます。

f:id:niwatako:20160820104028j:plain

普通に使うとこうなります

f:id:niwatako:20160820104035j:plain

f:id:niwatako:20160820104045j:plain

ヒラギノは随分高さが大きい。

f:id:niwatako:20160820104101j:plain

複数行になるともう少し面白い結果になる。

f:id:niwatako:20160820104109j:plain

f:id:niwatako:20160820104129j:plain

f:id:niwatako:20160820104135j:plain

上下に8ポイントのデフォルトマージンがある。左右にはデフォルトで5ポイントのマージンが設定されている。

ヒラギノを指定した場合

f:id:niwatako:20160820104209j:plain

leadingというプロパティの値が利用される。

描画をコントロールするには、デフォルトのスタイルをリセットしていきます。何処がどのように影響しているのでしょう。

f:id:niwatako:20160820104311j:plain

f:id:niwatako:20160820104313j:plain

f:id:niwatako:20160820104321j:plain

f:id:niwatako:20160820104331j:plain

f:id:niwatako:20160820104336j:plain

f:id:niwatako:20160820104342j:plain

leadingを取り除く

f:id:niwatako:20160820104401j:plain

f:id:niwatako:20160820104414j:plain

なぜデフォルトTrueか不明、UIで使うにはfalseが良いとヘッダーに書いてある。 行間が空きすぎるので基本的にはfalseが良いと思います。

f:id:niwatako:20160820104454j:plain

f:id:niwatako:20160820104500j:plain

f:id:niwatako:20160820104516j:plain

↑ただし一番下のleadingはboundingRectに含まれないようなので保管が必要

というわけでリセットに成功しました

f:id:niwatako:20160820104544j:plain

これをもとに

f:id:niwatako:20160820104609j:plain

f:id:niwatako:20160820104629j:plain

f:id:niwatako:20160820104638j:plain

f:id:niwatako:20160820104648j:plain

行間に関わる部分だけ

f:id:niwatako:20160820104701j:plain

f:id:niwatako:20160820104728j:plain

なぜLineHeightとSpacingが別々にあるのか、それは複数のフォントが混在する場合の最大値や最小値のためにある。

f:id:niwatako:20160820104836j:plain

f:id:niwatako:20160820104842j:plain

f:id:niwatako:20160820104857j:plain

これは一つのStringで構成されています

f:id:niwatako:20160820104905j:plain

上と箇条書き部分を別々に設定するのも有りですが、全部一括で描画させることも出来ます。

こういったこともStringとAttributedStringだけで可能になる。

f:id:niwatako:20160820105004j:plain

f:id:niwatako:20160820105036j:plain

Q&A

ヒラギノと指定されて実はSystemFontでよかったということはある。でもSystemFontにすると同じフォントサイズでヒラギノの日本語が小さくなる、SystemFontを使いたいが日本語は大きくしたい時どのように対処したら良いか

不可能ではないが難しい。基本的にはSystemFontで指定した場合の大きさを基準にデザイナーの方と作っていくのが良い。それが出来ない場合はAttributedStringでアルファベットと日本語の部分で別途サイズを指定していくようなやり方になる。ただしそれはコストが大きすぎる、と思います。

デザイナーさんがおもったより小さいと言われるのは、PhotoshopIllustratorでみたものより小さく描画されるのに驚かれた。SystemFontに相当するものをデザイン用のアプリケーションで使えたら良いのですが、PhotoshopIllustrator側を合わせることは出来るのか

MacとSan Franciscoの組み合わせはおなじになるのではないかと思うが、San Franciscoと一緒に利用した時に小さくなるのは本来のAscentとDiscentに合わせるから小さくなるわけで、Macで利用するとおなじになるのではないかと思いますが、後で私も調べてみたいと思います。

感想

twitter.com

twitter.com

twitter.com

twitter.com

twitter.com

まとめ

twitter.com