見出し画像

Foundationを便利に使おう

Foundationフレームワークは地味ですが重要です。

この記事ではWWDC2021の『SwiftuUIの新機能』の一部と『Foundationの新機能』でとりあげられた属性付き文字列や書式(フォーマット処理)関連新機能の基本的な使い方を中心に解説します。
iOS 15 や macOS 12 で利用が可能になっていますが、まだドキュメントが少ないうえに SwiftUI などにも分散していて全体像がつかみにく状態です。
ぜひこの記事を参考にしてください。
Xcode 13 の Playground でサンプルコードを実行し確認していきましょう。

iOS 15以降で利用可能な新機能を取り上げます。
このためXcode のバージョンは13以降が必要です。

  • 画像クリックで拡大表示できます

  • 画像を拡大表示中は画像の左右をクリックで画像だけを順に表示できます

  • ソースコード部分は左右にスクロールできます

画面は Xcode 13.1 を使った実行結果です。
サンプルはすべて Xcode の iOS用プレイグラウンド書類用コードです。
iPad や Mac の Playgrounds 3.4.1 アプリでは実行できません
この記事の最後の有料部分にあるリンクから完全なサンプルをダウンロードできます。


🟧1  Foundationフレームワークとは

”foundation”は一般的な名詞で土台や基礎のことです、コンピューター用語ではありません。

こちらではありません😉。

Safari(バージョン15.1)の翻訳機能では「財団」などと表示されることがありました。

ドキュメント検索での絞り込みでは
AVFoundationCore FoundationSecurity Foundation とともに表示されます。

画像1
デベロッパーサイト Documentation の Technologies 画面

ドキュメントは Foundation です。

なお Core Foundation は Foundation よりも深くハードウェアに近い低レベルな機能を提供します。

Foundation フレームワークのドキュメントは Fundamentals 、App Support、Files and Data Persistence、Networking、Low-Level Utilities に分類されていてたくさんあります。

Foundationページの概要(Overview)には
『Foundationフレームワークは、データストレージと永続性、テキスト処理、日付と時刻の計算、ソートとフィルタリング、ネットワーキングなど、アプリやフレームワークの機能の基本層を提供します。Foundationによって定義されたクラス、プロトコル、およびデータ型は、macOS、iOS、watchOS、tvOS SDK全体で使用されます。』
とあります。

Safariの翻訳機能を利用

全OSで初期のバージョンから利用可能で、ほかのフレームワークも内部で利用しています。

Intなどの数値関連の型は Swift の場合は Swift Standard Library が提供しています。
ちなみに NSInteger は Objective-C Runtime フレームワークです。

基本的ではありますが、少しずつ強化されていてWWDC2021の発表は特にSwiftでの利用がかなり便利になり魅力的なものです。
Foundation の機能はたくさんあります、この記事では『Foundationの新機能』セッションで取り上げられた範囲を紹介します。

通常(SwiftUIやUIKitなど)どれかのフレームワークを import すると Foundation もインポートされるためコードで Foundation のインポートを明示する必要はありません。

どのフレームワークもimportしない場合(単独で動作確認する場合)だけ

import Foundation

が必要です。




🟧2 AttributedStringの概要

AttributedString は値型で再実装された新しい属性付き文字列です。
AttributedStringProtocol に準拠しています。
iOS 15.0以降、macOS 12.0以降で利用できる新しい型です。
表示メソッドなどに AttributedString を利用できるメソッドなども追加されました。

属性
太字や文字色それにリンク情報などが属性です。
AttributedString では文字単位で範囲を指定して属性を追加できます。

フォント 太字 斜体 文字色 リンク など
ドキュメントをみるとほかにもたくさん属性があります。
独自の属性も設定可能ですが、この記事では取り上げません。

AttributedString に対応した描画メソッドでは属性に対応した表示になります。
SwiftUI の Text型など AttributedString を引数に持つメソッドが追加されました。
追加されたメソッドもiOS 15.0以降、macOS 12.0以降で利用可能です。


🟠2.1 マークダウン

AttributedString の作成は文字列の範囲を決めて属性を指定する作業が必要になります。
コードでは冗長ですが、一部の属性であればリテラルで次のように指定することができます。

let markdown = AttributedString(localized:"**札幌***Swift*`01`~23~")

