FlutterのMaterial design iconをネイティブ側でも使う(iOS)
Flutterでは、以下のようにIconsクラスを使うことで、豊富な Material design iconを利用できるのが便利ですよね。
Icon(Icons.calendar_today)
今回、iOSでウィジェット(WidgetKit)を実装する時にFlutterのiconをウィジェットにも表示させたいと思い、やり方を調べました。
iconのリソースとFlutterでのicon表示の仕組み
Githubにはmaterial-design-iconsというiconのリソースリポジトリがあり、iosディレクトリ配下にpngのicon画像が入っています。
この画像をXCodeにアセットとして追加して使えば良いかな...と思ったのですが、
実はFlutterのIconの実装(v2.10.3)を見ると
class Icon extends StatelessWidget {
/// Creates an icon.
///
/// The [size] and [color] default to the value given by the current [IconTheme].
const Icon(
this.icon, {
Key? key,
this.size,
this.color,
this.semanticLabel,
this.textDirection,
}) : super(key: key);
/// The icon to display. The available icons are described in [Icons].
///
/// The icon can be null, in which case the widget will render as an empty
/// space of the specified [size].
final IconData? icon;
// 省略
Widget build(BuildContext context) {
// 省略
Widget iconWidget = RichText( // ←お前だったのか、、iconを表示していたのは、、
// 省略
text: TextSpan(
text: String.fromCharCode(icon!.codePoint),
style: TextStyle(
inherit: false,
color: iconColor,
fontSize: iconSize,
fontFamily: icon!.fontFamily,
package: icon!.fontPackage,
),
),
);
return Semantics(
label: semanticLabel,
child: ExcludeSemantics(
child: SizedBox(
width: iconSize,
height: iconSize,
child: Center(
child: iconWidget,
),
),
),
);
実体はRichTextで、text, fontFamily, packageにそれぞれiconのcodePoint(Unicodeコードポイント)やfontFamilyを指定することで表示している
みたいなんですよね。
そうであれば、ネイティブ側でも然るべきfontファイルにアクセスできれば、codePointやfontFamilyを指定することでSwiftUIのTextで同じものが表示できるはず!
そう思いつつリソースリポジトリを見ると、fontディレクトリ配下にfontファイル(.ttfや.otf)が入ってました!
ということで、このfontファイルをネイティブ側にも追加して表示させたいと思います。
余談:
使いたいiconが数種類であればpng画像を追加するのが早いと思いますが、
今回は個人開発の時間管理アプリで、時間の記録対象であるアクティビティのicon(300種類ほどあってユーザが設定可)を表示させたかったので、バンドルサイズの観点からもfontファイルの方が良いだろうということでfontファイルを使うことにしました。
Xcodeにfontファイルを追加して表示する
以下の手順で表示できました
1.material-design-icons/fontから MaterialIcons-Regular.ttf をダウンロード
2.XCodeのファイルエリアにドラッグ&ドロップ
以下のようにチェックをつける
(今回はXCodeで新規にSampleというiOSのprojectを作ったのでSampleにチェック)
fontファイルが追加されたのが確認できる
3.Info.plistを開く
ファイルエリアからInfo.plistを開く、もしくは(XCode13で作ったprojectであれば見えない?ので)以下から開く
4.適当な行の+ボタンからFonts provided by application
の行(Array)を追加し、その子要素として追加したfontファイル名(MaterialIcons-Regular.ttf
)を追加する
5.viewを編集
以下のようにTextを追加
Text("\u{EA65}").font(.custom("MaterialIcons-Regular", size: 96.0)).foregroundColor(.orange)
6.iconが表示できた!
iconに対応するcode pointの調べ方
リソースリポジトリに MaterialIcons-Regular.codepointsというテキストファイルがあり、そこに対応関係が書かれています。
(上記例では celebration ea65
という行があります。
つまり、Flutter側でIcons.celebration
で表示するiconのcode pointがea65
ということです)
iconの見た目から選びたい場合、Icons classのページからicon名を特定 → .codepointsファイルでcode pointを見る
という手順でも良いですが、もっと直接的な手段として
こういうfontファイルを開いて一覧表示してくれるサービスがあるので、
左上の「SELECT FONT」から .ttfや .otfファイルを開く
→ iconをタップしてUNICODE
欄を確認
という手順も良さそうです。
わからなかったこと
code pointを調べる際、最初はFlutterソース上でCommand + クリックでソースに飛ぶ
/// <i class="material-icons md-36">celebration</i> — material icon named "celebration".
static const IconData celebration = IconData(0xe149, fontFamily: 'MaterialIcons');
↑ IconDataの第1引数(=code point)を確認
したところ、追加したfontファイルとは違うcode pointになっていました...(e149 ≠ ea65)
どうもFlutter側で使っているfontファイルとは違うようです。
そもそもXCodeにfontファイルを追加する際に
「Flutter側で使っているfontファイルがどこかにあるはず、ios/Runner配下とかに無いかな?」
と思い探しましたが見当たりませんでした。
(fontのソースリポジトリで別バージョンのfontもいくつか見てみたがわからず・・
もしご存じの方いたら教えて下さい。)
Fluterはオープンソースですが、Developer WikiのUpdating Material Design Fonts & Iconsを見てみると、「font及びiconファイルの更新は自動化されている」という記載の後にGoogle社内でのみ閲覧できる?documentへのリンクがあるだけで、これ以上はわかりませんでした😂
Androidはどうなる?
今回Androidのウィジェット(AppWidget) でも同様にfontファイルを使ってFlutterアイコンを表示したかったのですができませんでした。。
公式ドキュメントの XMLフォント に説明があったのでやってみたのですが、どうもウィジェットには表示できない。
色々調べてみると、どうやら2021年6月のアップデートで
This is an intended change. Allowing custom fonts in app widgets was an unintended behavior and could also expose a security bug. In june update we have fixed that flaw.
(これは意図した変更です。アプリウィジェットでカスタムfontを許可することは、意図しない動作であり、セキュリティバグを公開する可能性もありました。6月のアップデートでは、その不具合を修正しました。)
ということでウィジェットでカスタムfontが使えなくなったようです。(ちなみに事前の予告なし😭)
→ https://issuetracker.google.com/issues/190444596?pli=1
こちらではどうしようもないので、もし将来使えるようになったら対応してみようと思います。
Discussion