Wave / アプリチームの猪飼 (いかい) です。これは Nikkei Advent Calendar 2021 の 21 日目の投稿です。
iOS 版 紙面ビューアーアプリでは、11/18 にリリースされた 4.0.0 で、以下の 2 つの機能の提供を開始しました。
- メモ: 記事の上に自由に書き込み(メモ)ができる機能
- ハイライト: 記事の上に文字単位でハイライトを引ける機能
この投稿では、これらの機能がどう動くのか、バックエンドからフロントエンド (iOS) の実装について簡単に紹介します。
機能の使い方については、以下の動画をご覧ください。
紙面の上にメモを書き込む
2015 年に Apple Pencil が発売されたときから、アプリで表示される紙面の上に自由にメモを書きたいと思っていました。2018 年に第 2 世代の Apple Pencil が発売され、その翌年、iPadOS の発表とともに PencilKit が公開されます。
PencilKit により、アプリ上にツールピッカーを表示し、何も書かれていないキャンバスにメモを書く仕組みを簡単に作れるようになりました。
ツールピッカー
しかし、紙面ビューアーアプリ上においては、紙面画像の上に メモを書き込めるようにしたいのです。
このために、紙面画像を表示する既存のビュー (UIScrollView
+ UIImageView
) に、メモを描画するための PencilKit.PKCanvasView
をどうにかして組み込もうと考えました。しかし、開発を進めていくうちに、この方法では UX 上の課題があることが分かり、結局、紙面画像を表示するためのビューを PKCanvasView
を使ってゼロベースで作り直すことになりました。
紙面上の文字にハイライトを引く
一文字単位で正確にハイライトできるようにするためには、ハイライト対象となる文字領域の座標が必要です。
12/9 の投稿では、文字領域をどのように抽出するかを説明しています。
試作の段階で、高精度な文字領域抽出には一定の時間がかかることがわかっていました。ある朝刊内のすべての記事を対象にして、手元の端末で 30〜40 分ほど。リリース時点では半分以下に短縮されています。
一方で、文字領域座標のセットは、一つの記事に対して複数用意されうるものではないため、端末上で都度計算させる必要もありません。また、Python で記述された文字領域座標を抽出するためのロジックを Swift や Kotlin に移植するコストも発生します。
したがって、クライアントではなくバックエンドで前もって処理しておく仕組みを構成しました。記事が公開 / 編集されるたびに、文字領域の抽出処理を実行し、記事ごとに用意した文字領域座標のセットを Amazon S3 バケットにアップロード。クライアント (iOSアプリ) はそれをダウンロードして使う方針としました。
紙面ビューアーにデータを供給するためのバックエンド
上流の CMS が S3 バケットに書き込んだ紙面画像・メタデータ(元データ)は、サーバーレスな構成の仕組みの上で変換され、アプリや PC サイトに配信されます。

まず、朝刊や夕刊などをサービスするために必要な元データが CMS から S3 バケットに書き込まれます。
S3 バケットへの PUT イベントをトリガーに呼び出された AWS Lambda 関数は、ページ単位、記事単位にタスクを分割し、別の AWS Lambda 関数を直接呼び出し、または Step Function 経由で別の AWS Lambda 関数を呼び出し、タスクを並列に処理していきます。
ピーク時には12,000 Lambdaインスタンスほどが同時実行されることも
こうして、CMS から元データが書き込まれてからまもなく、CloudFront から朝刊や夕刊などをダウンロードできるようになります。
この仕組みの上で、新たに文字領域を抽出するための AWS Lambda 関数を追加します。

文字領域を抽出する実装は Python (一部 Cython) で書かれていて、OpenCV や Numpy を利用しています。通常の zip パッケージでは AWS Lambda の容量制限により upload できません。このため、Lambda Layers を利用して OpenCV と Numpy を import しています。
ハイライトを書き込む
こうして、前もって生成された文字領域の座標データを、iOS アプリ上で取り扱うためのライブラリ HighlightKit を開発しました。
HighlightKit は、主に以下の機能を提供します。
- ハイライト可能領域のガイド表示
- ハイライトの追加、削除、全削除
- 指または Pencil によるタッチジェスチャのハンドリング
- タッチジェスチャの待受、ハイライトの描画は任意の View に設定可能
文字単位でのハイライトを実現しているアイデアはシンプルです。
文字領域の一つ一つをビュー上に展開します。タッチされた座標が、文字領域の座標と重なっていれば、その文字座標はタッチされたと判定され、その時の状況(該当の座標がハイライトされているか、指の動きの方向は開始時と同じか、など)に応じて表示を切り替えます。
自然な使い心地を実現するためのジェスチャ処理
一方で、タッチされた文字領域のハイライトを単純に反転させるだけだと、自然な使い心地になりません。
画面上でハイライトを引く作業は、スマホ上でのテキスト選択と似ています。iOS や Kindle などが提供する体験に近づければ、多くのユーザーに受け入れられるものになると考えました。
たとえば、複数行にまたがるハイライトを引きたいときを考えます。指をおいた行の最下部に行く前に、次の行 (左方向) に指を動かすと、その行の該当の位置までハイライトを引いてほしいです。

このように、実際にタッチされていない領域へハイライトが引かれるケースも考えられます。
ジェスチャとハイライトのテスト
実装したジェスチャによって、意図通りにハイライトが追加 / 削除されるかを確認するためのテストが必要です。テストをメンテナンスしやすいものとするため、検証する動きと期待する結果を人間が読みやすい形にしておきたいと考えました。
上で例示したジェスチャは、以下のように表現できます。o
はタッチされなかった領域、数字はタッチされた領域を示します。1
であれば 1 度のみタッチされたことを示します。2
であれば 2 度、ということですね。
o o o o
o o o o
o o o o
o o 1 o
o o 1 o
o 1 1 o
o o o o
o o o o
o o o o
o o o o
o o o o
このジェスチャによるハイライト結果を、以下のように表現します。o
はハイライトされていない領域、x
はハイライトされた領域を示します。
o x o o
o x o o
o x o o
o x x o
o x x o
o x x o
o o x o
o o x o
o o x o
o o x o
o o x o
これによって、指 または Pencil のどのような動きによって、どのようなハイライト状態が期待されるかが分かりやすくなります。
ハイライトの内部表現
ハイライトが引かれた領域は、内部的には範囲の配列で表現します。たとえば、以下のようにハイライトが引かれている場合:
o o o o
o o x o
o o x o
o x x o
o x x o
o o x o
o o o o
Swift の Range
を使うと [8...12, 17...18]
のように表すことができます。Range
は Codable
に対応しているので、シリアライズも容易で永続化することが可能です。
読み出しと書き込み
こうしてユーザーが書き込んだメモ、ハイライトは、BFF を経由して Amazon DynamoDB 上に保存されるとともに、Myニュースの保存記事にも登録されます。後から保存記事一覧画面から記事を選び、書いたメモやハイライトを参照できます。

メモやハイライトはリモートに保存されているので、たとえば iPad で書いた内容を iPhone で確認、再編集することもできますし、もちろん逆も可能です。現在は紙面ビューアーアプリのみでの利用にとどまっていますが、将来的な機能拡張に伴いながら、日経電子版での他サービスでの利用も検討しています。
終わりに
本稿では紙面ビューアーアプリのメモ&ハイライト機能がどう動くのかについて簡単に紹介しました。今後もいくつかのアップデートを検討しています。ご期待ください。
この機能はエンジニア主体のスモールチームで実現しています。日経電子版の機能開発や、日経でのエンジニアとしての働き方などに興味をお持ちの方は、下の Entry からカジュアル面談に応募していただければと思います。