"**札幌***Swift*`01`~23~" これがマークダウンです。

WWDCセッションで示された例

"**Hello**, World!" // Hellowだけ太字にする
"[WWDC](https://developer.apple.com/wwdc21/)" // WWDCにリンクをつける

サンプルの実行画面です。(SwiftUIのText型を利用)

画像4
Xcode 13.1 の Playgroud画面


2.1.1 代表的なマークダウン書式

SwiftUIのText型イニシャライザーは直接マークダウンで属性を指定可能になりました。
次のような書式が使えます。

**札幌Swift** //太字にする(強調)
*札幌Swift* //斜体【日本語など斜体フォントがない場合は効果なし】
~~札幌Swift~~ //取り消し線
***札幌Swift*** //太字で斜体
`Swift0123` //等幅(コード)
[SwiftUI](https://developer.apple.com/documentation/swiftui) //[表示する文字](リンクURL)

サンプルです。
【サンプル 04markdown】

import SwiftUI
import PlaygroundSupport

func markdownStr() -> AttributedString {
   let markdown = AttributedString(localized:"""
       **マークダウンサンプル**
       札幌Swift0123
       `札幌Swift0123`
       **札幌Swift0123**
       *札幌Swift0123*
       ***札幌Swift0123***
       ~~札幌Swift0123~~
       **札幌***Swift*`01`~23~
       """)
   print(markdown)
   return markdown
}

struct ContentView: View {
   var body: some View {
       Text(markdownStr())
   }
}

// playgroundで実行する場合に必要なコード
PlaygroundPage.current.setLiveView(
   ContentView()
      .frame(width: 300, height: 300) // XcodeのPlaygroundで必要
)

Xcode での実行画面です:

画像5
Xcode 13.1 の Playgroud画面

日本語部分は斜体にはなりません。


2.1.2 マークダウンの特徴

マークダウンはコード内にリテラル文字列で直接指定でき便利です。
マークダウンもローカライズ可能となりその点も強力です。
アプリに不可欠なローカライズですが、属性なしのStringと同様のローカライズ対応が Xcode 13 を含め対応しました。


2.1.3 対応範囲

『改行、ソフトブレーク、段落またはブロックベースの書式設定(リスト、ブロック引用符、コードブロック、テーブルなど)はサポートしていません。また、imageURL属性もサポートしていません。』
の注釈がありました。
すべてのマークダウン書式に対応しているわけではありません。




🟧3 フォーマット処理概要

日時や数それにリストなどのフォーマット表示機能が拡張され Swift らしい記述に対応しました。

フォーマット処理が必要な日付表示の言語による違いの例:

// 例えば日付の表示は英語なら
Nov 18, 2021
November 18, 2021
// ドイツ語なら
18. Nov. 2021
18. November 2021
// 日本語では
20211118

など言語によりさまざまな記述が必要です。
この切り替えを実行環境の言語設定により自動で行う機能がフォーマット処理です。


🟠3.1 日時データの表示とフォーマット処理

日時データはいろいろな表記があります。
アプリでも多くの表示パターンが必要となります。
さらに多言語対応する場合は言語ごとに表示が変わります。

面倒ですが重要なフォーマット処理はFoundationにいろいろな機能が既にそろっています。
Xcode 13 からはSwiftからも利用しやすいメソッドなどが追加されました。

フォーマット処理を使うことで実行時の言語環境の設定により次のように表示が自動的に変わります

【日本語環境で実行】
2021/11/19 10:23 	formatted
2021/11/19 			onlyDate
20211119日 			.abbreviated
20211119日 金曜日 	.complete
20211119日	 	    .long
2021/11/19  			.numeric
【英語環境で実行】
2021/11/19 10:27 	formatted
2021/11/19 			onlyDate
Nov 19, 2021 			  .abbreviated
Friday, November 19, 2021 .complete
November 19, 2021	 	.long
2021/11/19  			.numeric

言語により影響を受けない場合もありますが、(ドイツ語のように)他の言語や表記指定では月と日の表示順も変わるものもあります。


🟠3.2 言語と地域の設定

言語と地域の設定対応はワールドワイドで日常的に利用されるアプリには不可欠の機能です。
利用者は「設定」アプリの 一般 > 言語と地域 でデバイスの言語と地域を設定します。
この「地域に応じた書式の例」に対応させるのがフォーマット処理の役目です。

サンプル11
画面は iOS 15.0 シミュレーター
ドイツ
画面は iOS 15.0 シミュレーター

このように同じ英語でもUS(アメリカ)とUK(イギリス)では月日の順序が逆になります。
英語以外では曜日や月も変わります。
そのほか通貨記号や数値の位取りと小数点も違ってきます。

たとえばドイツに変更すると数値表示の位取りがピリオドで小数点がカンマになります。
通貨記号は金額の後ろになります。

アプリはシステムの設定状態を反映した表示をしなければなりません。
Foundationのフォーマット処理を使うと地域や言語の対応はすべて(Foundationに)まかせることができます。




🟧4 AttributedString詳細

AttributedString は NSAttributedString や NSMutableAttributedString にかわる属性付き文字列です。
AttributedString は Swift専用で値型ですが NSAttributedString は Objective-C で使われ Swift でも利用可能な class です。

WWDCセッションで AttributedString の魅力をアピールしていましたが、公式ドキュメントは具体例がまだ少なく、詳細をたどっていくとドキュメントがすべてはそろってはいないことがわかります。
説明文のないページも多数ありました。

NSAttributedString は変更可能な NSMutableAttributedString が別の型であるなど古い設計でSwiftでは使いにくものでした。


4.0.1 AttributedString の ビュー

AttributedStringの用語のひとつに「ビュー」があります。
スペルはviewで画面に表示するためのView型と同じで紛らわしいですが、別のものです。
ここではカタカナで「ビュー」と書きます。

ちなみに String型にもビュー(unicodeScalars、utf16、utf8)があります。

AttributedString型には三つのビューがありインスタンスプロパティで参照できます。

A) characters
属性を設定できる一つの文字が並んでいます。

