ご覧いただきありがとうございます。
この記事は Qiita Advent Calendar 2017 ゆめみ1 の第 22 日目の記事です。
前日は @clown0082 さんの記事でした。
はじめに
iOS 11 から Dynamic Type でシステムフォントに加えて
ついにカスタムフォントが設定できるようになりました。
本記事執筆時では iOS 11 だけが対応可能となっており,
なかなか採用されにくいとは思いますが,
この記事では,どのようにしたら対応できるのかについて書きます。
システムフォントの場合とカスタムフォントとで一部表示が異なることがあり
もしご存知の方がいらっしゃったら教えていただきたいです。
この記事は iOS の Advent Calendar 2017 第 21 日目の記事2に
関連した内容になります。合わせてご覧いただけたら嬉しいです。
Dynamic Type とは(おさらい)
Dynamic Type は iOS 7 から導入されました。
設定アプリ,iOS 11 からはコントロールセンターに追加することで
ユーザが好みのフォントサイズに変えられますよね。
ユーザが設定したフォントサイズに伴って
アプリ内のフォントサイズも変化させるというものです。
私は一番小さなフォントサイズ設定が好きです。
ただ,全ユーザがそうだとは限りません。
ユーザによってみやすいフォントサイズは異なるという意識を持つことが大事です。
Dynamic Type でカスタムフォントに対応する
ひとつ前の記事2に書いたのと同じ実装をした上でさらなる実装が必要です。
UIFontTextStyle
を適用しただけではシステムフォントのままだからです。
下記が Dynamic Type 対応の要点(復習)です。
Storyboard など IB の場合
- UI 部品を配置しフォント部分で
UIFontTextStyle
のどれかを選ぶ - Automatically Adjusts Font 部分にチェックを入れる
コードの場合
- UI 部品のインスタンスを作り,フォント設定に
UIFontTextStyle
を指定 -
adjustsFontForContentSizeCategory
をtrue
にする
// body を設定する例
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
カスタムフォントの対応
カスタムフォントに対応する場合は上記の実装に加えて,
UIFontMetrics
3 を用います。
カスタムフォントを scaledFont(for:)
メソッドに渡すことで
ユーザが設定中のフォントサイズに合うように自動調整された
指定された UIFontTextStyle
のフォントが得られます。
多分コードで書くしかないです。。。
Futura
に最近ハマっているのでカスタムフォントの例として使います。
iOS 11 だけのコードでよければ下記のようになります。
// Futura-Medium で body を設定する例
let customFont = UIFont(name: "Futura-Medium", size: UIFont.labelFontSize)
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
iOS 11 未満はシステムフォントになるように場合分けします。
もっと綺麗に書けそうな気がします。
if #available(iOS 11.0, *) {
// カスタムフォントの綴り間違いには注意
if let customFont = UIFont(name: "Futura-Medium", size: UIFont.labelFontSize) {
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)
} else {
label.font = UIFont.preferredFont(forTextStyle: .body)
}
} else {
// iOS 11 未満はシステムフォントにする
label.font = UIFont.preferredFont(forTextStyle: .body)
}
簡単な実装例で確認
サンプルコード4
GitHub にサンプルアプリを用意しましたので
見づらいところ等,適宜参考にしてください。
Swift は 今まで業務で携われていないこともあり,
まともにレビューしてもらったことがないので
コード間違い・改善点等のご指摘,是非お願いいたします。
実装環境
- Xcode 9.2
- iOS 10 and later(実質 iOS 11 以上)
- 説明の都合上 FatViewController でお送りします
実装例
例として下記のような文字列と UIFontTextStyle
の Array を用意し,
TableView
のセルに流し込んで textLabel.text
に表示させてみます。
struct DynamicTypeSample {
static let textArray: [String] =
["Body", "Callout", "Caption1", "Caption2", "Footnote",
"Headline", "Subhead", "Title1", "Title2", "Title3"]
static let styleArray: [UIFontTextStyle] =
[.body, .callout, .caption1, .caption2, .footnote,
.headline, .subheadline, .title1, .title2, .title3]
}
UITabelViewDataSource
の実装だけ書くと下記のようになります。
**実装例:クリックでコード表示**
extension CustomFontViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DynamicTypeSample.textArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamicTypeCustomFontListCell")
cell?.textLabel?.text = DynamicTypeSample.textArray[indexPath.row]
// Custom Font は iOS 11 から
if #available(iOS 11.0, *) {
if let customFont = UIFont(name: "Futura-Medium", size: UIFont.labelFontSize) {
cell?.textLabel?.font = UIFontMetrics(forTextStyle: DynamicTypeSample.styleArray[indexPath.row]).scaledFont(for: customFont)
} else {
cell?.textLabel?.font = UIFont.preferredFont(forTextStyle: DynamicTypeSample.styleArray[indexPath.row])
}
} else {
cell?.textLabel?.font = UIFont.preferredFont(forTextStyle: DynamicTypeSample.styleArray[indexPath.row])
}
cell?.textLabel?.adjustsFontForContentSizeCategory = true
cell?.selectionStyle = .none
return cell!
}
}
セクション(Header)がある場合は文字列を流し込むだけでは
システムフォントのままになるのでカスタムフォントに対応するために
UITableViewDelegate
の Header 系の Delegate メソッドで個別で書く必要があります。
**実装例:クリックでコード表示**
extension CustomFontViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = self.generateHeaderView()
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let headerView = self.generateHeaderView()
return headerView.frame.size.height
}
}
// Header の UIView を生成し返す
func generateHeaderView() -> UIView {
let sectionLabel = UILabel(frame: CGRect(x: self.dynamicTypeListTableView.separatorInset.left,
y: 4.0,
width: (self.dynamicTypeListTableView.frame.size.width - self.dynamicTypeListTableView.separatorInset.left),
height: 0.0))
sectionLabel.text = "Custom Font"
if #available(iOS 11.0, *) {
if let customFont = UIFont(name: "Futura-Medium", size: UIFont.labelFontSize) {
sectionLabel.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont)
} else {
sectionLabel.font = UIFont.preferredFont(forTextStyle: .headline)
}
} else {
sectionLabel.font = UIFont.preferredFont(forTextStyle: .headline)
}
sectionLabel.sizeToFit()
let sectionView = UIView(frame: CGRect(origin: .zero,
size: CGSize(width: self.dynamicTypeListTableView.frame.size.width,
height: sectionLabel.frame.size.height + 8.0)))
sectionView.backgroundColor = UIColor(red: 232/255, green: 233/255, blue: 237/255, alpha: 1)
sectionView.addSubview(sectionLabel)
return sectionView
}
実行例
実行し,Accessibility Inspector
を使って
フォントサイズを変えてみると下記のような感じになります。
Accessibility Inspector
の使い方についてはこの記事2をご覧ください。
各 UIFontTextStyle
ごとにちゃんと変化していることがわかります🎉
がしかし,システムフォント側となんか違う・・・
特に Caption2
... 明らかに大きい。
🤔
システムフォントとの挙動の違いについて
この動作だけ見ると問題なくフォントサイズが変わっているように
見えるのですが,一部の UIFontTextStyle
で大きさが
システムフォントと異なってしまっています。
このことについて調べてみたのですがよくわからずでした。
下記の記事にもこのことが一部触れられていて .caption2
の代わりに
.largeTitle
(iOS 11からの UIFontTextStyle
)が使われているようで
バグではないかということでした。
Note: I am not sure if it is a bug or a “feature” but the .caption2 style seems to scale larger than the .caption1 style even though it uses a smaller point size at the .large size.
via
Using A Custom Font With Dynamic Type
とりあえず,Technical Support Incident(TSI)5で質問してみようと思います。
こういうのを WWDC のラボで聞けばいいんだろうなー
おわりに
iOS 11 から Dynamic Type でカスタムフォントを指定できるように
なったので今回はその対応方法について記事を書きました。
ありそうでなかった機能なので WWDC 17 のときは嬉しかったのですが,
iOS 11 以上かそれ未満かで場合分けなどいざ実装してみると色々大変で
iOS 10 のサポート切ってもいいかなというタイミング(iOS 13くらい?)まで
本格的な採用はないのでは?というのが現時点の私見です。
Dynamic Type 自体が商用アプリとして採用されにくい部分があるので,
まずは社内ハッカソンや社内の便利ツール(受付アプリなど)など
小規模な単位で対応してみたら面白いかもしれないですね!
Apple が WWDC 17 の色々なセッションで言及しているだけに
知見はためておいて損はない気がします。
来年も引き続き UI 周りに力入れて,新しい技術習得をしながら,
日々の業務・個人アプリの制作を頑張っていこうと思います。
ここまでご覧いただきありがとうございました!
明日は @u-minor さんが担当します!!