📲

FlutterのMaterial design iconをネイティブ側でも使う(iOS)

2022/05/29に公開

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を見る
という手順でも良いですが、もっと直接的な手段として
http://mathew-kurian.github.io/CharacterMap/
こういうfontファイルを開いて一覧表示してくれるサービスがあるので、
左上の「SELECT FONT」から .ttfや .otfファイルを開く
→ iconをタップしてUNICODE欄を確認
という手順も良さそうです。

わからなかったこと

code pointを調べる際、最初はFlutterソース上でCommand + クリックでソースに飛ぶ

packages/flutter/lib/src/material/icons.dart
  /// <i class="material-icons md-36">celebration</i> &#x2014; 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