iOS
Swift
NSAttributedString

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

概要

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

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

作ったもの

文字のサイズ感は目見当。

chirashi.png

//
//  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.
    }
}

説明

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

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

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

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

各種 の部分。
文字サイズは基本サイズの40%の大きさを指定。
や数字よりベースラインが少し下にずれて見えるので、.baselineOffset: 2 を指定して少し上にずらした。
ベースラインについては ラテン文字と欧文組版 をP14の図を参照。
雑に説明すると、.baselineOffset に正の数を与えると上に移動し、負の数を与えると下に移動する。

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

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

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

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

// ()のベースライン調整
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を触っている気分になった。

まとめ

参考

Mastering TextKit