LoginSignup
1
1

中華フォントをたった1行で解決するtypesettingLanguage【SwiftUI】

Posted at

中華フォント例

中華フォントとは

中華フォントとは、デバイスの言語設定で日本語を選択していないような場面において、日本語文章(特に漢字)のフォントが中華圏基準のものになる現象の俗称です。

比較

注記
「中華フォント」という呼称がポリティカルコレクトネス的に適切か否かを私個人としては判断しかねるため、本記事では以降「中華フォント(俗称)」と呼びます。

typesettingLanguageとは

特定の言語/書体/地域の表現を尊重するように明示的に指定するSwiftUIのViewModifierです。iOS 17世代以降の全てのAppleプラットフォームで対応しています。

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.

UIKit

UIKitにも同等のAPIがあるようです。

実践

例.png

Text("今すぐ直角に変えてください。")
Text("今すぐ直角に変えてください。")
    .typesettingLanguage(.init(languageCode: .japanese))

中華フォント(俗称)以外での事例

「同じ文字列なのに特定の言語/書体/地域によってフォント表現が変わる現象」は中華フォント(俗称)に限りません。

ロシア語とブルガリア語の「ニコライ」

ニコライ(Николай)という言葉も言語によって表記が変わります。

ニコライ.png

Text(verbatim: "Николай")
    .typesettingLanguage(.init(languageCode: .russian))
Text(verbatim: "Николай")
    .typesettingLanguage(.init(languageCode: .bulgarian))

どちらもUnicode的にはU+041D 0438 043A 043E 043B 0430 0439です。

タイ語などは縦に長い文字型

英文の中に固有名詞としてタイ語のような縦に長い文字型を混在させると行の高さのミスマッチが起きるケースがあります。typesettingLanguageで行の高さ調整も実現出来ます。

thai.png

Text(verbatim: "Who's a good dog, ไมโล?")
    .typesettingLanguage(.init(languageCode: .english))
Text("""
    Who's a good dog, \
    \(Text("ไมโล").typesettingLanguage(.init(languageCode: .thai)))?
    """)

中華フォント(俗称)の逆

もちろん中華フォント(俗称)の逆もあるわけです。例えば「日本語しか対応していないアプリを対象言語圏の人達が使う場合」や「日本人が対象言語習得を目的とした学習アプリを使う場合」などです。

返.png

List(Array<Locale.Language>([
    .init(languageCode: .chinese, script: .hanSimplified),
    .init(languageCode: .chinese, script: .hanTraditional),
    .init(languageCode: .chinese, script: .hanTraditional, region: .hongKong),
    .init(languageCode: .korean),
    .init(languageCode: .japanese)
]), id: \.self) {
    Text(verbatim: "返")
        .typesettingLanguage($0)
        .badge($0.languageCode?.debugDescription)
        .badge($0.script?.debugDescription)
        .badge($0.region?.debugDescription)
}

typesettingLanguageの引数

.typesettingLanguage(Locale.Language(identifier: "ja"))
.typesettingLanguage(Locale.Language(languageCode: .japanese))
.typesettingLanguage(Locale.Language(script: .japanese))
.typesettingLanguage(Locale.Language(region: .japan))
.typesettingLanguage(Locale.Language(components: /* 略 */))
.typesettingLanguage(TypesettingLanguage.explicit(.init(languageCode: .japanese)))

様々なパラメータを選択できます。
TypesettingLanguage.explicitの有無で何が変わるのかは私個人の理解力では不明。

Textに渡す値の型について

Text(verbatim: "返")
    .typesettingLanguage(.init(languageCode: .japanese))
Text("返") // ← これだと失敗する?
    .typesettingLanguage(.init(languageCode: .japanese))

Textに渡す値の型によってtypesettingLanguageが適用されたりされなかったりします。型を指定せずに文字列リテラルを渡すとLocalizedStringKey等に型推論されるのでそれ関係で挙動が変わるのだと思います。
バグかどうかは分かりませんが、実際に実装する際にはTextに渡す値の型には注意した方がよさそうです。

iOS 17世代未満は?

iOS 15世代以降、iOS 17世代未満はAttributedStringでこんな感じ

struct 日本語テキスト: View {
    var 文字列: String
    private var attributedContent: AttributedString {
        var  = AttributedString(stringLiteral: self.文字列)
        .languageIdentifier = "ja"
        return 
    }
    var body: some View {
        Text(self.attributedContent)
    }
}

それ以前はNSAttributedStringでこんな感じ

class ViewController: UIViewController {
	@IBOutlet weak var label: UILabel!
	override func viewDidLoad() {
		super.viewDidLoad()
        let nsMutableAttributedString = NSMutableAttributedString(string: /* 略 */, attributes: [
            kCTLanguageAttributeName as NSAttributedString.Key : "ja",
        ])      
		label.attributedText = nsMutableAttributedString
	}
	//色々省略
}

詳しくはこちらを参照

まとめ

  • typesettingLanguageという新APIがとても良い感じ
  • 特定の言語/書体/地域の表現を尊重するように明示的に指定することがたった1行で可能
  • iOS 17世代以降の全てのAppleプラットフォームに対応

関連リンク


余談

求職中です。興味ある方、連絡ください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1