Apple Engine

Apple, iPhone, iOS, その周辺のことについて

2020 年、AR、これから

AR とはとなんぞやと問われると困るけど、Augmented Reality の略なので拡張現実と呼ぶものらしい。
拡張現実と言われてもよくわからない感じはあり、現状では仮想空間にデジタルな処理をされた現実空間をミックスしたものとなっている。
個人的には、拡張された現実とは空間に何かが付加された現実だと思って、本来 AR の目指すべきところは仮想空間も含めて現実になる状態だと思っている。

 

拡張された現実と拡張された感覚

我々が拡張された現実を体験できるのは五感のおかげだ。
現実が拡張されるわけではなく感覚の拡張となるのだろう。
感覚の拡張と考えるのなら AR はスマートフォンやグラスだけではなく、AirPods Pro のような外周音を取り込みミックスする機器も立派な AR だと考える。
今後、触覚や匂い、味覚なども拡張された世界に必要なものだろう。

 

空間 と Space UI

自分が AR を語る際は、現実空間や仮想空間など空間という言葉をよく用いる。
空間は固定されたものが配置された何らかの体積のあるものと物理現象や動物など動きがあるものがあり時間が加わる事で初めて空間となる。
AR の UI ではよく言う Spatial (空間的な) というより、自分はすべての Space が UI となると予想される。
ここでいう Space とはすべての空間という感じで、UI が現実と密接になり、すべての現実と仮想が溶け込んだ空間を触ったり、あたかもそこにある様な UI / UX を目指すべきだろう。

 

モード

現状、AR に関してはバッテリーを多量消費する問題があり長時間使用するようにはつくられていないない。
また、常時使用される時に想定されるのは、世界が仮想オブジェクトに溢れかえる可能性がある事だ。
特に空間内で他のユーザーとあるものを共有する場合、ユーザー数が多い場合は混沌となるはず。
そのため、AR の空間では使用する際に状態を変更する必要がある。
とりあえず、大まかに分けるとパブリックとプライベートだろう。
AR グラスなどでパブリックに仮想オブジェクトを配置されたものを投影し続けていたら 仮想オブジェクトによる画面(空間)汚染がされてしまう。個人や知人とのみ共有できるプライベートなモードは必要だろう。
端末のバッテリーと画面や空間の汚染を考えるとモードの切り替えや何らかの権限は必須になると予想される。

 

UI とコントロール

現状 AR の UI の解はないのでなんとも言えないが、わかっている事は空間上にある UI と手を動かすためのデバイスは長時間使用するには向いていない。
要するに今ある VR コンテンツでの UI を長時間使用するものに転用できる可能性は少ないということ。
ひとつはハンドコントローラーやハンドトラッキングは単純に長時間は腕が疲れる点。
それと空間を使用するとなるとユーザー操作からのフィードバックがスマートフォンより乏しくなる。

もうひとつは、前も書いたが AR は現実とリンクするため場所を超越することができない。そのため空間上にある UI を操作する場合疲れる可能性はある。
テレビの物理チャンネルからリモコン、有線のマウスから無線のマウスなど遠隔で操作する便利さを手に入れたのに、また不便な操作を強いる必要性はない。

静的な UI は環境に溶け込むべきだとは思うが、ユーザーインタラクションが行われるものに関してはもっとスマートなものが必要だと思われる。

 

AR グラスはスマートフォンを超えるのか

正直わからない。
PC / Mac からスマートフォンをへの流れはエンターテインメントの閲覧を含む情報取得の即時性が人々の利便性を上げたが、現状のままではスマートフォンの閲覧が AR グラスになるだけだ。
VR のヘッドセットをつけるのが面倒なように、このままでは大半の人は AR グラスではなくてもスマートフォンでも便利だろう。

 

まとめ

Kinect v1 を Mac で動かし、電話越しにクレジットカードの番号を言う謎作業からの Kinect v2 の開発者版を購入したり、HoloLens や他のものも試したり、空間でインタラクションを行うものはそこそこ昔からやっていた経験はある。

そのため、一応 AR に関して色々ネタを持っているが、一発芸的ビジュアルのものや軽いゲームのようなものでは一般層には届かない。

まだ、実用的になるまでは解決しなければいけない問題がまだ多すぎると考えている。

とりあえず、ここ数年で Apple や他の会社ではどのように AR を進化させるかみもの。

 

今更 AirPods Pro のレビュー

f:id:x67x6fx74x6f:20200120123224j:plain

ある程度 AirPods Pro を触ったので感想を書いてみる。

