booklista tech blog

booklista のエンジニアリングに関する情報を公開しています。

iOS17の新機能「スタンバイ」を推し活アプリに導入してみた話

アイキャッチ

株式会社ブックリスタ プロダクト開発部の酒井です。

2023年9月18日にiOS17が正式リリースされました。
メッセージやAirDropが強化されたり、ウィジェット上でボタン操作などができる「インタラクティブウィジェット」が実装されるなど、いくつかの新機能が追加されました。
その中でも「スタンバイ」という新機能に着目し、弊社で開発している推し活アプリ「Oshibana」に実装してみることにしました。

過去にはiOS16の新機能「ロック画面ウィジェット」に関する記事も執筆していますので、そちらもご一読ください。

iOS16の新機能「ロック画面ウィジェット」を推し活アプリに導入してみた話
https://techblog.booklista.co.jp/entry/2022/09/13/110000

スタンバイについて

スタンバイとは、スマホを横向きに置くことでウィジェットを常時表示させておくことのできる機能のことです。
ウィジェットは小サイズのものが2つ配置できるようになっており、カレンダーウィジェットを表示させれば卓上カレンダーのようになり、時計ウィジェットを表示させれば置き時計のような役割を担うことができます。
ウィジェットは小サイズであれば種類を問わず表示させることができ、もちろんOshibanaで実装されているウィジェットも全て配置できます。

スタンバイ1

使い方は以下の通りです。

  • 設定アプリからスタンバイを選択し、「スタンバイ」をONにする
  • 充電中のスマホをロック状態にし、横向きに立てる(スマホスタンドがあると一番良いです)
  • しばらくするとスタンバイの画面になる
  • 表示されているウィジェット部分を長押しすると、ウィジェットを変更できる
  • ウィジェット変更画面の左上の+ボタンを押すことでウィジェットを追加できる

スタンバイ2
スタンバイ3

スタンバイの実装方法

スタンバイの実装方法ですが、実は特別なコーディングを行う必要はありません。
iOS17のSDKをサポートしているXcode15以降でビルドすれば使用可能になります。

ただし、iOS17ではウィジェットのViewの仕様にいくつか変更点があり、Xcode15でビルドすると必然的に仕様変更の影響を受けます。
今回スタンバイを使用するにあたり、iOS16以下の端末でOshibanaのウィジェットが今まで通り動くようにするため、いくつかソースを修正する必要がありました。
※Xcode14以前でビルドしていればiOS16の互換性でウィジェットが動きますが、スタンバイにアプリが表示されないため、Xcode15へのバージョンアップが必須です。

まず、ウィジェットがiPhoneのホーム画面以外の様々な場所へ配置できるようになったことで、ウィジェットの背景を各画面の背景に合わせる指定が必要になりました。
よって、ビューの背景に関する定義であるcontainerBackground(_:for:))を追記する必要があります。

今回は特に背景の指定はないため、以下のように記述します。

var body: some WidgetConfiguration {
    return IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
        return oshibana_widgetEntryView(entry: entry, widgetSize: .SMALL)
            .containerBackground(for: .widget) {
                EmptyView()
            }
    }
}

これの指定がないと、ビルドは通るのですが、以下のようなエラーがウィジェット上に表示されて動かないという事象が起こります。

スタンバイ4

更に、iOS17からウィジェットにマージンが追加され、元々のレイアウトが崩れてしまうという事象も発生します。

スタンバイ5

余分なマージンを取り除くには、以下のようにcontentMarginsDisabled())を指定し、デフォルトのコンテンツマージンを無効にする必要があります。

var body: some WidgetConfiguration {
    return IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
        return oshibana_widgetEntryView(entry: entry, widgetSize: .SMALL)
    }
    .contentMarginsDisabled()
}

これらはiOS17固有の事象のため、#available(iOS 17.0, *)でiOS16以前と動きを分ける必要があります。

修正したソースの全体像は以下の通りです。

struct oshibana_widgetSmall: Widget {
    let kind: String = "oshibana_widget_small"
    var body: some WidgetConfiguration {
        if #available(iOS 17.0, *) {
            return IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
                return oshibana_widgetEntryView(entry: entry, widgetSize: .SMALL)
                    .containerBackground(for: .widget) {
                        EmptyView()
                    }
            }
            .configurationDisplayName("小サイズ")
            .description("設置したいウィジェットを選択しましょう")
            .supportedFamilies([.systemSmall])
            .contentMarginsDisabled()
        } else {
            return IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
                return oshibana_widgetEntryView(entry: entry, widgetSize: .SMALL)
            }
            .configurationDisplayName("小サイズ")
            .description("設置したいウィジェットを選択しましょう")
            .supportedFamilies([.systemSmall])
        }
    }
}

これらに関してはWWDC2023のBring widgets to new placesというセッションでも詳しく解説されています。
https://developer.apple.com/videos/play/wwdc2023/10027/

感想

今回Oshibanaにスタンバイを実装してみましたが、思った以上に簡単に追加できました。
Oshibanaは推し活アプリであるため、常に推しに関するウィジェットが画面に表示されていることの需要も高いと感じています。
Appleとしても、iPhoneのホーム画面以外にもウィジェットを表示させる対応を実施し続けていることから、ユーザーがより長くウィジェットに触れることを重要視していることがうかがえます。
今後も様々な方法でユーザーに情報を通達する手段が確立されていくと思われるので、新しい機能が発表されたら積極的に活用していけるよう心がけたいです。