iOSでチラシっぽい価格レイアウトを再現してみた

## 概要
TwitterでフォロワーさんがUIStackViewで挑戦してたのを見かけた。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">あえて同じくStackViewで挑戦して¥が下はみ出る… <a href="https://t.co/0rPgMqLocr">https://t.co/0rPgMqLocr</a></p>&mdash; ありぜ (@a_aryzae) <a href="https://twitter.com/a_aryzae/status/1038065355229356036?ref_src=twsrc%5Etfw">2018年9月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

スレッドを遡ってみると、NSAttributedStringで実装できるんじゃないかという話があり、興味が湧いたので挑戦してみた。

## 作ったもの
文字のサイズ感は目見当。

<img width="547" alt="chirashi.png" src="https://qiita-image-store.s3.amazonaws.com/0/81221/80367c05-09af-2b53-d42b-92ae1592de97.png">

```swift
//
//  ViewController.swift
//  Price
//
//  Created by macneko on 2018/09/07.
//  Copyright © 2018年 macneko. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let label = UILabel()
        self.view.addSubview(label)
        
        label.frame = self.view.frame
        label.textAlignment = .center
        
        let fontSize: CGFloat = 80
        let font = UIFont.systemFont(ofSize: fontSize)
        let attributeText = NSMutableAttributedString(string: "各種￥(税込)280",
                                                      attributes: [.font: font, .foregroundColor: UIColor.red])
        // 各種
        attributeText.addAttributes([.font: UIFont.systemFont(ofSize: fontSize * 0.4),
                                     .baselineOffset: 2],
                                    range: NSRange(location: 0, length: 2))
        // ￥
        attributeText.addAttributes([.font: UIFont.systemFont(ofSize: fontSize * 0.6),
                                     .kern: -50],
                                    range: NSRange(location: 2, length: 1))
        // (税込)
        attributeText.addAttributes([.font: UIFont.systemFont(ofSize: fontSize * 0.2),
                                     .baselineOffset: (fontSize * 0.8) - (fontSize * 0.2) - 4],
                                    range: NSRange(location: 3, length: 4))
        // ()のベースライン調整
        attributeText.addAttributes([.baselineOffset: (fontSize * 0.8) - (fontSize * 0.2) - 3],
                                    range: NSRange(location: 3, length: 1))
        attributeText.addAttributes([.baselineOffset: (fontSize * 0.8) - (fontSize * 0.2) - 3],
                                    range: NSRange(location: 6, length: 1))
        label.attributedText = attributeText
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
```

## 説明
ソースコードを部分的に切り出して処理内容を説明する。

```swift
let fontSize: CGFloat = 80
let font = UIFont.systemFont(ofSize: fontSize)
let attributeText = NSMutableAttributedString(string: "各種￥(税込)280",
                                              attributes: [.font: font, .foregroundColor: UIColor.red])
```

はじめに `attributeText` に基本的な装飾と文字列を格納しておいて、装飾を変更したい部分をNSRangeで指定して、加工していく方法をとることにした。

```swift
// 各種
attributeText.addAttributes([.font: UIFont.systemFont(ofSize: fontSize * 0.4),
                             .baselineOffset: 2],
                            range: NSRange(location: 0, length: 2))
```

**各種** の部分。
文字サイズは基本サイズの40％の大きさを指定。
`￥` や数字よりベースラインが少し下にずれて見えるので、`.baselineOffset: 2` を指定して少し上にずらした。
ベースラインについては [ラテン文字と欧文組版](https://www.morisawa.co.jp/fonts/multilingual/typesetting/pdf/la_multilingualtypesetting_1802.pdf) をP14の図を参照。
雑に説明すると、`.baselineOffset` に正の数を与えると上に移動し、負の数を与えると下に移動する。

```swift
// ￥
attributeText.addAttributes([.font: UIFont.systemFont(ofSize: fontSize * 0.6),
                             .kern: -50],
                            range: NSRange(location: 2, length: 1))
```

**￥** の部分。
文字サイズは基本サイズの60％の大きさを指定。
`.kern: -50` を指定して、次の文字の **(** との字間を詰めた。
雑に説明すると、`.kern` （カーニング）は当該文字と次の文字の文字間のアキを詰める設定。
なお、パーレンにはカーニングが適用されないっぽかったので、**￥**を前にもってきてカーニング設定をしている。

```swift
// （税込）
attributeText.addAttributes([.font: UIFont.systemFont(ofSize: fontSize * 0.2),
                             .baselineOffset: (fontSize * 0.8) - (fontSize * 0.2) - 4],
                            range: NSRange(location: 3, length: 4))
```

**(税込)** の部分。
文字サイズは基本サイズの20％の大きさを指定。
ベースラインは相対値で計算したものの、それだけではうまく揃わなかったので、微調整している。

```swift
// ()のベースライン調整
attributeText.addAttributes([.baselineOffset: (fontSize * 0.8) - (fontSize * 0.2) - 3],
                            range: NSRange(location: 3, length: 1))
attributeText.addAttributes([.baselineOffset: (fontSize * 0.8) - (fontSize * 0.2) - 3],
                            range: NSRange(location: 6, length: 1))
```

**(** と **)** の部分。
ベースラインが揃わなかったので、この部分だけさらに微調整した。

文字列ごとにNSAttributedStringKeyを指定していき、最後に結合する方法でも良かったんだけど、DTP的な手法のほうが手慣れているため、この方法にしてしまったがゆえに、すごくIllustratorやInDesignを触っている気分になった。

## まとめ
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">奥さんに「できたで！」って誇らしげに見せたら「あ、はい」という感想をいただいた。iOSでもチラシレイアウトできるんやで！すごくない？！すごいやろ？！ <a href="https://t.co/7VlaDJlVyc">https://t.co/7VlaDJlVyc</a></p>&mdash; こうちゃん黒猫まみれ (@macneko_ayu) <a href="https://twitter.com/macneko_ayu/status/1038107076491235329?ref_src=twsrc%5Etfw">2018年9月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 参考
[Mastering TextKit](https://speakerdeck.com/kishikawakatsumi/mastering-textkit-1)