ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Foundationの新機能
Foundationの最新のアップデートが、Appのローカリゼーションと国際化のサポートの改善にどのように役立つかをご確認ください。Swift専用に設計された新しいAttributedStringを紹介します。Markdownを使用して各言語に対応したテキストにスタイルを適用する方法を確認しましょう。各言語に対応したテキストを自動的に修正して文法的性と複数形表記に合致させる文法準拠エンジンを検証します。日付と数値のフォーマット設定に関する改良点をお伝えします。これらの改良点により、パフォーマンスが向上すると共に複雑な要件がシンプル化されます。
リソース
関連ビデオ
WWDC23
WWDC22
WWDC21
-
ダウンロード
こんにちは Tonyです Foundationチームのエンジニアです このセッションへようこそ FoundationフレームワークはAppとフレームワークの 基本機能を提供し ファイル操作からネットワーク 通知機能まで 豊富な機能を備えています 今回は Appに必要な国際化と ローカライズに焦点を当てます 今年のリリースはこのAPIにおいて 過去最大の進歩を遂げています まず SwiftにおけるAttributedStringを 再考する最下層の作業から始めました Swiftのためにformatterを再構築し より速く 使いやすくし新機能を追加しました 最後にオート・グラマー・アグリーメントという 新機能があり ローカライズされた文字列を 劇的に減らすことができ 同時にコードもシンプルになります ではAttributedStringsから AttributedStringはキャラクタ 範囲のセット およびディクショナリの組み合わせです 属性を持つ文字列ではキーと値のペアである属性を 文字列の特定の範囲に関連付けることができます 一般的な属性はSDKで定義されますが 独自に作成することも可能です リッチテキストをサポートするAPIでは AttributedStringsをよく見かけます 例を見てみましょう これは私が作ったカフェというAppで シンプルなメニューです 食べたいもの そしてサイズや量を選びます 最後にレシートに 注文したもののリストが表示されます サンキューメッセージを一番下に入れました これがAttributedStringです 太字の部分と斜体の部分があります リンクもついています このように属性が重なることもあります Foundationの初期から NSAttributedStringという参照型がありました 今年は 新しい構造体AttributedStringを 導入し Swiftが提供する すべての機能を最大限に活用します まず第一に値型であること また Swift Stringと同じ文字カウントの 動作をします ソフトウェアを簡単に書けるという 取り組みの一環で AttributedStringは完全にローカライズできます 最後に 安全と安心を考えて作られています それは強力な型付けによるコンパイル時と Codableによるアンアーカイブ時を含みます 新しいAttributedStringを 早速見てみましょう
サンキューのメッセージを作ります まずはシンプルに AttributedStringを作ります この文字列全体に属性を設定します フォントの設定並にシンプルにできます AttributedStringの構築では すべての属性が直接利用でき 正しい型を使用しています 例えばこれはSwiftUIフォントです 次に別の部分を作ります これはリンクがつくので URLをつけます フォントとリンクを属性全体に設定しました 部分的に変える方法は後ほど説明します AttributeContainerという便利ツールもあります 文字列がなくても 属性や値を単独で保持できる場所です ここではメッセージの重要性に応じて Containerを作成しいくつか属性を設定します そしてその属性を両方のAttributedStringsに マージします 先程も言った通りAttributedStringは キャラクタ 範囲 ディクショナリの組合せです AttributedStringはそのどれか1つの コレクションではありません 代わりに これらのプロパティにアクセスする viewと呼ばれるものがあります 最も重要なview2つは 文字列へのアクセスを提供するcharactersと 属性へのアクセスを提供するrunsです これらのviewはSwiftのコレクションです つまりArrayなどの型でおなじみの機能が ここでも利用できます 別の例を見てみましょう デザイナーの指示で句読点を オレンジ色にして 華やかさを演出したとします まずAttributedStringのどこに 句読点があるか調べる必要があります 他のSwiftコレクション同様 AttributedStringのViewは indicesを使用します インデックスによる反復処理には 標準ライブラリのindices関数を使います 次にisPunctuationという関数を使い 変更が必要な文字か確認します 最後にAttributedStringの機能スライシングで 文字列のサブレンジのみに属性を適用します 範囲はこのindexから 次のindexまで続く1つのキャラクタです 句読点がオレンジになりました もう1つのviewであるrunsを見てみましょう runは特定の属性の 開始位置 長さ 値です まずメッセージ内のrunを数えます 文字列内の属性値の 連続した範囲を反復します この文字列にはrunが4つあります runは各属性の値またはnilが設定されています rangeのcharactersとrunは交換可能なので 属性に対する文字列を探したりその逆も可能です ここではcharactersのサブスクリプトに 属性のrangeを使用し結果を 独立した文字列に変換しています
特定の属性に焦点を当てrunを見るのが 有効であることが多いです ここではキーパスのリンクを使って link属性だけのために合体させています コレクションの各要素は文字列に設定されている 可能性のある他の属性を 考慮せずにlink属性の値を提供します リンクだけを見ると3つの流れがあります 最初は設定されていない状態 2番目は値が設定されている状態 3番目はピリオドで設定されていない状態です Runを反復すると 値と範囲のtupleが得られます 値がタイプセーフなのでキャストしたり 間違ったタイプになる心配がなく URL上のschemeのようなAPIを使用できます ここではAttributedStringに 含まれるリンクがhttpsか確認しています また 部分文字列を探して その範囲を使って文字や属性を編集する という便利なテクニックもあります 例えば“visit”をスローバックな雰囲気に 置き換えてみます まず部分文字列の範囲を探します 次にその範囲を使い そのサブレンジだけに属性や文字を設定します 結果 6つのrunを持つ AttributedStringになります 続いてローカライズについて説明します AttributedStringは完全にローカライズできます またObjective-CのNSAttributedStringに ローカライズサポートを追加しました AttributedStringsは通常の文字列と同様 Appの文字列ファイルに配置されます Swiftでは SwiftUIのTextViewのように 文字列補間で文字列とAttributedStringの ローカライズフォーマットをサポートします 簡単な例を挙げます この関数はドキュメント名でカスタマイズされた ローカライズされた文字列を返します %@や%dのようなフォーマット指定子や フォーマット関数の代わりに 値を直接渡すことができます AttributedStringでも同様の方法が使えます
Xcodeでは これらの新しいイニシャライザから コンパイラで文字列ファイルを生成できます これをオンにするにはBuild settingsで Localization settingsを探し Use Compiler to extract Swift Stringsをオン ローカライズされたAttributedStringが どう属性を取得するか気になりますね AttributedStringにMarkdownのサポートが 追加されました ローカライズされたAttributedStringを SwiftUIのTextで使用する例を紹介します まずシンプルなストリングから始めます テキストにアステリスクを追加し SwiftUIが太字として レンダリングすることで 文字を強調します リンクのサポートも提供しています 別の言語用にカスタマイズされたURLを 提供する絶好の機会です 取り消し線やコードボイスなど その他のインラインスタイルにも対応します 最後にAttributedStringsのアーカイブを説明します まずNSAttributedStringの参照型との間で 変換する機能が必要です AttributedStringsはデータモデルになる可能性が あり エンコードやデコードできる必要があります 最後にMarkdownでカスタム属性を指定する 方法です これらの操作はすべて関連しています まずはコンバージョンから見てみましょう
NSAttributedStringを使用するコードを たくさん書いてきたので構造体からクラス型への 変換が簡単にできるようにしました NSAttributedStringプロパティのViewです NSAttributedStringイニシャライザに AttributedString構造体を渡して変換します SDKに含まれる属性については これで十分です ではエンコードとデコードを見てみましょう カフェAppのレシートを格納する構造体を紹介します ここでもSwiftUI UIKit AppKit Foundation が提供する属性を利用しています AttributedStringのデフォルトの Codable実装があればいいわけです ReceiptにCodableの適合性を追加するだけで 完了です もう少し踏み込んでカスタム属性の エンコーディングに対応してみましょう まずは属性そのものの話から 属性はキーと値の2つの部分で構成されます キーは新しいAttributedStringKey プロトコルに準拠した型です どのようなタイプの値が必要なのか またアーカイブの名前を定義します このキーは他のプロトコルに準拠して 値のエンコードやデコード 方法をカスタマイズできます AttributedStringの範囲を定義して 色を付けたいとします このレインボーエフェクトには プレーン ファン エクストリームの 3つのレベルがあります その値を表すためにenumを使用し 名前をrainbowに設定します タイプと名前を定義することが このプロトコルの唯一の要件です この属性をCodableにしてエンコードされた AttributedStringの一部にします Codable適合性を追加するだけです 最後にレインボーレベルをローカライズされた 文字列の一部にします 文字列のどこにあってもどんな言語でも 正しい部分に適用できるのです オプトインするためのプロトコル適合性が必要です 属性がMarkdownデコード可能であると言うことは Markdownから直接デコードして AttributedStringに挿入できます その値がCodableであることだけが必要です 次にMarkdownのカスタム属性構文を 見てみましょう 最初の例ではリンクへの参照があります リンクテキストを角括弧で囲み リンク先のURLを括弧で囲みます 2つ目の例では画像への参照があります 感嘆符で始まり 角括弧で画像の説明を 括弧で画像のソースを表しています この最初の2つの例はMarkdownでは一般的です 3つ目の例ではカスタム属性の 構文を示しています キャレットで始まり 角括弧でテキストを括弧で属性を表します 属性はJSON 5で表されます JSON 5はJSONとの互換性があり 引用符のないキーやコメント その他の機能が使用できます このように人に易しい文字列との 相性が良いのです またFoundationのもう1つのJSON APIに JSON 5のサポートを追加しました カスタム属性はJSONを使用しているため JSONDecoderでデコードできるものは自動的に 新しいカスタムMarkdown構文に対応します ここでは単一の属性 文字列と数値の2つの属性 そして 複数のプロパティを持つ単一の属性があります ここではMarkdownの名前をどのように Swiftのタイプに接続するか という追加要素があります その部分をアトリビュートスコープといいます スコープは属性キーのグループです スコープはJSONやMarkdownから デコードする際にどの属性を見つけるのか その名前やデコード方法を 教えてくれるので便利です Foundation UIKit AppKit SwiftUIに それぞれスコープを定義します 独自のスコープを定義することもできます カフェAppのスコープを定義しましょう スコープをAttributeScopesに入れ子にして AttributeScopeプロトコルに適合させます あとはスコープ内の属性を letでリストアップするだけです ここまではRainbowAttributeだけです 次にSwiftUIのスコープを 私たちのスコープに入れます そうすると私たちの属性に加え それらすべての属性を許容します スコープは再帰的にネストするので Foundationの属性も含まれます 新しいスコープにプロパティ を定義するのに便利です スコープを引数とする関数で キーパス構文を使用できます 最後に ローカライズされたAttributedStringを カスタムMarkdownから読み込むことができます アーカイブやNSAttributedStringへの 変換などのスコープ機能も備えており すべてのステップで動作をカスタマイズできます カフェAppの最初の画面ではタイトルにカスタムの レインボー属性が表示されています ローカライズされた文字列がMarkdownから AttributedStringに変換された後 属性を見つけ文字列の範囲だけに効果を適用します
この属性はローカライズされた文字列ファイルから 来るので スペイン語などカフェがサポートする すべての言語で正しく動作します まだまだこれからですけどね 全く新しいFormatterAPIもあります Formatterも前からあるFoundationの機能です 数字や日付時刻などのデータを ローカライズされた文字列に 変換する役割を担っています Formatterはかなりの量の 設定データに支えられるので キャッシュして再利用する のはよくあるパターンです しかしAppはさまざまなコードで構成されています formatterをすべての機種で共有することが 意味があるとは限りません また 日付や時刻の読み方は人によって様々で Appの作者としては デザインに合った方法でデータを表示したいので エッジケースが多く発生します 今年はFormatter APIを見直し パフォーマンスと ユーザビリティを向上しました つまり新しいAPIはフォーマット重視です Earthquakesのコードを見てみましょう キャッシュパターンの動作が確認できます 2段階のプロセスがあります まずformatterの作成と設定を行います 次にformatterに日付を加え文字列を取得します 簡単でしょう? まず独自の日付formatterを 作成する必要性をなくします これがキャッシュされる必要だと忘れがちで テーブルのすべてのセルに同じformatterを 作り直すことになっていました 続いて フォーマットのステップです 日付をformatterに渡すのではなく 日付そのものを使ってみます もう たった1行のコードです 好みにフォーマットを指定して終わりです この数値のフォーマットについて詳しく説明します コードは多くありませんが複雑な部分があり 注意すべき落とし穴がいくつかあります 引数が浮動小数点数でない場合 ここでは全く間違った出力が得られます 浮動小数点数をフォーマット するための特殊な構文や 単なる文字列定数である 修飾子のセットに注意する必要があります このコードは 理解しやすくメンテナンスしやすく 読みやすいはずです これはSwiftの正規関数を使用して 数値のフォーマットを正確に指定します オートコンプリートやタイプセーフも実現します この新しいアプローチを Foundationの10種類のformatterに適用しました インターフェイスを整理してシンプルにし よくある落とし穴を回避するため変更を加え さらに多くの新機能を追加しました 代表的な書式である 日付と数値について 詳しく紹介します 日付のフォーマットとはカレンダーと タイムゾーンを使って時間のポイントを 人が理解できる日付に変換することです さらに“日付をどのように見せたいか” という人それぞれの好みも すべて考慮しています そのような環境をロケールと呼びます 日付をフォーマットするために必要な 短いコードを見てみましょう まずDate.nowを使って現在の時刻を取得します 次にフォーマットされた関数を呼び出します これだけです もちろん先ほどのように 日付の書式設定には選択肢が多くあります では これを少し広げてみます
フォーマット機能では 日付または時刻のみを表示する設定が可能です そのどちらも いくつかの選択肢から選べます この新しいフォーマットAPIの重要な目標は 正しいフォーマットを作成するために コンパイル時にできるだけ支援を提供することです マジックストリング値を使ったフォーマットは 通常の環境では正しく見えるのに 年末のようなエッジケースでは 全く間違った値を生成する という落とし穴があります ここにもデフォルトのフォーマットがあります これは日付と時刻のスタイルを求める 短いバージョンで次のようなものです 引数なし シンプルスタイルバージョンともに デフォルトのフォーマットを選んでいますが 日付をカスタマイズしたいのであれば 気になるフィールドを追加していくだけです この例では スタイルにフィールドを追加して フォーマットを構築しています 年月日だけにします 時 分 秒を 入れることもできます 出力形式はユーザーのロケールに合わせて 自動的に調整されます これらのフィールドも設定可能です この場合 月をワイドフォーマットに変更すると 月名が略されずに表示されます 日付の一部をフォーマットすることも簡単にできます ここでは平日のみを取得します 日付も様々なスタイルにフォーマットできます ここではISO8601形式とISO8601を選択していますが 年月日のみダッシュで区切っています これらの例で フォーマットのパターンが明確になります まずフォーマットしたい値を入力します format関数を呼び出し引数がそのスタイルです タイプごとに複数のスタイルがある場合もあります 例えばDateはdateTimeとISO8601の両方です このスタイルはデフォルト設定で 使用することもカスタマイズもできます このフォーマットAPIはフィールドのリストを 指定して動作しますが追加オプションもあります 提供するフィールドの順序は関係ありません 各フィールドは最終出力の どこかに含まれるべき値を formatterに伝えるだけです APIの最も短いバージョンつまり引数がないものや スタイル名だけのものはデフォルトを選択します そこにフィールドを追加していくと 出力がカスタマイズされ表示するものだけが 反映されるようになります
2つの日付を相対的にフォーマットするための 新しいAPIもあります こちらが例です
まず範囲内の2つの日付をフォーマットします 2つの日付では通常のSwiftの範囲構文を 使用することができます フォーマットの範囲では単一の日付同様 日付と時刻の表示を設定できます この範囲は 期間コンポーネントまたは今を 基準とした単一の日付としてフォーマットできます フォーマットのもう一つの新機能は 属性付き出力です これによりユーザーの好みに合わせ フォーマットされた値の特定の部分を再配置後に formatterがどこに置いたか見つけることができます AttributedStringを使用しています フォーマットされた出力のスタイリングの適用は よく見られます watchOSでは多くのコンプリケーションが フォーマットされた文字列です Apple Watchはパーソナルデバイスなので ユーザーの好みを考慮することは大切ですが 日付の一部をユーザーの好みの色にするなど ある種のデザイン言語を 適用したいところでもあります これをSwiftUIで設定するのは楽しいことです デモで見てみましょう 次の無料コーヒーの時間を表示するカフェAppの 開始点があります フォーマットされた日付を表示するだけの SwiftUIビューを使っています
SwiftUIのプレビューでコントロールできるように フォーマットにロケールを設定しています
なかなか良いですが もう少しカスタマイズしたいですね 私のAppに合わせてもう少し具体的に 説明しましょう 必要なのは 分 時間 そして平日だけです いいですねでは色を足しましょう リターンタイプをAttributedStringに 変更して属性付きの出力を求めます
次にAttributeContainerを使用します 文字列内の特定の文字に関連付けられていない 属性を保持することができます 日付フォーマットが出力する weekday属性用を作成します 曜日を含む文字列の範囲に 設定されます
次に 設定したいカラー属性のコンテナを作ります
最後にAttributedString関数を使って 1つ目のコンテナにある属性と一致する属性を 2つ目のコンテナにある値で AttributedStringに置き換えます
AttributedStringは値型であるため mutating functionを置き換えるのでletをvarに
いいですね ロケールすべてに適用されます プレビューに少し追加してダブルチェックします
これらのロケールの フォーマットされた日付のどこにあっても 平日がオレンジ色です さらにformatter APIについて学んでいきましょう 日付を文字列に変換しましたが 今度は文字列を日付に変換する方法を説明します 日付がstrategy引数を取る イニシャライザを持ちます strategyはパーサーに入力でどのフィールドを 期待するかを伝えるために使われます 日付の場合 フォーマットは一種のstrategyです これは出力を表示と同時に 新しい日付を入力できる テキストフィールドのように 日付をラウンドトリップさせる場合に便利です ラウンドトリップの例です パースが投げられることわかります 入力によってはパースが 失敗することがあるからです strategyによっては高度な パースオプションがあります ここでは固定フォーマットを解析しています これは日付フォーマットがサーバーから 受信したものである場合に便利です フォーマット文字列でstrategyを初期化します マジックナンバーを使うのではなく 文字列補間を使います ここでは 年月日形式の文字列を想定しています 各補間はフィールドごとに明確に識別されており どんなフォーマットを期待するか示しています ここでとてもいいのが オートコンプリートです 別の日のフォーマットを使用したい場合は オートコンプリートがオプションを表示し それぞれの意味を説明してくれます 年号のパースに何個のYを使うか 考える必要はありません 続いて数値です 数値フォーマットは整数や浮動小数点の値を 人が読めるように変換します 他のフォーマット同様 数字の表示方法の好みが考慮されます これには使用する数字の種類や 数字をグループ化する文字などが含まれます 日付のフォーマット同様追加パラメータは不要で 出力を簡単に得られます
オプションやアウトプット には様々な種類があります ここではパーセンテージ科学的記数法 および通貨を表示します 最後にフォーマットをまとめてみましょう リストの書式設定が単なる 配列の書式設定になりました このメンバースタイル引数は配列の各要素の フォーマットスタイルを指定します これは数字なのでパーセンテージで表します どのユーザーのロケールも正しい出力が得られます 値を直接フォーマットする ことに重点を置いてきました TextFieldにフォーマットスタイルもサポートします フォーマットスタイルは値の種類のtype情報を 持っているので ここでは レシートのチップを読み易く かつ安全な構文で表現することができます カフェAppを見てフォーマットが 表示される場所の数を確認してみましょう リスト形式で表記しています 価格には通貨形式を採用しています 数量は数値フォーマットを使用し 注文ボタンの カウントをローカライズ するためにも使用しています ここで忘れてはならないのが いつも隅にある日付のフォーマットです フォーマットされた出力はどこにでもありますが この新しいAPIを使えば簡単に楽しく使えます ローカライズされた文字列やformatterについては 他にもたくさんのリソースがあります “Localize yourSwiftUI App”と “Streamline yourlocalized strings”も このテーマですのでご覧ください 次にオート・グラマー・アグリーメントについて 説明します スペイン語などにローカライズする場合 自然な翻訳には限界があり ぎこちない文章になることもありました これらの言語では 異なる品詞間での性や複数形の一致を 実現するための変換が必要で ユーザーが好む呼びかけ語を 知る必要もあります 英語にもこの機能があり 単数形と複数形の形が異なる名詞を識別します 多くの言語専門用語を投げかけたので 例を見てみましょう カフェAppではフード サイズ 数量を 選ぶことができます スモールサイズのサラダを1つ選びました
友人も一緒に食べることになり 追加して2つにします 2つになったので“salad”が複数形になりました これがアグリーメントです つまり文章内の単語がお互いに マッチしていなければならないということです 英語では複数化による単語の修正は よくあるアグリーメントの1つです ではAppをスペイン語に切り替えます スモールサイズのサラダを1つ選びます
友人分を追加すると 英語では名詞が複数形ですが スペイン語の場合は形容詞と名詞両方が 複数形にならなければなりません そのため“ensaladapequeña”ではなく “ensaladas pequeñas”になります 次はドリンクです ここでは複数形だけでなく 文法上の性も 修正する必要があります “jugo”(ジュース)は男性名詞です 形容詞の修正も必要です このようにテキストを正しくローカライズする 組み合わせでつまづきます フード サイズ 数量それぞれ組み合わせごとに 異なる文字列が必要になります コードではこのようになりがちです アイテムごとに切り替え サイズごとに切り替えとなってしまいます またstringsdictファイルもあり 数に合わせて正しく複数形にしてくれます キーボードのサジェスト機能と同じ技術を利用して これらのケースに簡単に対応できる 新しいAPIを開発しました この機能がオート・グラマー・アグリーメントです ローカライズされた文字列を自動で修正し 正しい文法にしてくれます これでコードがシンプルになります 1つの文字列に数量 サイズフードをまとめられます オート・グラマー・アグリーメントは inflectionという処理で文字列を修正します 細かく見てみましょう どの文字列に修正が必要か 見つけます 運良くSwiftがそれをやってくれます MarkdownのAttributedStringとカスタム属性です この文字列にその構文を使いフード サイズ 数に inflect属性を適用します 値はtrueです このプロジェクトのローカリゼーションを エクスポートすると注釈付きの文字列と フードやサイズなどソースコード内の他の ローカライズを含む文字列ファイルが得られます 中南米のスペイン語の文字列です スペイン語では形容詞が名詞の前に来るので ローカリゼーションでは引数の並べ替え構文 %1 %3 %2が使用されました 文字列のこの領域をinflectionするための カスタム属性構文を保持し フードやサイズの翻訳を提供します あとは自動で 修正してくれます 言語によってはローカライズされた テキストの単語間だけでなく テキストと読み手との間にも一致が見られます この機能はそれにも役立ちます 例えば このNotesの ウェルカム画面を見てみましょう 英語では“Welcometo Notes”です スペイン語では“Te damosla bienvenida a Notas” となります スペイン語でも英語と同じ使い勝手に なるようにしたい しかしスペイン語の“bienvenido”は 読み手が好む呼びかけ語と一致しなくてはなりません この単語は選択肢の1つであり テキストに影響します 正しい呼びかけ語を使うことで よりユーザーに寄り添うものとなります 今年のリリースでスペイン語を使う方は 呼びかけ語を指定できるようになりました 言語と地域の設定で 新しく呼びかけ語のオプションがあります そのオプションから好みの呼びかけ語を選ぶと すべてのAppで使えるようになります 女性で設定すると このような画面になります こちらは男性の場合です 特に指定がない場合は オリジナルの文字列が使用されます 先ほどと同じinflection属性が ローカライズされた文字列にも使われています 今回の“welcome to”では“bienvenido”という 単語にinflection属性を適用します 英語の文字列を変更する必要はありません inflection属性も追加できます ユーザーの好みに関する情報がない場合に エンジンが使用する代替文字列です 今年はスペイン語と英語の オート・アグリーメントに 対応しています Notesのウェルカム画面などOS全体で 採用しています Appにも適用できます 必要なコードの変更は異なる文字列を選ぶため 多くのロジックを削除するだけです inflectionの指示はローカライズされた 文字列に含まれるためその言語での文字列の 表示方法をより細かくコントロールできます カフェAppでのオート・グラマー・アグリーメントの 動作を見てみましょう カフェAppを英語で起動します ピザを注文します ラージサイズを1つ注文しましょう ボタンのテキストが“0 large pizzas”から “1 large pizza”に変わりました 自動で修正されました 2にするとまた修正されます 1つにしておきます 画面の下のボタンに 注文数が反映されます ドリンクも注文してみましょう スモールを1つ注文します “item”が“items”に 変わりました 文字列が自動で修正されました 精算します
レシートです 注文内容と合計金額が表示されます 下部にはAttributedStringで カスタマイズしたメッセージが表示されます Xcodeに戻りソースを見てみましょう 最初にフードのviewです
これはサイズを選ぶviewです サイズを追加してみます フードの種類 サイズ 数に応じた文字列を追加せず スペイン語に合わせてローカライズされた文字列を 1つ追加するだけで済むことがわかります この行はリストです モデルオブジェクトからきています 見てみましょう
スモールとラージサイズがあるので お腹ペコペコな人のために “huge”(特大)を追加しましょう ローカライズの文字列も併せ caseを追加します
あとは値段を追加するだけです 今回のデモではイニシャライザに入れました
viewをもう一度見てみましょう
これがプレビューです 英語の文字列はありますが スペイン語も必要です 新しい文字列の生成はコンパイラを使い “huge”のローカライズされた文字列を見つけます ProductからExportLocalizationsを選び スペイン語の文字列を保存します
ではスペイン語訳を追加しましょう
新しい文字列を検索し スペイン語訳を入力します
そしてAppに インポートします
スペイン語でAppを起動してみましょう ProductからSchemeそしてEdit Schemeを選択 Optionsでテストしたい言語を 選択し
そして起動します ヘッダーで スペイン語だとわかりますね 注文してみましょうまずはサラダから 数を変えるとボタンも変わりますね 特大サイズも正しく複数形になりました “ensalada”の文法上の性にも対応しており 1つの文字列でカバーしています 今年 Foundationには 多くの優れた機能が加わり 今日にでも皆さんのAppで利用できます AttributedStringは文字列の範囲に キーと値のペアを追加するための高速で使いやすい Swift初のインターフェースを提供します テキストでSwiftUIと一緒に使うことができ ローカライズされた文字列でMarkdownを使えます 新しいFormatter APIフォーマットに重点を置き コードを簡素化しパフォーマンスを上げました App内のデータすべてで使用できるフォーマットです 最後はオート・グラマー・アグリーメントです ローカライズされた文字列を知性的に修正し 文法上の性 数呼びかけ語に 一致するようにします 皆さんに気に入っていただければ幸いです 皆さんのAppで確認できる日を楽しみにしています ありがとうございました [音楽]
-
-
2:50 - Attributed String Basics
func attributedStringBasics(important: Bool) { var thanks = AttributedString("Thank you!") thanks.font = .body.bold() var website = AttributedString("Please visit our website.") website.font = .body.italic() website.link = URL(string: "http://www.example.com") var container = AttributeContainer() if important { container.foregroundColor = .red container.underlineColor = .primary } else { container.foregroundColor = .primary } thanks.mergeAttributes(container) website.mergeAttributes(container) print(thanks) print(website) }
-
4:24 - Attributed String Characters
func attributedStringCharacters() { var message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let characterView = message.characters for i in characterView.indices where characterView[i].isPunctuation { message[i..<characterView.index(after: i)].foregroundColor = .orange } print(message) }
-
5:12 - Attributed String Runs (Part 1)
func attributedStringRuns() { let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let runCount = message.runs.count // runCount is 4 print(runCount) let firstRun = message.runs.first! let firstString = String(message.characters[firstRun.range]) // firstString is "Thank you!" print(firstString) }
-
5:49 - Attributed String Runs (Part 2)
func attributedStringRuns2() { let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let linkRunCount = message.runs[\.link].count // linkRunCount is 3 print(linkRunCount) var insecureLinks: [URL] = [] for (value, range) in message.runs[\.link] { if let v = value, v.scheme != "https" { insecureLinks.append(v) } } // insecureLinks is [http://www.example.com] print(insecureLinks) }
-
6:36 - Attributed String Mutation
func attributedStringMutation() { var message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") if let range = message.range(of: "visit") { message[range].font = .body.italic().bold() message.characters.replaceSubrange(range, with: "surf") } print(message) }
-
7:29 - Localized Strings
func prompt(for document: String) -> String { String(localized: "Would you like to save the document “\(document)”?") } func attributedPrompt(for document: String) -> AttributedString { AttributedString(localized: "Would you like to save the document “\(document)”?") }
-
9:34 - Codable Attributed Strings
struct FoodItem: Codable { // Placeholder type to demonstrate concept var name: String } struct Receipt: Codable { var items: [FoodItem] var thankYouMessage: AttributedString } func codableBasics() { let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._") let receipt = Receipt(items: [FoodItem(name: "Juice")], thankYouMessage: message) let encoded = try! JSONEncoder().encode(receipt) let decodedReceipt = try! JSONDecoder().decode(Receipt.self, from: encoded) print("\(decodedReceipt.thankYouMessage)") }
-
10:42 - Markdown Decodable Attribute
enum RainbowAttribute : CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { enum Value : String, Codable { case plain case fun case extreme } public static var name = "rainbow" }
-
11:30 - Custom Markdown Syntax
This text contains [a link](http://www.example.com). This text contains ![an image](http://www.example.com/my_image.gif). This text contains ^[an attribute](rainbow: 'extreme').
-
12:27 - Custom Markdown Attributes
This text contains ^[an attribute](rainbow: 'extreme'). This text contains ^[two attributes](rainbow: 'extreme', otherValue: 42). This text contains ^[an attribute with 2 properties](someStuff: {key: true, key2: false}).
-
13:15 - Attribute Scopes
extension AttributeScopes { struct CaffeAppAttributes : AttributeScope { let rainbow: RainbowAttribute let swiftUI: SwiftUIAttributes } var caffeApp: CaffeAppAttributes.Type { CaffeAppAttributes.self } } func customAttributesFromMarkdown() { let header = AttributedString(localized: "^[Fast & Delicious](rainbow: 'extreme') Food", including: \.caffeApp) print(header) }
-
17:28 - Formatting Dates
func formattingDates() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let date = Date.now let formatted = date.formatted() // example: "6/7/2021, 9:42 AM" print(formatted) let onlyDate = date.formatted(date: .numeric, time: .omitted) // example: "6/7/2021" print(onlyDate) let onlyTime = date.formatted(date: .omitted, time: .shortened) // example: "9:42 AM" print(onlyTime) }
-
18:16 - Formatting Dates With Styles
func formattingDatesWithStyles() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let date = Date.now let formatted = date.formatted(.dateTime) // example: "6/7/2021, 9:42 AM" print(formatted) }
-
18:36 - Formatting Dates - More Examples
func formattingDatesMoreExamples() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let date = Date.now let formatted = date.formatted(.dateTime.year().day().month()) // example: "Jun 7, 2021" print(formatted) let formattedWide = date.formatted(.dateTime.year().day().month(.wide)) // example: "June 7, 2021" print(formattedWide) let formattedWeekday = date.formatted(.dateTime.weekday(.wide)) // example: "Monday" print(formattedWeekday) let logFormat = date.formatted(.iso8601) // example: "20210607T164200Z" print(logFormat) let fileNameFormat = date.formatted(.iso8601.year().month().day().dateSeparator(.dash)) // example: "2021-06-07" print(fileNameFormat) }
-
20:30 - Formatting Intervals
func formattingIntervals() { // Note: This will use your current date & time plus current locale. Example output is for en_US locale. let now = Date.now // Note on time calculations: This represents the absolute point in time 5000 seconds from now. For calculations that are in terms of hours, days, etc., please use Calendar API. let later = now + TimeInterval(5000) let range = (now..<later).formatted() // example: "6/7/21, 9:42 – 11:05 AM" print(range) let noDate = (now..<later).formatted(date: .omitted, time: .complete) // example: "9:42:00 AM PDT – 11:05:20 AM PDT" print(noDate) let timeDuration = (now..<later).formatted(.timeDuration) // example: "1:23:20" print(timeDuration) let components = (now..<later).formatted(.components(style: .wide)) // example: "1 hour, 23 minutes, 20 seconds" print(components) let relative = later.formatted(.relative(presentation: .named, unitsStyle: .wide)) // example: "in 1 hour" print(relative) }
-
21:39 - Demo - SwiftUI and AttributedString
import SwiftUI struct ContentView: View { @State var date = Date.now @Environment(\.locale) var locale var dateString : AttributedString { var str = date.formatted(.dateTime .minute() .hour() .weekday() .locale(locale) .attributed) let weekday = AttributeContainer .dateField(.weekday) let color = AttributeContainer .foregroundColor(.orange) str.replaceAttributes(weekday, with: color) return str } var body: some View { VStack { Text("Next free coffee") Text(dateString).font(.title2) } .multilineTextAlignment(.center) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environment(\.locale, Locale(identifier: "en_US")) ContentView() .environment(\.locale, Locale(identifier: "he_IL")) ContentView() .environment(\.locale, Locale(identifier: "es_ES")) } }
-
23:53 - Parsing Dates
func parsingDates() { let date = Date.now let format = Date.FormatStyle().year().day().month() let formatted = date.formatted(format) // example: "Jun 7, 2021" print(formatted) if let date = try? Date(formatted, strategy: format) { // example: 2021-06-07 07:00:00 +0000 print(date) } }
-
24:23 - Parsing Dates - Strategies
func parsingDatesStrategies() { let strategy = Date.ParseStrategy( format: "\(year: .defaultDigits)-\(month: .twoDigits)-\(day: .twoDigits)", timeZone: TimeZone.current) if let date = try? Date("2021-06-07", strategy: strategy) { // date is 2021-06-07 07:00:00 +0000 print(date) } }
-
25:30 - Formatting Numbers
func formattingNumbers() { // Note: This will use your current locale. Example output is for en_US locale. let value = 12345 let formatted = value.formatted() // formatted is "12,345" print(formatted) }
-
25:36 - Formatting Numbers With Styles
func formattingNumbersWithStyles() { // Note: This will use your current locale. Example output is for en_US locale. let percent = 25 let percentFormatted = percent.formatted(.percent) // percentFormatted is "25%" print(percentFormatted) let scientific = 42e9 let scientificFormatted = scientific.formatted(.number.notation(.scientific)) // scientificFormatted is "4.2E10" print(scientificFormatted) let price = 29 let priceFormatted = price.formatted(.currency(code: "usd")) // priceFormatted is "$29.00" print(priceFormatted) }
-
25:47 - Formatting Lists
func formattingLists() { // Note: This will use your current locale. Example output is for en_US locale. let list = [25, 50, 75].formatted(.list(memberStyle: .percent, type: .or)) // list is "25%, 50%, or 75%" print(list) }
-
26:05 - Receipt Tip View
struct ReceiptTipView: View { @State var tip = 0.15 var body: some View { HStack { Text("Tip") Spacer() TextField("Amount", value: $tip, format: .percent) } } }
-
29:41 - Automatic Grammar Agreement
func addToOrderEnglish() { // Note: This will use your current locale. Example output is for en_US locale. let quantity = 2 let size = "large" let food = "salad" let message = AttributedString(localized: "Add ^[\(quantity) \(size) \(food)](inflect: true) to your order") print(message) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。