MoTLab -GO Inc. Engineering Blog-MoTLab -GO Inc. Engineering Blog-

『GO』のiOSアプリにロック画面Widgetを追加してみた話

iOS
May 29, 2023

タクシーアプリ『GO』のiOSアプリを開発している黒田です。

今回は『GO』にロック画面Widgetをお試しで導入してみたので、その手順や所感を簡単にまとめてみました。


ロック画面Widgetとは

iOS16からiPhoneのロック画面をカスタマイズする方法に様々な機能が追加されました。

その中の一つにロック画面Widgetがあります。

名前の通りロック画面にWidgetを置けるのですが、これにより天気やバッテリー残量、カレンダーの予定などの情報をロック画面から素早く確認できます。また、Widgetからアプリを起動することもできるようになりました。

An image from Notion

Widget

ロック画面Widgetの種類

ロック画面には3種類のWidgetが配置でき、各Widgetに対応するWidgetFamilyは以下の通りです。

  • WidgetFamily.accessoryCircular
    • 円形のWidget
  • WidgetFamily.accessoryRectangular
    • 長方形のWidget
  • WidgetFamily.accessoryInline
    • 単一行のテキストとオプションの画像を含むWidget

An image from Notion

ロック画面Widgetの実装

ロック画面Widgetの実装方法はホーム画面Widgetの実装に非常に似ており、WidgetKitとSwiftUIを使用します。

WidgetKitおよびホーム画面Widgetの実装についてはこちらの記事をご確認ください。 「GO」のiOSアプリにWidgetを追加してみた話

上記記事でWidgetKitについての説明はされているため、この記事では細かい説明は割愛します。

今回は『GO』の機能の一つである「GO Pay」のQRコード読み取り画面をロック画面から起動するWidgetを作ります。

Widgetの作成

まずはロック画面にWidgetを表示させてみます。

XcodeのFile > New > Targetで「Widget Extension」を選択し、適当な名前をつけて追加します。 今回はわかりやすいようにGOPayWidgetという名前で設定しました。

Widget Extensionに追加されたテンプレートのコードのStaticConfigurationに以下の修飾子を追加します。

.supportedFamilies([.accessoryCircular, .accessoryRectangular, .accessoryInline])

Wdigetのコード全体は下記のようになります。

struct GOPayWidget: Widget {
    let kind: String = "GOPayWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            SampleWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        .supportedFamilies([.accessoryCircular, .accessoryRectangular, .accessoryInline])
    }
}

アプリをビルドして起動し、ロック画面の編集画面を表示するとWidgetが追加されています。

An image from Notion

(左がaccessoryRectangular, accessoryCircular, 右がaccessoryInlineの編集画面です)

Viewのカスタマイズ

次に、Widgetの表示を変更してみます。

テンプレートのコードは時刻が表示されるだけのViewになっていますが、今回は「GO Pay」のQRコード読み取り画面を起動させるため、その機能を表すViewに変更していきます。

ロック画面とホーム画面のWidgetでは、使用するレンダリングモードが異なります。

ホーム画面ではフルカラー(fullColor)のモードを使用しますが、ロック画面ではvibrantというレンダリングモードを使用します。

vibrantはテキストや画像をモノクロに脱色し、ロック画面の背景に合わせてコンテンツを適切に着色することで、鮮やかな効果を生み出します。

(もう一つ、watchOS用のaccentedというモードがありますが、本記事では割愛します。)

ホーム画面Widgetとロック画面Widgetを一つのViewで扱う場合、widgetRenderingModeという環境値を使用し、各レンダリングモードのViewを作成しましょう。

struct GOPayWidgetEntryView : View {
    @Environment(\.widgetRenderingMode) var widgetRenderingMode

    var entry: Provider.Entry

    @ViewBuilder
    var body: some View {
        ZStack {
            switch widgetRenderingMode {
            case .fullColor:
						    // フルカラーのWidgetや時計のコンプリケーションのためのViewを作成します。
		        case .accented:
                // watchOSでのアクセント付きレンダリングモードでコンプリケーションをレンダリングします。
            case .vibrant:
               // iPhoneのロック画面WidgetのViewを作成します。
            }
        }
    }
}