B) runs
同じ属性を持つAttributedStringが並んでいます。

AttributedString型インスタンスをprint文で出力するとrunに分解し属性も出力します。
例:

let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._")
print(message)

実行すると次のように出力されます。

Thank you!  {
	NSLanguage = en
}
Please visit our  {
	NSLanguage = en
	NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 1)
}
website {
	NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 1)
	NSLink = http://www.example.com
	NSLanguage = en
}
. {
	NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 1)
	NSLanguage = en
}

C) unicodeScalars
このビューを使うと AttributedString の文字列を String と同様に処理できます。
characters ビューでは一つの文字に表示されても unicodescalarsビューでは複数の組み合わせの場合があります。(絵文字の一部、仮名文字と濁点、欧文文字とアクセント記号など)

【サンプル 03ビュー】

import Foundation

// AttributedStringのcharactersビューとunicodeScalarsビュー
let messageEmoji = AttributedString(localized: "**あ**漢*S*👨🏽‍🍳🇯🇵")
print("🟩AttributedStringのprint出力")
print(messageEmoji)

// 属性なしのString型インスタンスに変換するにはcharactersビューを使います
let str = String(messageEmoji.characters)
print("\n🟩属性なしStringのprint出力")
print(str)
print("str.count=\(str.count)")
print("\n🟩characters.count=\(messageEmoji.characters.count)")

print("🟩charactersビュー")
for c in messageEmoji.characters {
   print("  \(c)")
}

print("\n🟩unicodeScalarsビュー")
for u in messageEmoji.unicodeScalars {
   print("  \(u) \(String(format: "%04X", u.value))")
}

このコードは Debug area に結果を表示します。

🟩AttributedStringprint出力
あ {
	NSLanguage = en
	NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 2)
}
漢 {
	NSLanguage = en
}
S {
	NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 1)
	NSLanguage = en
}
👨🏽‍🍳🇯🇵 {
	NSLanguage = en
}

🟩属性なしStringprint出力
あ漢S👨🏽‍🍳🇯🇵
str.count=5

🟩characters.count=5
🟩charactersビュー
 あ
 漢
 S
 👨🏽‍🍳
 🇯🇵

🟩unicodeScalarsビュー
 あ 3042
 漢 6F22
 S 0053
 👨 1F468
 🏽 1F3FD
 ‍ 200D
 🍳 1F373
 🇯 1F1EF
 🇵 1F1F5

絵文字「👨🏽‍🍳」は1文字ですがunicodeScalarsビューで見ると4つの文字を組み合わせていることが確認できます。