以前 AirPods の1世代目購入したが、外の音が入り込んでしまう設計だったため、都内では外音が煩すぎて電車や駅のホームなどでは使い物にならず、日本でフィールドテストしてから出すべきと何度も自分は言っていた。
1番近い最寄り駅が JR 秋葉原駅でホームで電車が来た際 AirPods では外の音が煩すぎて内容がかき消される状態だったが、Pro はカナル型になったためその点はかなり緩和された製品となった。

ただ、音をちゃんと聴きたいのであれば 27,800 (税別) で、消費税含めると3万超えるという価格を考えると有線のものを同価格で買った方が良いと思われる。

それでも AirPods Pro の利点があるとするなら、Apple 製品との連携の良さカナル型の分離型ワイアレス、外音が取り込めるところ。

この機能に対して価格が見合わないと思うなら買わない方が良い。

 

Apple 製品との連携の良さ

ペアリングが簡単かつ各デバイスのバッテリ残量を見ることができる便利さはある。

また、AirPods は iCloud との関連づけがされるため、一度そのアカウントに登録されれば他の Apple 製品での複数デバイスのペアリングに関しては何もしなくてもよい。

各デバイスで使用する時に接続を切り替えるだけ。

 

カナル型とノイズキャンセリング

以前は漏斗型で耳の穴に入り込むものでなかったため、外から流れる音がかなり入り込んでいたが、カナル型になり遮音性が上がった。
上がってはいるものの自分の耳のせいなのか、以前使用していたカナル型のイヤフォンより遮音性はない。
そのため、ノイズキャンセリングが付いてやっとという感じ。

ノイズキャンセリングは雑に説明するとマイクで外部の音を拾い反対の位相を流して音を聴こえなくする技術。
他の物の方が良いものもあるのであまり期待はしない方が良いかも。
とりあえず、電車の中で聞こえる走行音がかなり消えるので初めてノイズキャンセリングを使用する人は驚きはあると思う。

注意したいところがいくつかある。

ひとつは、ペアリング時のイヤーチップ装着状態テストを必ず行いカナルが密閉されているかを調べること。合わない場合はイヤーピースを付属の S や L へ替える。
これをやらないとノイズキャンセリングと自動で行われる音質調整が無駄になる。
設定アプリ > Bluetooth > AirPods Pro の情報アイコン からでもアクセス可能。

2つ目は耳の中の圧力を一定にするために内部に通気孔があり、何故か装着時、感覚的に違和感があったりする点とノイズキャンセリング時の逆位相独特の音。

これが辛いと思う方は使用が厳しいかもしれない。

 

外部音取り込み

このデバイスの特徴的な機能。

再生しているものを聴きつつ、クリアに外部音を取り込むことができる。
外部音をマイクで拾い主の音が聴きづらくなるため、ボリュームを上げる必要が出てくるので注意。

とりあえず、個人で試した感じではこれを使用しなくても日本ではノイズキャンセリングの状態で外部の音がかなり聞こえるから問題なく生活できると思われる。
基本的に近距離での音や高音や低音はノイズキャンセリングというかカナルイヤフォンでも音が聴こえるので。

それとデフォルトではうどん部分の圧力センサーの長押しでノイズキャンセリングと外部音取り込みを簡単に行き来できるので試してみると良いと思う。

 

音質と付け心地

音的には今までのようにフラット。 一応、耳の中の形状を調べ再生音の調整が行われるらしいが、単純に同価格の他の有線イヤフォンの方が音が良いと思われる。

個人的には中音域が良く、高音域そこそこ残念、低音域が残念と言う感じ。
スペック的には 20hz 〜 20khz との話なのでハイレゾと言われているイヤフォンと上下倍以上差があり致し方なしかなというところ。

付け心地としては自分は他のカナル型の方が耳に合う。
問題点とすればスピーカー部分が楕円になっているところと、今までのカナル型と比べてイヤーピースが若干硬いところ。

ただ本体は軽いので重さ的耳に負担はかかりづらい。
以前の AirPods は耳の形によっては落ちやすい人がいたので今回はかなり改善されたのではと思う。

 

電波干渉

昔の Bluetooth のイヤフォンレシーバーは人の多いところや交差点でのタクシーなど一部車両が通ると電波干渉が起き若干音が途切れたり止まったりしていた。AirPods Pro では起こりづらくなっている。

また、1世代目では付けたまま手で耳を押さえると電波が途絶されたが、AirPods Pro では起こらない。
外部の音がかなり消えるのでお勧めしないか イヤーマフのかけても使えそう。