また、ロック画面Widgetの各WidgetFamilyに対して適切なViewを返すため、SwiftUIが提供するwidgetFamilyという環境値を使用します。

struct GOPayWidgetEntryView : View {
    @Environment(\.widgetFamily) var family
    var entry: Provider.Entry

    @ViewBuilder
    var body: some View {
        ZStack {
            switch family {
            case .accessoryCircular:
                // 円形のWidget用のView
            case .accessoryRectangular:
                // 長方形のWidget用のView
             case .accessoryInline:
                // 単一行のテキストとオプションの画像を含むWidget用のView
        }
    }
}

実際に実装してみました。

An image from Notion

.accessoryCircularと.accessoryRectangularで設定しているAccessoryWidgetBackground()で背景に色をつけています。

struct GOPayWidgetEntryView : View {
    @Environment(\.widgetFamily) var family
    var entry: Provider.Entry

    @ViewBuilder
    var body: some View {
        ZStack {
            switch family {
            case .accessoryCircular:
                ZStack {
                    AccessoryWidgetBackground()
                    Image(systemName: "qrcode")
                        .resizable()
                        .frame(width: 40, height: 40)
                    Text("GO Pay")
                        .font(.caption)
                        .padding(.all, 1)
                        .background(Color.black)
                }
            case .accessoryRectangular:
                AccessoryWidgetBackground()
                    .cornerRadius(8)
                HStack {
                    Image(systemName: "qrcode.viewfinder")
                        .resizable()
                        .scaledToFit()
                        .padding(.all, 8)
                    Spacer()
                    Text("GO Pay")
                    Spacer() 
                }
            case .accessoryInline:
                HStack {
                    Image(systemName: "qrcode")
                        .resizable()
                        .scaledToFit()
                    Text("GO Pay")
                }
            default:
                Text("GO Pay")  
            }
        }
    }
}

今回はこのようなViewにしましたが、表示スペースが狭いことや色の指定ができないため、シンプルで見やすいViewを設計する必要がありそうです。

また、accessoryInlineは日付の横に表示されるため、今回のような特定の画面を起動するWidgetではなく、日付に関連する情報を表示するWidgetを配置するのが良いと思います。『GO』ではAI予約の時刻の表示などが適していそうです。

Widgetから特定の画面を起動

ロック画面Widgetは何も実装しなければタップするとアプリが起動しますが、アプリの特定の画面を直接開くこともできます。

こちらは.widgetURL修飾子を使用することで簡単に実現できます。

WidgetのViewを以下のように指定します。

ZStack {
    AccessoryWidgetBackground()
    Image(systemName: "qrcode")
        .resizable()
        .frame(width: 40, height: 40)
    Text("GO Pay")
        .font(.caption)
        .padding(.all, 1)
        .background(Color.black)
}
.widgetURL(URL(string: "example://gopay?from=widget"))

次にアプリ側でURLを受け取ります。アプリの構成により下記の通り取得方法が変わります。

それぞれの方法でURLをハンドリングしましょう。

『GO』では、まだapp-based life-cycleを採用しているため、下記のコードでurlをハンドリングします。

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        print(url) // => "example://gopay?from=widget" 
    }
}

実際に動かしてみた様子は以下の通りです。

きちんと動きましたね。

おわりに

何の知識もない状態で取り組み始めましたが、実際に調べて手を動かしてみると、思ったよりサクッと実装することができました。

この狭いViewの中にどのような情報を表示するかについてはもっと検討する必要がありそうです。

今後『GO』により良いWidgetを組み込めるよう取り組んでいきたいと思います。

皆さんも是非実装してみてください。

参考

Creating Lock Screen widgets and watch complications


We're Hiring!

📢
GO株式会社ではともに働くエンジニアを募集しています。

興味のある方は 採用ページ も見ていただけると嬉しいです。

Twitter @goinc_techtalk のフォローもよろしくお願いします!