ハイライト
Apple は iOS 18 で Multilingual Keyboard(手動切り替えなしで複数言語入力)、Live Text のアラビア語サポート、10 種類の新しいインド系数字体系など、複数の多言語強化を導入しました。しかしこのセッションの核心はこれらのシステム機能ではなく、多言語シナリオでよくあるバグを回避する方法を開発者に教えることです。
主要内容
あなたの app は中国語入力をサポートしていますか?ユーザーがピンインや手書きキーボードで中国語文字を入力している場合、自動補完ロジックが入力を中断する可能性があります — CJK 入力法は最初に「marked text」(下線付きの一時テキスト)を表示し、ユーザーが候補を選択して初めて消えます。コードが毎キーストロークでテキストを変更すると、この入力フローが壊れます。
同じ問題が検索にも現れます。ヒンディー語ユーザーが連絡先を検索しても、スペルスタイルの違いで見つからないことがある。検索ハイライトに太字を使うと、ヒンディー語の文字と付加符号が分断される。ウルドゥー語の Nastaliq スクリプトは一部 app でレンダリングが崩壊し、文字が完全に判読不能になる。
セッションは異なる地域の 3 人の友人 — シンガポールの April(中英バイリンガル)、インドの Raj(ヒンディー語+英語)、米国の Ismat(英語+ウルドゥー語)— を使い、これらの実際のシナリオを 1 つずつ分解し、それぞれ具体的な 1 行または数行のコード修正を提示します。
詳細
入力:キーボード記憶と marked text
April は異なる会話で中国語と英語を使い分け — キーボードは各会話の言語を自動記憶。textInputContextIdentifier をオーバーライドして実装(03:18):
override var textInputContextIdentifier: String? {
uniqueID
}
textInputContextIdentifierはUIResponderのメソッドで、一意の文字列を返す- システムはこの識別子に基づき、各入力コンテキストの言語設定を独立して記憶
- 同じ app 内の異なる会話で異なる identifier を使用可能 — キーボードが自動切り替え
キーボード高さが変化したとき(手書きキーボードが拡大するなど)、UI レイアウトを調整。2 つの方法:inputAccessoryView はキーボード直上に直接ドッキング(03:41)、keyboardLayoutGuide はカスタムビューとキーボードの相対位置に使用(04:00)。
CJK 入力の marked text は最も陥りやすい罠。テキスト変更前に marked text をチェックして修正(04:42):
if textView.markedTextRange.empty {
// Perform actions involving editing text
}
markedTextRangeは現在の marked text の範囲を返す- 空ならユーザーが候補を確定済み — テキスト変更安全
- 空でなければユーザーはまだ入力中 — インラインテキストを変更しない
- marked text 存在中も、それに基づいて検索し他の場所(table view など)に提案を表示可能 — Spotlight がこの方式
検索:localizedStandardRange とハイライト色
Raj がヒンディー語で検索しても連絡先が見つからない — 検索が range(of:) で完全一致しているが、ヒンディー語には複数のスペルスタイルがある。localizedStandardRange に切り替え(05:58):
let range = text.localizedStandardRange(of: search)
localizedStandardRangeは locale 規則に従う標準検索 API- iOS 18 でこの API が大幅強化 — より多くのスクリプトでクロススペルスタイルマッチとクロス数字体系マッチをサポート
- 1 行の置き換えで検索問題を修正
太字の検索ハイライトはヒンディー語テキストを分断 — 太字と通常体は異なるフォントで、文字と付加符号はフォントをまたいで接続できない。代わりに色でハイライト(07:24):
attributedString[range].foregroundColor = highlightColor
- マッチテキストのマークにフォント太さの変更の代わりに
foregroundColorを使用 - 色属性はフォントを変更しない — 文字と付加符号の接続を壊さない
また、斜体(italicization)は大多数の言語に対応する概念がない — 10 言語の「こんにちは」のうち 3 言語のみ斜体適用で視覚変化あり(07:32)。情報差異の伝達に斜体に依存しないこと。
表示:TextKit 2、Text Styles、人名フォーマット
Ismat がウルドゥー語レンダリング崩壊に遭遇 — 解決策は TextKit 2(09:39)。良いニュース:SwiftUI、UIKit、AppKit の label と text view はすでに TextKit 2 がデフォルト。
Text Styles はすべてのユーザー言語に適応した行間隔を保証(09:39):
// Text Styles
// SwiftUI
Text("Hello, world!") // uses .body Text Style by default
Text("Hello, world!").font(.title)
// UIKit
let label = UILabel()
label.text = "Hello, world!"
label.font = UIFont.preferredFont(forTextStyle: .body)
// AppKit
let textField = NSTextField(labelWithString: "Hello, world!")
textField.font = NSFont.preferredFont(forTextStyle: .body)
// Keep clipsToBounds off
clipsToBounds = false
- Text Styles は app 言語とユーザーのすべての優先言語を考慮し、行間隔を自動調整
clipsToBoundsはfalseを維持 — 多くの言語のテキストはビュー境界外へのレンダリングが必要- 特定言語向けの専用コードは不要
コンテンツに明確な言語がある場合、typesettingLanguage で指定(10:03):
// SwiftUI
Text(verbatim: "Hello, world!").typesettingLanguage(.init(languageCode: .english))
// UIKit
let label = UILabel()
label.text = "Hello, world!"
label.traitOverrides.typesettingLanguage = Locale.Language(languageCode: .english)
- デフォルト動作はユーザーのすべての優先言語を考慮(ウルドゥー語などに大きな行間隔)
- 言語指定後、組版はその言語の規則を使用(例:英語はよりコンパクトな行間隔)
人名表示は formatted API を使用(10:29):
// Formatting names
let nameComponents = PersonNameComponents
(givenName: "花子", familyName: "山田", nickname: "花ちゃん")
// Short Name (respects settings like "Prefer Nicknames")
let shortName =
nameComponents.formatted(.name(style: .short)) // 花ちゃん
// Abbreviated Name (can be used for monograms)
let monogram =
nameComponents.formatted(.name(style: .abbreviated)) // 山田
- 中国語と日本語では given name のみ表示は不適切 — short style は適切な場合 nickname を使用
- abbreviated style は monogram など — 中国語では family name を返す
- この API は「言語と地域」の「ニックネームを優先」設定を尊重
ローカライズ:文法エンジンと数字フォーマット
iOS 18 は自動文法一致(automatic grammatical agreement)エンジンをヒンディー語と韓国語に拡張(12:20)。サポート 8 言語:ドイツ語、英語、スペイン語、フランス語、イタリア語、ポルトガル語、ヒンディー語、韓国語。
数字フォーマットには 2 つの方法。コード内の文字列補間(13:43):
Text("\(numberOfDays)-day forecast")
Text内のnumberOfDaysは現在 locale に従って自動フォーマット- 実行時に数字が確定し、すべての言語で数字を使用する場面に適する
今年追加:ローカライズ文字列内で直接数字をフォーマット(14:21):
AttributedString(localized: "10-day forecast")
AttributedString(localized: "0.5× zoom")
- ローカライズ文字列内の数字フォーマットにゼロコード
- 翻訳時に
formatNumberで正しい数字体系を確保 — 例:ヒンディー語翻訳でインド数字 - 小数点区切りなどのフォーマット差異も処理
重要ポイント
-
やること:検索実装が
localizedStandardRangeを使用しているか確認。価値:ヒンディー語、アラビア語などには多数のスペルバリアントがあり、range(of:)の完全一致ではユーザーがコンテンツを見つけられない — iOS 18 のlocalizedStandardRangeはクロススペルスタイルとクロス数字体系マッチをサポート。始め方:range(of:)とrangeOfStringをグローバル検索し、localizedStandardRange(of:)に置き換え — 1 行のコード。 -
やること:自動補完ロジックが marked text を処理しているか確認。価値:CJK ユーザーが自動補完で入力中断されると体験が極めて悪い — IME フローが壊れ、再入力が必要。始め方:すべてのテキスト変更ロジック前に
if textView.markedTextRange.empty判定を追加、marked text 存在時は外部提案リストのみ更新、インラインテキストは変更しない。 -
やること:検索ハイライトに太字の代わりに色を使用、多言語シナリオで斜体に依存しない。価値:太字はヒンディー語などで文字と付加符号の接続を分断(異なるフォントは接続不可)、斜体は大多数の言語で視覚効果なし。始め方:検索ハイライトを
font: .boldからforegroundColorに変更、斜体で情報差異を伝えるすべての箇所をレビュー — 色またはフォントサイズで代替。 -
やること:すべての label と text view で
clipsToBoundsがfalseであることを確認。価値:多くの言語のテキスト(ウルドゥー語 Nastaliq スクリプト、ヒンディー語の付加符号)はビュー境界外へのレンダリングが必要 — クリッピングで切り詰めまたは判読不能に。始め方:clipsToBounds = trueの label/text view をすべて検索し、必要性を確認 — 大部分はデフォルトfalseで十分。
関連セッション
- What’s new in UIKit — TextKit 2 改善と新 API を含む UIKit 更新
- Discover SwiftUI enhancements — テキストレンダリングとレイアウト強化を含む SwiftUI 改善
- What’s new in SF Symbols 6 — 多様な言語とスクリプトにローカライズされた SF Symbols
- What’s new in SwiftUI — SwiftUI テキストカスタマイズとレイアウト改善
コメント
GitHub Issues · utterances