現状、端末負荷や近くに高頻度で通信しているものがあると音の途切れはみられるので完全というわけにはいかなそう。

 

バッテリー

バッテリーは以前と同様。

1世代目と比べるとケースがワイヤレス充電できるようになって便利さが増した。

一応、ノイズキャンセリング等使用すると前より30分使用時間が減るが、ケース等に入れれば5分で1時間充電されるからあまり問題ないかと。

ケース含めると24時間使えるので、通勤と休日数時間使うという使用用途なら1週間に1回のフル充電十分に運用できる感じ。

 

落とすか否か

ちなみに使用した3日目取り出す時に L 側を落とし上部が若干陥没した。

本体がプラスチックで滑りやすいこと、以前より丸くなっていることと、ケースに対してうどん部分が斜めに入る設計なので出し入れは要注意。

あとケースを落とした際、運が悪いと地面にぶつかった衝撃で蓋が開き中身が出る場合があるらしい。

できるだけ室内や落ちてもリカバーできるところで装着した方が良さそう。

 

保証

イヤフォンでも Apple Care が付けられるようになったので常用するなら加入した方が良いかも、交換の場合はかなり安くなるので。

iPhone など物によって保証金額が異なるが 「AppleCare+ for ヘッドフォン」は Apple のヘッドホンなら 3,400 (税別) と一定なので高い AirPods Pro に関しては保証されるコスト的にお得になる。

保証内容は下記の URL 参照。 support.apple.com

 

まとめ

このところ自分はイヤフォンでは Podcast を聴くことがメインなので音質的にはこのレベルで十分。
音質的にも1万円以下のイヤフォンを使用しているならかなり良くなると思う。

Apple Watch, iPhone, iPod touch, iPad, Apple TV, Mac 全ての Apple プラットホームで使用でき、ノイズキャンセリング、外部音取り込み、完全ワイヤレスと考えると税込3万は妥当という感じではある。

あと、分離型ワイヤレスイヤフォンのシェアをほぼ持ってしまっているため、他のものよりケースカバーが多く出ることが予想される。

 

USDZ を手軽に作成できる Reality Converter Beta がリリース

obj、glTF、USD ファイルをドラッグ&ドロップで USDZ へ変換できる Reality Converter の Beta 版がリリースされた。
開発者アカウントが必要になるが以下の URL からダウンロード可能。

https://developer.apple.com/news/?id=01132020adeveloper.apple.com

 

開発者サイトの USDPython の中にある usdzconvert を使用しており UI をつけたものだと思われる。
Python を使用しているため、Mac 以外の Apple プラットフォームで動作させることはできないが、
今後 Reality Composer の同様に Xcode に付属すると予想。

Beta 版でスクリーンショットが取れないため News の文言をざっくり訳。

新しい Reality Converter アプリを使用すると、Mac で 3D オブジェクトを簡単に USDZ へ変換、表示、変更ができる。
.obj、.gltf、.usd などの一般的な 3D ファイルをドラッグ&ドロップするだけで、変換された USDZ を表示し、
マテリアルプロパティから自前のテクスチャへカスタマイズしたり、ファイルメタデータを編集したりすることが可能。
アプリの IBL オプションを使用すると環境マップのライトや背景の表示を USDZ オブジェクトと共にプレビューするができる。

 

ざっくり触ってみた感じ、環境テクスチャが 6つ でグリッドと背景表示の有無がある。
USDZ 仕様上マテリアルに説明できるテクスチャは以下のもの。

  • Base Color
  • Emissive
  • Metalic
  • Roughness
  • Normal
  • Occulusion
  • Opacity
  • Clearcoat
  • Clearcoat Roughness

 

設定ではコピーライトと出力する尺度が設定でき初期値は ARKit の基準となる1ユニット = 1メーター。

とりあえず、glTF を試してみたがテクスチャが組み込まれているものか、.glb となっているバイナリのみ読み込める。
以下の画像が読み込んだ結果。

 

まとめ

ファイルフォーマットと USDZ の制限に対応していれば簡単に変換できるし、
Share ボタンから AirDrop で iOS / iPadOS 端末に保存すると QuickLook でプレビューできたり、Reality Composer でコンテンツを即つくったりできるので便利。

 

今回、お借りした Model データはこちら。

sketchfab.com

スペックで見る Mac Pro

Mac Pro はプロ向けではあるものの、この価格を払ってでもスピードを上げたい人向けで、 コスパが良いかと言われると困る。
昔のグラフィックワークステーションで的なものなので個人で買うものではないと思われる。

 

