2022-08-22

React Nativeでカスタムフォントのweight設定をする

expo-fontsでのfontWeight指定

expo-fontsを使うとアプリでカスタムフォントを利用することができます。

ただ、fontWeightで太さを変えたい場合に一つのfontFamily指定ではうまくいきません。例えば以下のようにカスタムフォントを指定した場合です。

useFonts({ NotoSansJP: require("./assets/fonts/NotoSansJP-Regular.otf") })

<Text style={{ fontFamily: "NotoSansJP", fontWeight: "normal" }}>
  Normal
</Text>
<Text style={{ fontFamily: "NotoSansJP", fontWeight: "bold" }}>
  Bold
</Text>

Androidの場合はフォントファイルの末尾が_bold_italic_bold_italicになっていれば自動で読み込んでくれるため、bold用のフォントファイルを配置しておけばboldに変わってくれるのですが、iOSの場合はboldになってくれません。また、Androidの場合でもweight"100""900"で細かく指定することはできません。

How to add fonts for different font weights for react-native android project? - Stack Overflow

ではどうしたらweightに応じて太さを変えるかですが、以下のIssueで対処法があったためこれを参考にしてweightを指定可能にしてみたいと思います。

Props font-weight doesn't work on iOS by use expo-font and custom font · Issue #9149 · expo/expo

fontWeightに応じてfontFamilyを変更する

NotoSansJPを利用すると仮定して進めます。

フォントのロード時にすべてのフォントファイルを個別のfontFamilyとしてロードします。

useFonts({
    NotoSansJPRegular: require("./assets/fonts/NotoSansJP-Regular.otf"),
    NotoSansJPBold: require("./assets/fonts/NotoSansJP-Bold.otf"),
    NotoSansJPBlack: require("./assets/fonts/NotoSansJP-Black.otf"),
    NotoSansJPThin: require("./assets/fonts/NotoSansJP-Thin.otf"),
    NotoSansJPMedium: require("./assets/fonts/NotoSansJP-Medium.otf"),
    NotoSansJPLight: require("./assets/fonts/NotoSansJP-Light.otf"),
  })

プロジェクトに一つTextコンポーネントを用意します。各画面からはReact NativeのTextではなくこのコンポーネントを常に使うようにします。

import { ComponentProps } from "react"
import { StyleProp, StyleSheet, Text as RnText, TextStyle } from "react-native"

export function Text(props: ComponentProps<typeof RnText>) {
  return <RnText {...props} style={withFontFamilyStyle(props.style)} />
}

const FONT_MAPPING: { [key in TextStyle["fontWeight"]]: string } = {
  "100": "NotoSansJPThin",
  "200": "NotoSansJPLight",
  "300": "NotoSansJPLight",
  "400": "NotoSansJPRegular",
  "500": "NotoSansJPMedium",
  "600": "NotoSansJPMedium",
  "700": "NotoSansJPBold",
  "800": "NotoSansJPBlack",
  "900": "NotoSansJPBlack",
  normal: "NotoSansJPRegular",
  bold: "NotoSansJPBold",
}

function withFontFamilyStyle(
  style: StyleProp<TextStyle>,
): StyleProp<TextStyle> {
  if (style == null) return style
  const flattened = StyleSheet.flatten(style)
  const fontWeight = flattened?.fontWeight
  if (fontWeight == null) return style

  const fontFamily = FONT_MAPPING[fontWeight]
  if (fontFamily == null) return style

  return [style, { fontFamily }]
}

withFontFamilyStyle()でスタイルの中身をチェックしfontWeightの値に応じてfontFamilyを指定したスタイルを付加します。

StyleSheet.flatten(style) は、スタイルが配列の形になっていたり入れ子になってたりする場合に対応するため必要です。例えばスタイルが[{}, [{}, {}]]のような形式になっていた場合でも、一つのオブジェクトにしてくれます。また、[{ fontWeight: "bold" }, { .fontWeight: "normal" }]のように、後から追加されたスタイルで打ち消されている場合も、後から指定したものを優先してくれます。

それでは以下のようにそれぞれのfontWeightの値を指定して表示してみます。

import { Text } from "./Text"

  {[
    "100",
    "200",
    "300",
    "400",
    "500",
    "600",
    "700",
    "800",
    "900",
    "normal",
    "bold",
  ].map(weight => (
    <Text
      key={weight}
      style={{ fontSize: 16, fontWeight: weight }}
    >
      {weight}
    </Text>
  ))}

fontWeightに応じて太さが変わって表示されるようになりました。

最終更新: 2022-08-23 01:05
筆者: @gaishimo 主にReact Nativeでのアプリ開発を行っています。
© 2021 Omoidasu, Inc. All rights reserved.