エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

m3.com iOSアプリにホーム画面とロック画面のウィジェットを導入しました

こんにちは、マルチデバイスチームでモバイルアプリエンジニアをしている小林 (@bakobox) です。

先日、m3.com iOSアプリにホーム画面とロック画面用のウィジェットを実装しました。 エムスリーのiOSアプリでは初の導入ということもあり、ウィジェット実装に関する知見が得られたので共有いたします!

ウィジェット導入の背景

m3.comアプリは医療従事者向けのプラットフォームで、最新の医療情報を収集できるアプリです。

アプリ内では、ニュースや医薬品、講演会など医療に関する様々な情報がチェックできたり、他の医療従事者と情報交換を行える場が提供されています。 忙しい医療現場でも、m3.comアプリを活用することで効率的に情報収集を行い、より良い医療提供につなげることができます。

m3.comアプリ

3年ほど前にアプリのリファクタリングを行った後、新規機能や施策の開発が活発になり、またデザインリニューアルも行ってきました。

その流れで、より気軽にm3.comアプリへアクセスしていただけるようにウィジェットを開発することになりました。

導入したウィジェット

今回追加したのは次のウィジェットです。

ホーム画面用

ナビゲーションウィジェットとイチ推しウィジェット

ナビゲーションウィジェットとイチ推しウィジェット(ダーク)

ナビゲーションウィジェットは、アプリ内の特定のサービスへ素早くアクセスできるウィジェットです。 バッジにより、新着メッセージや放送中の講演会があるかどうかもひと目で分かります。

イチ推しウィジェットは、アプリ内のおすすめコンテンツを表示するウィジェットです。 各ユーザーにパーソナライズされています。

ロック画面用

ロック画面用ウィジェット

こちらもホーム画面と同様に、特定のサービスへ素早くアクセスしたり、おすすめコンテンツを表示するウィジェットを追加しました。

実装時のTips

ウィジェットの導入方法に関しては様々な情報が既にあるため、ここでは少し工夫が必要なポイントをいくつかピックアップして紹介いたします。

Widget Extensionを追加する方法(XcodeGenを利用している場合)

m3.comアプリではXcodeGenを利用してプロジェクトを管理しているため、この場合の設定方法をご紹介します。

はじめにXcodeでファイルを生成する

Xcode -> File -> New -> Target
でWidgetExtensionを作成し、必要なファイルを生成します。

SampleWidget
├── Assets.xcassets
│   ├── AccentColor.colorset
│   │   └── Contents.json
│   ├── AppIcon.appiconset
│   │   └── Contents.json
│   ├── Contents.json
│   └── WidgetBackground.colorset
│       └── Contents.json
├── Info.plist
├── SampleWidget.swift
└── SampleWidgetBundle.swift

project.ymlを編集

targetの追加

name: SampleApp

...

targets:
    # アプリ本体
    SampleApp:
        type: application
        ...
    dependencies:
    - ...
    - target: SampleWidget # 追記

    # ウィジェット
    SampleWidget:
        type: app-extension
        platform: iOS
        dependencies:
        - sdk: SwiftUI.framework
        - sdk: WidgetKit.framework
        sources:
        - name: SampleWidget
          path: SampleWidget
        scheme: {}
        settings:
            base:
                PRODUCT_BUNDLE_IDENTIFIER: com.example.SampleWidget
                INFOPLIST_FILE: SampleWidget/Info.plist
                SKIP_INSTALL: true
                PRODUCT_NAME: $(TARGET_NAME)
                ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
                ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME: WidgetBackground        

schemeを追加

schemes:
  SampleWidget:
    analyze:
      config: Debug
    archive:
      config: Release
    build:
      targets:
        SampleApp: all
        SampleWidget: all
    profile:
      config: Release
      askForAppToLaunch: true
    run:
      config: Debug
      askForAppToLaunch: true
      debugEnabled: false
      environmentVariables:
      - variable: _XCWidgetKind
        value:
        isEnabled: false
      - variable: _XCWidgetDefaultView
        value: timeline
        isEnabled: false
      - variable: _XCWidgetFamily
        value: systemMedium
        isEnabled: false
    test:
      config: Debug

targetsのscheme: {}は一見必要ないように思えますが、これを省くとSampleWidget.xcschemewasCreatedForAppExtensionYESに設定されず、Xcode上でApp Extensionであることが正しく認識されなくなります。*1

scheme: {} を設定しなかった場合(左)とした場合(右)

これをベースにして、後はプロジェクトに合わせて適宜修正していただけたらと思います。

本体側とのデータ共有について

ウィジェットは App Extension なので、アプリ本体とはプロセスが別です。 そのため、データ共有は少し工夫が必要です。

UserDefaults

アプリ本体の UserDefaults.standard とウィジェット側のUserDefaults.standardはデータが共有されていません。

共有したい場合は、App Groupsを使う必要があります。

init(suiteName:) | Apple Developer Documentation

これを用いてUserDefaultsを取得することで、データ共有が可能となります。

UserDefaults(suiteName: "{App Groupsのidentifier}")

HTTPCookieStorage

HTTPCookieStorage.sharedも同様に共有されないので、App Groupsを使う必要があります。

sharedCookieStorage(forGroupContainerIdentifier:) | Apple Developer Documentation

こちらを利用してHTTPCookieStorageを取得します。

HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: "{App Groupsのidentifier}")

ユーザーがウィジェットを設置しているかどうかを調べたい

アプリ本体からウィジェットの利用状況を調べたい場合、WidgetCenterのgetCurrentConfigurations(_:)で取得可能です。

getCurrentConfigurations(_:) | Apple Developer Documentation

WidgetInfoの配列が返ってきます。これが、現在ユーザーが設置しているウィジェットです。

ユーザーの行動ログを荒らさないように気をつける

ウィジェットはアプリ本体が起動していなくてもデータ取得が行われるため、API呼び出しに基づいてユーザーの行動ログを取っている場合は注意が必要です。

例えば、最終アクセス時刻をAPIが最後に呼び出された日時で計測している場合、ウィジェットからのアクセスも混ざってしまい、最終アクセス時刻が意図していないものになる可能性があります。

ウィジェットからのアクセスを区別して記録するなどの対策が必要です。

特定時刻にデータを更新する実装を行っている場合には、スパイクに気をつける

ウィジェットで特定時刻にAPIを叩いてデータを更新するような場合、ウィジェットを設置している端末達がその時間に一斉にAPIを叩くことになります。

急激に負荷がかかってしまう可能性があるため、時間を少しずらして負荷を分散させるなど工夫する必要があるかもしれません。

ウィジェットだけでもダークモードに対応しておくのがおすすめ

アプリ本体でダークモード対応ができていない場合でも、ウィジェットはダークモードに対応しておきましょう。

ウィジェットを設置するのはホーム画面です。ホーム画面にはシステムUIや他のアプリのウィジェットも表示されているため、ダークモード対応をしていないとウィジェットが浮いてしまいます。

まとめ

ウィジェット導入に関するお話をさせていただきました。
リリースしてから1ヶ月ちょっと経ちましたが、利用してくださっているユーザーも徐々に増えてきていて嬉しいです!

We’re hiring !

エムスリーでは複数のiOSネイティブアプリを開発しており、Swift・iOSが好きなエンジニアを募集しております! ご応募お待ちしています!

エンジニア採用ページ

jobs.m3.com

マルチデバイスチーム(アプリ開発チーム)紹介資料

speakerdeck.com