CPU

Mac Pro は Cascade Lake ベース Xeon W 3200 シリーズ。
iMac Pro の Xeon W の大きな違いは DL Boost という機械学習向けの拡張機能が使用でき、
iMac や MacBook Pro 16 inch Core i9 との大きな違いは DL Boost に加え、AVX-512 が使用できる点である。

Mac Pro iMac Pro iMac i9 (2019)
AVX-512
DL Boost

 

AVX-512 は 512 bit 幅の SIMD (Single Instruction, Multiple Data) ユニット。
デスクトップ版 Core i9 で使用できるが、本来サーバー向けとして開発された Skylake-X にしか実装されてない。
元々、Intel Xeon Phi Processor という並列コンピューティング用の演算ボードのコプロセッサに導入されたもので、いままでの AVX とは異なり大きな変更があり、どちらかと言えば GPU のような感じ。
Xeon Phi の2世代目しか出ていない点を考えると推して知るべしというところ。
ちなみに、多分、今も AMD が 256、スマートフォンで使用している ARM は 128 だったはず。

Mac では Skylake-X が採用されていないため、AVX-512 を使用できるのは、現状 iMac Pro と Mac Pro だけだと思われる。

DL Boost は 推論の畳み込み積和算 を1命令で実行する VNNI (Vector Neural Network Instruction) と Google の TPU で使用されている bfloat16 という 32 Bit の浮動小数点の下位 16 Bit を切ることで精度は低くなるが、32 Bit で倍の詰め込めるものの総称でマーケティング的に DL Boost という名称を使っているらしい。

正直、これらの機能が現状で生かされるのかというと多分微妙であると思われる。

 

GPU

ベースモデルは 580X で微妙。
6K の Pro Display XDR では、解像度的にかなり描画処理に喰われそうではあるし。

Macお宝鑑定団 blog(羅針盤)で Compute ベンチがあり、現状 Vega II は Vega 64 よりベンチが遅く、この数値なら Vega II Duo * 2 は 64X の倍ぐらいしか出ない気はする。

それを考えるとそのうち出る 5700X は 64X の 10% up ぐらいなだろうと思われる。

www.macotakara.jp

 

Apple Afterburner

開発側がハードウェア記述言語を使って構成を設定できる集積回路である FGPA を使用したボード。
動画のデコードのために設計されており、Apple ProRes RAW でほぼ無圧縮な動画を 8K なら最大 6 ストリーム、4K なら 23 ストリーム同時再生できる。

とりあえず、他の用途でも使用できるよう期待している。

FPGA の詳細不明で、Intel や Xilinx や Lattice だろうかというところ。
Apple の FPGA 関連の求人も多いので、もしかすると自前でつくっているのかもしれない。

動画処理に CPU の使用率がかなり減るため動画編集するなら、正直 CPU/GPU のスペック上げるより、こちらに投資した方が良さそう。
(一部 ProRes の処理は GPU を使用するため、GPU も強ければなお良し)

ちなみに Afterburner は最大で3枚までさせるらしい。

 

その他

Geekbench における CPU や Compute のベンチはメモリにも左右されるので、今後高速なメモリが出れば結果は変わるかも。

また、ストレージの速度が上がると体感的に作業時間が変わるのでそこら辺も考えたいところ。

 

で、どうなの?

Mac Pro のベースモデルですらかなり高い価格が設定されており、現状ベンチ的にはベースモデルよりも iMac の上位グレードや iMac Pro 8 Core の方が良い結果が出てしまっている。
メモリもストレージもあまりないので、 何年間か売り続けた場合、旧 Mac Pro 以上コストに対してスペックが厳しくなる予感はある。

早い段階でマイナーアップされる可能性もあるが過去の流れから考えるとどうかわからない。

とはいえ、動画編集は早くしたいなら Mac Pro と Afterburner という感はある。
ただ、CPU は 12 Core 以上かつ、GPU は 580X 以上かつ Vega II 2つ以上と Afterburner となる相当厳しい価格帯。

Pro Display XDR を使用しないなら、コスパ的には iMac Pro、GPU のスペックが劣ってもよければ、iMac の i9 といったところ。
もし、Pro Display XDR を使用したいなら、eGPU で繋げば良いかと。

Swift 5.1 で追加された SIMD の機能

忘れていたのでまとめてみる。
Swift でビルトイン実装されている SMID の機能と Swift Standard Library で SMID を引数として持つ関数が追加された。

 

