デバイスの言語設定で日本語を選択していないような場面において、日本語文章のフォントが想定外のものになる現象は多くのデバイスで発生する。
今回は2023年中旬にAppleプラットフォーム向けアプリに実施したSwiftUIにおける対処事例を紹介する。
参考
2015年に公開された下記のブログポストを参考にした。このポストでは「本現象の解説」と「UIKit環境におけるNSAttributedStringを用いた解決策」を示している。また、本現象のことを「中華フォント現象」と呼んでいる。
対象アプリ
- 全てSwiftUIで実装
- iOS 15以降をサポート
- iOS/iPadOS/watchOS/macOS/tvOSといった全てのAppleプラットフォームでリリース
- 英語(プライマリー)と日本語に対応
- 将棋の駒をシンプルにSwiftUI.Textで表示
- OSの言語設定で日本語を指定していない場合、将棋の駒に対して本現象が発生
- 特に「角」は見た目が大きく変化
2023年5月にリリースしたバージョン1.4にて本現象を解消した。
対処内容
前記のブログポストを参考にしつつ、今回はNSAttributedStringの後継API(?)であるAttributedStringを採用した。
AttributedString | Apple Developer Documentation
最低限の実装例
struct 例1: View {
var 文字列: String
private var attributedContent: AttributedString {
var 値 = AttributedString(stringLiteral: self.文字列)
値.languageIdentifier = "ja" // 👈ここで指定する
return 値
}
var body: some View {
Text(self.attributedContent)
}
}
実際のアプリとほぼ同様の実装例
「Plain将棋盤」では駒の文字のサイズ変えたり太字にしたりセリフ体にしたりアンダーライン引いたりする必要があるため、Textに対する各装飾をSwiftUIのModifierではなくAttributedStringレイヤーで全て適用することにした。
struct 例2: View {
var 文字列: String
var サイズ: CGFloat
var 太字: Bool
var セリフ体: Bool
var アンダーライン: Bool
private var attributedContent: AttributedString {
var 値 = AttributedString(stringLiteral: self.文字列)
値.font = Font.system(size: self.サイズ,
weight: self.太字 ? .bold : .regular,
design: self.セリフ体 ? .serif : .default)
if self.アンダーライン {
値.underlineStyle = .single
}
値.languageIdentifier = "ja"
return 値
}
var body: some View {
Text(self.attributedContent)
}
}
iOS 17世代以降の新API
iOS 17世代(2023年秋)以降のAppleプラットフォーム向けにtypesettingLanguageというViewModifierが登場した。
- typesettingLanguage(_: Locale.Language, isEnabled:) | Apple Developer Documentation
- typesettingLanguage(_: TypesettingLanguage, isEnabled:) | Apple Developer Documentation
In some cases Text may contain text of a particular language which doesn’t match the device UI language. In that case it’s useful to specify a language so line height, line breaking and spacing will respect the script used for that language.
struct 例3: View {
var 文字列: String
var body: some View {
Text(self.文字列)
.typesettingLanguage(.init(languageCode: .japanese))
}
}
たった1行書くだけで解決出来るようになったっぽい。
Text(self.文字列)
.typesettingLanguage(.init(identifier: "ja"))
Text(self.文字列)
.typesettingLanguage(.init(languageCode: .japanese))
Text(self.文字列)
.typesettingLanguage(.init(script: .japanese))
Text(self.文字列)
.typesettingLanguage(.init(region: .japan))
Text(self.文字列)
.typesettingLanguage(.explicit(.init(languageCode: .japanese)))
いくつかのパラメータが用意されている。上記の各コードで(恐らく)同様の表示がされるようだ。私個人の理解力ではその差は不明。
- What’s new in UIKit - WWDC23 - Videos - Apple Developer
- typesettingLanguage | Apple Developer Documentation
UIKit向けにも同等のAPIがあるようだ。
まとめ
- iOS 17世代以降の環境ならtypesettingLanguage
- iOS 15世代以降、iOS 17世代より前の環境ならAttributedString
- 古い環境も対象になるUIKit等ならNSAttributedString
を採用すれば良さそう。
リンク