非日本語圏日本語フォント問題、SwiftUIにおける対処事例(2023年中旬)

デバイスの言語設定で日本語を選択していないような場面において、日本語文章のフォントが想定外のものになる現象は多くのデバイスで発生する。

![例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/c5929b30-7929-3770-da82-ba9ad7fb94f6.png)


今回は2023年中旬にAppleプラットフォーム向けアプリに実施したSwiftUIにおける対処事例を紹介する。


# 参考
2015年に公開された下記のブログポストを参考にした。このポストでは「本現象の解説」と「UIKit環境におけるNSAttributedStringを用いた解決策」を示している。また、本現象のことを「中華フォント現象」と呼んでいる。

https://qiita.com/usagimaru/items/46c27c42e862fb8aba7e


# 対象アプリ

https://apps.apple.com/app/id1620268476

![アプリ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/19ffae4f-3c86-7f98-4405-0156bfaccfa0.png)

- 全てSwiftUIで実装
- iOS 15以降をサポート
- iOS/iPadOS/watchOS/macOS/tvOSといった全てのAppleプラットフォームでリリース
- 英語(プライマリー)と日本語に対応
- 将棋の駒をシンプルにSwiftUI.Textで表示
- OSの言語設定で日本語を指定していない場合、将棋の駒に対して本現象が発生
- 特に「角」は見た目が大きく変化

![将棋盤.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/ae840189-e9b9-db46-21f7-f81d708007d6.png)

![角.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/cf8df259-ea00-26a6-2637-e2fc689cd747.png)

2023年5月にリリースしたバージョン1.4にて本現象を解消した。


# 対処内容
前記のブログポストを参考にしつつ、今回はNSAttributedStringの後継API(?)であるAttributedStringを採用した。

[AttributedString | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/attributedstring)


## 最低限の実装例
```swift
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レイヤーで全て適用することにした。

```swift
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](https://developer.apple.com/documentation/swiftui/text/typesettinglanguage(_:isenabled:)-8dobz)
- [typesettingLanguage(_: TypesettingLanguage, isEnabled:) | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/text/typesettinglanguage(_:isenabled:)-85e9h)

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

```swift
struct 例3: View {
    var 文字列: String
    var body: some View {
        Text(self.文字列)
            .typesettingLanguage(.init(languageCode: .japanese))
    }
}
```

たった1行書くだけで解決出来るようになったっぽい。

* * *

```swift
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)))
```

いくつかのパラメータが用意されている。上記の各コードで(恐らく)同様の表示がされるようだ。私個人の理解力ではその差は不明。

* * *

![スクリーンショット 2023-09-27 13.05.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564662/048869fe-3fba-f9b6-3e12-810bf461e7a5.png)

- [What’s new in UIKit - WWDC23 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2023/10055/)
- [typesettingLanguage \| Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uitraitoverrides/4213133-typesettinglanguage)

UIKit向けにも同等のAPIがあるようだ。

# まとめ
- iOS 17世代以降の環境ならtypesettingLanguage
- iOS 15世代以降、iOS 17世代より前の環境ならAttributedString
- 古い環境も対象になるUIKit等ならNSAttributedString

を採用すれば良さそう。


# リンク
https://qiita.com/usagimaru/items/46c27c42e862fb8aba7e

https://apps.apple.com/app/id1620268476

https://github.com/FlipByBlink/PlainShogi

https://developer.apple.com/documentation/foundation/attributedstring

https://developer.apple.com/documentation/swiftui/text/typesettinglanguage(_:isenabled:)-8dobz

https://developer.apple.com/documentation/swiftui/text/typesettinglanguage(_:isenabled:)-85e9h

https://developer.apple.com/videos/play/wwdc2023/10055/

https://developer.apple.com/documentation/uikit/uitraitoverrides/4213133-typesettinglanguage