追加されたものは以下のもの

 

SIMD
  • one
  • clamp(lowerBound:upperBound:)
  • max()
  • min()
  • sum()
  • wrappedSum()
  • hashValue
  • scalarCount
  • subscript(_:)
  • イニシャライズ時に ArrayLiteralElement のサポート

 

SIMD3、SIMD4 のみ
  • init(_ xy: SIMD2, _ z: Scalar)
  • init(_ xyz: SIMD3, _ w: Scalar)

 

Swift Standard Library
  • any(_:)
  • all(_:)
  • pointwiseMax(::)
  • pointwiseMin(::)

 

内容説明

以下、特に記載がなければ、SIMD2 〜 64 と全ての型 (Scalar) を対応。
SIMD の説明はしないので前回を参照。

 

one

以前にあった zero はレーンを 0 で埋めるものだがこちらは 1 で埋めていく。

let s = SIMD4<Float>.one

// SIMD4<Float>(1.0, 1.0, 1.0, 1.0)

 

clamp(lowerBound:upperBound:)

指定された2レーンの中から上限値と下限値内に収まるように各値を変更する。

let s1 = SIMD4<Float>(1.1, 2.2, 3.3, 4.4)

let s2 = s1.clamped(
    lowerBound: SIMD4<Float>(2,2,2,2),
    upperBound: SIMD4<Float>(4,4,4,4)
)
// SIMD4<Float>(2.0, 2.2, 3.3, 4.0)

 

max()
min()

レーンの中から最大値や最小値を調べその値を返す。

let s = SIMD4<Float>(1.1, 2.2, 3.3, 4.4)

s.max() // 4.4
s.min() // 1.1

 

sum()
wrappedSum()

sum() は FloatingPoint を返し、wrappedSum() は FixedWidthInteger を返す。
そのため、型 (Scalar) が浮動小数点数の場合は sum()、
整数の場合は wrappedSum() が使用可能。

let s1 = SIMD4<Float>(0.0, 1.1, 2.2, 3.3)
let s2 = SIMD16<Int>(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)

s1.sum() // 6.6
s2.wrappedSum() // 120

 

hashValue

ハッシュ値を返す。

let s1 = SIMD4<Int>(0,1,2,4)
s1.hashValue // -8324632633033442855 など

 

scalarCount

Scalar が幾つの SIMD か調べることができる。

let s1 = SIMD4<Int>(0,1,2,3)
let s2 = SIMD16<Int>(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)

s1.scalarCount // 4
s2.scalarCount // 16

 

subscript(_:)

FixedWidthInteger の SIMD を添え字として使用し対象の SIMD の値を取り出し、
新しい値の入ったレーンを取得できる。

let s1 = SIMD2<Int>(3,2)
let s2 = SIMD4<Float>(0.1, 0.2, 0.3, 0.4)

let s3 = s2[s1] // SIMD2<Float>(0.4, 0.3)

 

イニシャライズ時に ArrayLiteralElement のサポート

配列の記述を使用し、SIMD イニシャライズすることが可能になった。
SIMD2 〜 64、全ての型 (Scalar) を対応。

let a2:[Float] = [1,2]
let a3:[Float] = [1,2,3]
let a4:[Float] = [1,2,3,4]
let a8:[Float] = [1,2,3,4,5,6,7,8]

let s2 = SIMD2<Float>(a2)
let s3 = SIMD3<Float>(a3)
let s4 = SIMD4<Float>(a4)
let s8 = SIMD8<Float>(a8)

 

init(_ xy: SIMD2, _ z: Scalar)
init(_ xyz: SIMD3, _ w: Scalar)

SIMD3、SIMD4 のみ、SIMD2、SIMD3 と最後の値を足すことでイニシャライズできる関数が追加された。

let s2 = SIMD2<Float>.zero // SIMD2<Float>(0.0, 0.0)
let s3 = SIMD3<Float>(s2, 3) // SIMD3<Float>(0.0, 0.0, 3.0)
let s4 = SIMD4<Float>(s3, 4) // SIMD4<Float>(0.0, 0.0, 3.0, 4.0)

 

any(_:)
all(_:)

SIMDMask の Bool を使い状態を判別する関数。
any はレーンのいずれかが true であれば true、
all はレーンの全てが true であれば true を返す

let s8 = SIMD8<Float>(1,2,3,4,5,6,7,8)

any(s8 .< 3) // true
all(s8 .< 7) // false
all(s8 .< 9) // true

 

