おいしい健康 開発者ブログ

株式会社おいしい健康で働くエンジニア・デザイナーが社内の様子をお伝えします。

Swift でドロップキャップな TextView を作る

こんにちは、関口@tanukiti1987 です。

今、おいしい健康では iOS アプリを開発しています。 そこで、ドロップキャップを使った表現が何箇所か登場する(予定)なのですが、 UITextView を純粋に使っただけでは、そういった表現が出来ずに、ちょっとした実装が必要になってきます。

今回は、そのちょっとした実装方法をご紹介していきます。

ドロップキャップ(drop-cap) とは

そもそも、ドロップキャップがなんなんだ。という話があると思います。 僕自身も実装を担当することになり、この言葉を知りました。

ドロップキャップは、こういうやつです。

f:id:oishi-kenko:20171115223923p:plain

1文字目が大きく表示され、以降1文字目を回り込むように文章が展開されていく例のやつです。

css の世界では

p:first-letter{
    font-size: 2em;
}

という指定をしてあげることで、このドロップキャップの表現が可能です。 いつもは嫌いだけど、この時ばかりは最高じゃないか、CSS

Swift ではどうやるのか

ここまで紹介しましたが、さきにも述べたように swift では :first-letter みたいなベンリ attribute はないので、自前でがんばっていくことになります。

使っていくのは、 UITextView UIImageView の2つです。

  • 一文字目を UIImageView としてコードで生成する
  • TextKit の exclusion path を指定することで 作った UIImageView の周りに文字を回り込ませる

この2点を行うことで、ドロップキャップを実現していきます。

文字を UIImageView として生成する

文字を画像として生成するには UIGraphics を使って、描画していきます。

今回は、以下のような関数を作ってみました。

@IBOutlet weak var dropCapImageView: UIImageView!

func createDropCapImage(text: String) -> UIImage {
    let size = CGSize(width: dropCapImageView.frame.size.width, height: dropCapImageView.frame.size.height)
    UIGraphicsBeginImageContext(size)

    let fontSize: UIFont = UIFont.systemFont(ofSize: 42.0)

    let style: NSMutableParagraphStyle = NSMutableParagraphStyle()
    style.alignment = NSTextAlignment.center
    style.lineBreakMode = NSLineBreakMode.byClipping
    style.minimumLineHeight = 30

    let attributes = [
        NSAttributedStringKey.font: fontSize,
        NSAttributedStringKey.paragraphStyle: style,
        NSAttributedStringKey.foregroundColor: UIColor.black,
        NSAttributedStringKey.backgroundColor: UIColor.clear
    ]

    text.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height), withAttributes: attributes)
    let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() as UIImage!
    UIGraphicsEndImageContext()

    return image
}

font 情報を諸々設定したあとで text.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height), withAttributes: attributes) で文字を書き、 UIGraphicsGetImageFromCurrentImageContext() as UIImage! で画像を生成しています。

TextKit の exclusion path で文字を回り込ませる

先ほど作った画像を、UITextView の上に載せ、文字を回り込ませていきます。

こんな感じにviewを重ねます。

f:id:oishi-kenko:20171115230254p:plain

constraint などはよしなに調整してください。 今回のアプローチの少しイケてないところは、ドロップキャップの大きさを変えたり、テキストの attributes を変えると、レイアウトの微調整も必要になってくるところですね。。

ともあれ、他の方法もないので、この方法で突き進みます。

xibファイルが用意できたら、文字を回り込ませるような処理を書いていきます。

@IBOutlet weak var textView: UITextView!
@IBOutlet weak var dropCapImageView: UIImageView!
let text: String = "吾輩(わがはい)は猫である。名前はまだ無い。どこで生れたかとんと見当(けんとう)がつかぬ。"

textView.text = String(text[text.index(text.startIndex, offsetBy: 1)...])
setTextAttributes()

dropCapImageView.image = createDropCapImage(text: String(text[text.startIndex]))
let exclusionRect = CGRect(x: 0, y: 0, width: dropCapImageView.frame.size.width, height: dropCapImageView.frame.size.height)
let path: UIBezierPath = UIBezierPath(rect: exclusionRect)
textView.textContainer.exclusionPaths = [path]

また、ここで忘れてはいけないのは、1文字目は既に画像としてViewに含ませているので、2文字目以降の文字を UITextView に乗せていくというところです。

UITextView に2文字目以降の文字を代入し、 UIImageView に1文字目の画像を載せていきます。 最後に画像の周辺にテキストを回り込ませるための exclution path を生成し、 UITextView.textContainer.exclusionPaths に登録します。

ここまでいけば、完成です!

このあたりのコードを実装していけば、冒頭ご紹介したような

f:id:oishi-kenko:20171115232122p:plain

このような表示ができるはずです。

おしまいに

今回ご紹介したコードはこちらにおいておきました。

github.com

Xcode9.1 で作っていますので、古くて動かなくなったらご愛嬌ということで。 是非、みなさまのご参考になればと思います。

おしまいのおしまいに

おいしい健康では、一緒に Swift で iOS アプリを作ってくれる仲間を募集しています! 新しいプロジェクトであり、会社のコアプロダクトを作っているところですので、自由度も高く、もりもり開発していけます。

何より、この記事の内容を見て、思うところがある方とはぜひお話してみたいところです ( ̄ー ̄)

Swift初心者の関口がお送りしました〜。