4.0.2 プロパティについて

詳しくはFoundationフレームワークのAttributedStringのドキュメントを参照してください。

ただし「font や link」など AttributedString のドキュメントに載っていないプロパティもあります。

font プロパティは SwiftUIAttributes のため Foundation のドキュメントには載っていないようです。【不便です】

linkプロパティは FoundationAttributes ですが Foundation のドキュメントには載っていません。【不便です】

【サンプル02】(WWDC「Foundationの新機能」2:50のコードをもとにしています)

import SwiftUI
import PlaygroundSupport

struct ContentView: View {

   var body: some View {
       VStack {
           Text(attributedStringBasics(important:false))
           Text(attributedStringBasics(important:true))
       }
       .font(.title)
   }
}

//2:50
func attributedStringBasics(important: Bool) -> AttributedString {
   var thanks = AttributedString("Thank you!")
   thanks.font = .body.bold()

   var website = AttributedString("Please visit our website.")
   website.font = .body.italic()
   website.link = URL(string: "https://developer.apple.com/jp/swift/")

   var container = AttributeContainer()
   if important {
       container.foregroundColor = .red
   }

   thanks.mergeAttributes(container)
   website.mergeAttributes(container)

   return thanks + " " + website
}

// playgroundで実行する場合に必要なコード
PlaygroundPage.current.setLiveView(
   ContentView()
      .frame(width: 300, height: 400) // XcodeのPlaygroundで必要
)

実行画面:

画像6
Xcode 13.1 の Playgroud画面

リンク部分は自動で青文字で表示しますが、全体に container.foregroundColor = .red で赤文字を設定するとそちらが優先となります。

Xcode の Playground ではリンク部分はクリックしても反応しませんが、アプリでは Safari に切り替わり表示します。
文字色を赤にしてもリンクは有効です【リンクがわからないのでリンク部分の文字色の変更はするべきではありません】。


🟠4.1 AttributedString型の主なイニシャライザー

init(_:attributes:)
文字列と属性コンテナから AttributedString インスタンスを作成します。

init(_ string: String,
 attributes: AttributeContainer = .init())

パラメータ
string
属性を追加する文字列。

attributes
stringに適用する属性。省略可能


init(markdown:options:baseURL:)
提供されたオプションを使用して、Markdown形式の文字列から AttributedString インスタンスを作成します。

init(markdown: String,
 options: AttributedString.MarkdownParsingOptions = .init(), 
 baseURL: URL? = nil) throws

パラメータ
markdown
Markdownの書式を含む文字列。

options
イニシャライザーがMarkdown文字列の書式設定をどのように解釈するかに影響を与えるオプション。省略可能
このパラメータのデフォルトはオプションはありません。

baseURL
Markdown URLを解決する際に使用するベースURL。省略可能
イニシャライザーは、URLをこのURLからの相対的なものとして扱います。
この値がnilの場合、初期化子はURLを解決しません。
デフォルトはnil。


init(localized:options:table:bundle:locale:comment:)
★解説なし

init(localized key: String.LocalizationValue,
 options: AttributedString.FormattingOptions = [],
 table: String? = nil,
 bundle: Bundle? = nil,
 locale: Locale? = nil,
 comment: StaticString? = nil)

tableName:bundle:comment:引数部分は String型の init(_:tableName:bundle:comment:) と同じと思われます。

key引数にはリテラルでマークダウンで属性を書くことができます。

★ほかにカスタム属性用のイニシャライザがありますがこの記事では触れません。


🟠4.2 属性の指定

AttributedString APIは、テキストのスタイリング、日付や数字のようなformattableタイプの意味的なマークアップ、ハイパーリンクなど、一般的な用途のためのキーを定義しています。
これらは、Foundation、SwiftUI、UIKit、および AppKit のための属性を含むAttributeScopes 列挙で見つけることができます。

属性は文字列の範囲を指定して設定します。
コードで指定する場合、文字列の範囲指定は面倒です。

Foundation や SwiftUI などのシステムフレームワークは共通キーを定義し、カスタム拡張機能で独自のキーを定義できます。

ここから先は

25,446字 / 3画像 / 1ファイル
この記事のみ ¥ 500

今後も記事を増やすつもりです。 サポートしていただけると大変はげみになります。