pointwiseMin(_:_:)
pointwiseMax(_:_:)

2つのレーンを調べその中の最大値や最小値を設定したレーンを返す。

let s1 = SIMD3(0,1,2)
let s2 = SIMD3(2,1,0)
pointwiseMin(s1, s2) // SIMD3<Int>(0, 1, 0)
pointwiseMax(s1, s2) // SIMD3<Int>(2, 1, 2)

 

廃止になったもの

  • init(Self.Scalar)
  • static func - (Self) -> Self

 

以上、更新の内容。
実は前回のやつ全て書いてないので全部紹介しきれていないけど

MacBook Pro 16-inch のベンチマークとその先

Geekbench にて MacBook Pro 16 inch のベンチマークが出たので以下に一部抜粋してみた。

端末名 CPU クロック Single Multi
iMac 27-inch
Retina Early 2019)
Core i9-9900K
(8 cores)
3.6 GHz 1254 8187
iMac Pro
(Late 2017)
Xeon W-2140B
(8 cores)
3.2 GHz 1081 8142
Mac Pro Late 2013 Xeon E5-2697 v2
(12 cores)
2.7 GHz 727 7051
MacBook Pro 16-inch Core i9-9980HK
(8 cores)
2.4 GHz 1112 7029
MacBook Pro 16-inch Core i9-9880H
(8 cores)
2.3 GHz 1083 6682
iPad Pro
12.9-inch 3rd
A12X Bionic 2.5 GHz 1114 4614
iPhone 11 Pro Max A13 Bionic 2.7 GHz 1330 3430

 

デスクトップ版の i9 に近しく、現行の Mac Pro (Late 2013) とほぼ同じだ。

MacBook Pro 16 inch の GPU は iMac Pro の Vega 56 と比べると 5500M 8G は半分ぐらいなので、すでに ディスプレイやキーボード、トラックパッドやマウスなどのポインティングデバイスがあるのなら、上のグレードの MacBook Pro 16 inch でも良いのではという感はある。
多分、そのうち出る Mac Pro 以外でスペックの高いデスクトップは当分先だろうし、次の CPU の 10900 系も物凄い期待はできない。

MacBook Pro 16 inch で CPU を 9980HK、5500M 8G、メモリ 16 G にした場合、 iMac 5K i9 で同じぐらいにすると税込で 40 万超えるので、上位の 16 inch は妥当な価格だと思う。

逆にいうと 現行の Mac Pro の方はコスパが良くなく、iMac Pro は 10 Core からという感はある。

 

また、リリースはされていないが次の iPad Pro で搭載されると思われる A13X では A12X より少なくとも 20 %は上がるとは思われるので、以前も書いたように第8世代目のデスクトップ版 Core i7 に近くなり、今後 Apple A チップでの Mac もあり得るかもしれない。

ただ、Windows を見ていると、Intel チップの命令をシミュレートするのは厳しそうなので、Mac Catalyst のアプリしか動かない感はある。

iOS 13 で Custom Fonts の API を使ってみる

今までの iOS では自前のフォント(カスタムフォント)の使用はアプリ内に制限されていたが、iOS 13 からは OS 機能として、自前のフォントのインストールと使用ができるようになり、
他のアプリでも自前のフォントが使用できるようになった。

フォントのインストール方法は、
アプリからフォントをインストールし他のアプリでも使用できるような形。
そのため、アプリをアンインストールするとインストールしたフォントが使えなくなる。

インストールしたフォントの確認は以下の場所でできる。
また、ユーザーによるフォントの削除もここでも行うことができる。

設定アプリ > 一般 > フォント
(Settings > General > Fonts)

f:id:x67x6fx74x6f:20191116040110p:plain

 

結局どう動いているのか?

詳しくは不明なので憶測だが、 CTFontManagerCopyRegisteredDescriptors という API があるということは、 設定アプリにフォントがインストールされるわけではなく、Today など Extensions のように XPC みたいなやつで、アプリのリソースを参照している可能性がある。

 

API できること

  • カスタムフォントのインストール
  • インストールされているフォントを選択するための UI の表示
  • インストールされているフォントの検索
  • カスタムフォントの削除

 

今回はカスタムフォントの削除の説明は行わない。
削除を行うケースがサブスクリプションなどの使用期間が決まっているものしか用途がないし、ユーザーで消すことができるので。
あと、アプリからの削除はフォントを指定して命令実行するだけなので。

 

カスタムフォント使用アプリで必ず行う設定

今回の様に他のアプリでもフォントを使用する場合は必ず行うことがあり、カスタムフォントのインストールやカスタムフォントの使用する場合、Entitlements ファイルが必要になる。

プロジェクトの Targets からアプリを選択し、「Signing & Capabilities」選択して、 左上プラスボタンの「Capabiliy」から「Fonts」を選択する

追加された「Fonts」で Privileges に 2 項目のチェックボックスがある。

  • Install Fonts
  • Use Installed Fonts

 

f:id:x67x6fx74x6f:20191116035940p:plain

 

「Install Fonts」
フォントをインストールするアプリで必須となり、このチェックがないと「does not have a necessary entitlement.」とほざいてエラーになる。

「Use Installed Fonts」
インストールされたフォントを使用できる様にする。
これにチェックがないとフォントを選択することができない。
こちらは資格情報がないなどのエラーが出ないので要注意。

 

API を使用する: フォントのインストール

f:id:x67x6fx74x6f:20191116040500p:plain

CFArray で設定した1つまたは複数のリソース、スコープの選択、他のアプリでの検知を有効にする Bool、フォントを登録する際に処理されるクロージャーを設定し、関数を実行するとフォントのインストールが開始される。

使用できる関数は以下の 3 つでバンドルの URL、フォントディスクリプター、アセット名から動作するものがあり、
CTFontManagerRegisterFontsWithAssetNames のみ CFBundleRef を選択する引数が増える。

  • CTFontManagerRegisterFontURLs
  • CTFontManagerRegisterFontDescriptors
  • CTFontManagerRegisterFontsWithAssetNames

 

今回は面倒なので CTFontManagerRegisterFontURLs を使用する。
CTFontManagerRegisterFontURLs の API の定義はこんな感じ

void CTFontManagerRegisterFontURLs(CFArrayRef fontURLs, CTFontManagerScope scope, bool enabled, bool (^registrationHandler)(CFArrayRef errors, bool done));

 

引数
引数 説明
fontURLs フォントURLの配列
scope 登録の可用性と有効期間を定義する定数
enabled CTFontManagerRequestFonts で検出可能にするかを示すブール値

 

scope (CTFontManagerScope)

フォント登録範囲の定義を表す。
また、ここでいうセッションとは macOS ではログインセッション、iOS では現在の起動セッションのことをいうらしい。

スコープ 説明
none フォントは登録されておらず、フォントディスクリプターでは一致しない。
そのためフォントの登録時に指定する有効なスコープではないとのこと。
process フォントは直接登録解除しない限り、現在のプロセスが継続中である場合に使用できる。
persistent フォントは現在のユーザーセッションのすべてのプロセスで使用でき、登録解除しない限り後続のセッションで使用できる。
session このフォントは現在のユーザーセッションで使用でき、以降のセッションでは使用できない。
このセッションスコープは macOS でのみ使用可能。

 

registrationHandler (CFArrayRef errors, bool done)

エラーが発見されたとき、または完了時に呼び出されるブロックのハンドラー。
errors パラメーターは CFError 参照の配列が含まれ、空の配列はエラーがないことを示す。

各エラーでは kCTFontManagerErrorFontURLsKey に対応するフォント URL の CFArray が含まれたエラーとなり、正常に登録されなかったフォントファイルを表す。

このハンドラーは登録プロセス中に複数回呼び出される場合があり、 登録プロセスが完了すると、done パラメーターは true を返す。

ブロックで操作を停止する場合、return で false を返す必要があり、 エラーを受け取った後に行うと良いだろうと思われる。

 

サンプルコード

メインバンドルから、スコープを persistent、このフォントを CTFontManagerRequestFonts で検出可能にする。

実行すると専用の UI が表示されインストールするか否かを操作できる。
試したところ、アプリで 1 回フォントをインストールをするとインストール用の UI が表示されなくなるため、複数インストールする場合は CFArray で複数設定した方が良さそう。
(しばらくするとキャッシュが切れ再度 UI が表示されるかも? サンプルコードでお試しあれ)

guard let mainBundleURL = Bundle.main.url(forResource: "フォント名", withExtension: "otf") else {
    print("File Not Found!")
    return
}

let fontURLArray = [mainBundleURL] as CFArray
CTFontManagerRegisterFontURLs(fontURLArray, .persistent, true) { (errors, done) -> Bool in
    
    if CFArrayGetCount(errors) > 0 {
        print(errors as Array)
        return false
    }
    
    if done {
        print("done")
        return true
    }

    return true
}

 

API を使用する: インストールされているフォントを UI で選択する

f:id:x67x6fx74x6f:20191116042332p:plain

iOS 13 で追加された UIFontPickerViewController を使用し、テーブルビューに列挙されたフォント名からフォントを選択する。

UIFontPickerViewController、その設定とデリゲートを作成し、普通の ViewController を様に present で表示する。

 

サンプルコード

class ViewController: UIViewController, UIFontPickerViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // フォントピッカーの表示設定
        let fontPickerConfig = UIFontPickerViewController.Configuration()
        
        // 複数のフォントフェイスを表示
        fontPickerConfig.includeFaces = true
        
        // 各フォント名にそのフォントを適応する (true だとシステムフォントで表示されるっぽい)
        fontPickerConfig.displayUsingSystemFont = false
        
        // 日本語フォントのみ表示
        fontPickerConfig.filteredLanguagesPredicate = UIFontPickerViewController.Configuration.filterPredicate(forFilteredLanguages: ["ja"])
        
        // フォントピッカーの設定
        let fontPicker = UIFontPickerViewController(configuration: fontPickerConfig)
        fontPicker.delegate = self

        // フォントピッカーの表示
        self.present(fontPicker, animated: true, completion: nil)
    }
    
    // UIFontPickerViewControllerDelegate - 選択後
    func fontPickerViewControllerDidPickFont(_ fontPicker: UIFontPickerViewController) {
        if let fontName = fontPicker.selectedFontDescriptor?.postscriptName {
            print("FontName: \(fontName)")
        }
    }

    // UIFontPickerViewControllerDelegate - キャンセル
    func fontPickerViewControllerDidCancel(_ viewController: UIFontPickerViewController) {
    }
}

 

UIFontPickerViewController.Configuration の説明

UIFontPickerViewController を表示する際、Configuration で値を設定すると特定の条件で表示をフィルタリングすることができる。

 

includeFaces

複数のフォントフェイスを表示するか否か。
false だと Regular っぽいもののみ表示し、true だとそのファミリー全て表示される。

 

displayUsingSystemFont

false にするとそのフォントでフォント名で表示される。
true にすると全てシステムフォントで表示される。

 

filteredTraits

以下、表示の際にフィルターできる特性。
例えば traitBold にすると Bold (太文字) のみ表示される。

  • traitItalic
  • traitBold
  • traitExpanded
  • traitCondensed
  • traitMonoSpace
  • traitVertical
  • traitUIOptimized
  • traitTightLeading
  • traitLooseLeading
  • classMask
  • classOldStyleSerifs
  • classTransitionalSerifs
  • classModernSerifs
  • classClarendonSerifs
  • classSlabSerifs
  • classFreeformSerifs
  • classSansSerif
  • classOrnamentals
  • classScripts
  • classSymbolic

 

filteredLanguagesPredicate

言語でフィルターする。
UIFontPickerViewController.Configuration のクラス関数 filterPredicate から言語を設定する。
複数の言語指定も可。

 

デリゲート

選択後とキャンセルしかない。
使用方法は上記のコードや最後のサンプルコードを参照。

 

API を使用する: インストールされているフォントの検索

f:id:x67x6fx74x6f:20191116042514p:plain
画像は API を使用し検知できなかった時の UI

CTFontManagerRequestFonts に検知したいフォントを UIFontDescriptor で設定し、CFArray に渡し、CTFontManagerRequestFonts で検索する。

CTFontManagerRequestFonts のブロック内で DispatchQueue を使用しているのは、CFArray が複数設定できるため複数回かつある程度の速さでコールされるため。

 

サンプルコード

let fontName = UIFontDescriptor(fontAttributes:
                                    [UIFontDescriptor.AttributeName.name: "98Font"])
        
let fontAskArray = [fontName] as CFArray

CTFontManagerRequestFonts(fontAskArray as CFArray) { (unresolved: CFArray) in
    DispatchQueue.main.async {
        print(unresolved)
    }
}

 

サンプルプロジェクト

一応、今回紹介したものを使用したプロジェクトファイルを作成してみた。

github.com

 

ちなみにインストールしたカスタムフォントを他のアプリで使用する場合は、使用中フォントが消されたり使えなくなる可能性もあるため NotificationCenter で kCTFontManagerRegisteredFontsChangedNotification などで調べる必要がある。
要注意。

 

まとめ

わりと手軽に macOS の様にフォントのインストールや選択や検索ができる様になった。
今後もこういう新規実装と手軽に使えるものをつくっていただきたいと思う。