こんにちは。Yahoo!ショッピングでiOS開発を担当している大前です。
今回は、Yahoo!ショッピングiOSアプリで対応した新しい「ロック画面に配置できるウィジェット」(以下、ロック画面ウィジェット)について紹介します。また、細かい実装例やその際に注意したポイントなどを交えてお話しできればと思います。
Yahoo!ショッピングiOSアプリのロック画面ウィジェットについてご紹介
Yahoo!ショッピングでは、Appleの新しいiOSバージョン「iOS 16」より新たに提供が開始されたロック画面ウィジェットに対応しました。
その日の日替わりクーポンを確認できる「日替わりクーポン」ウィジェット、その日のお得なキャンペーン情報が確認できる「イベント情報」ウィジェットの2つのウィジェットを用意しています。
ロック画面にウィジェットを配置しておくことで、アプリを開かなくてもお得な情報をいち早くゲットできます。お好きなウィジェットをロック画面に追加して、ぜひお買い物にお役立てください!
ロック画面ウィジェットについて
iOS 16以上で利用可能なロック画面ウィジェットは、watchOSのコンプリケーションという機能をヒントに移植され、一目でわかる価値の高い情報を提供し、タップすればアプリ内の関連する場所に移動することを目的としているウィジェットです。このウィジェットは、既存のWidgetFamilyに追加される形で提供され、「accessoryCircular」、「accessoryRectangular」および「accessoryInline」の3種類が追加されました。
import SwiftUI
import WidgetKit
struct WidgetView: View {
@Environment(\.widgetFamily) var family: WidgetFamily
@ViewBuilder var body: some View {
Group {
switch family {
// iOS 14から追加されたホーム画面上ウィジェット
case .systemSmall:
Text("小")
case .systemMedium:
Text("中")
case .systemLarge:
Text("大")
// iPadOS 15から追加
case .systemExtraLarge:
Text("特大")
// [NEW!] iOS 16から追加されたロック画面上ウィジェット
case .accessoryCircular:
ZStack {
if #available(iOSApplicationExtension 16.0, *) {
AccessoryWidgetBackground()
}
Text("円形")
}
case .accessoryRectangular:
ZStack {
if #available(iOSApplicationExtension 16.0, *) {
AccessoryWidgetBackground()
}
Text("長方形")
}
case .accessoryInline:
Text("直線形")
// watchOS 9以降専用
case .accessoryCorner:
Text("watchOSの文字盤の端のウィジェット")
@unknown default:
fatalError()
}
}
}
}
accessoryCircular
accessoryCircularはロック画面の時計の下に配置することが可能で、形は円状となっています。ロック画面の時計の下には合計4マス分のウィジェットを配置でき、「accessoryCircular」は1マス分の大きさとなっています。「accessoryCircular」は簡単な情報、ゲージまたは進捗状況の表示に最適なウィジェットとなっています。
情報を表示する幅がとても狭く、ユーザが一目で理解できるようなデザインをおすすめします。
accessoryRectangular
accessoryRectangularはロック画面の時計の下に配置することが可能で、形は長方形となっています。大きさは、2マス分の大きさとなっており、複数行のテキスト、小さなグラフまたはチャートの表示に最適です。
ロック画面ウィジェットとしてはサイズが大きく、より細かな情報を載せることが可能で採用しやすいウィジェットかと思います。
accessoryInline
accessoryInlineは、ロック画面の時計の上に配置することが可能で、形としては直線のような見た目です。このウィジェットはロック画面に対して1つだけ配置でき、テキストや画像を表示させることが可能です。
実装したウィジェットの左に、日付および曜日が表示されるので、日付や曜日と関連した情報を表示させると良いと思います。
また、iOSにおけるロック画面ウィジェットの見た目についてもお話しします。ロック画面ウィジェットはvibrantというレンダリングモードに基づきレンダリングされます。vibrantはテキスト、画像またはゲージのようなViewを一度単一色になるように脱色し、ロック画面の背景に合わせて自動的に着色されます。赤や黒などの細かい色の指定はできないことに注意してください。
実装にあたって注意したポイント
Yahoo!ショッピングほどの規模になると、ロック画面ウィジェットからの通信は何千rps、何万rpsに達するか予測が非常に困難です。また、ロック画面ウィジェットで用いているWeb APIは、iOSアプリ内の機能やWebなど他のプラットフォームからのアクセスがあります。そのため、ロック画面ウィジェットの影響で、他の箇所への影響を極力抑えるような実装にする必要があり、想定外の負荷に配慮したタイムライン設計になるよう努めました。
具体的には、ウィジェットからのリクエスト頻度をサーバサイドで変更できるようにするため、「ウィジェット設定取得API」を別途用意しました。メンテナンスモードのフラグおよび更新頻度時間を「ウィジェット設定取得API」からあらかじめ取得することで、ウィジェットの通信制御を行っています。
例として、日替わりクーポン情報取得までのシーケンス図を以下に示します。
まず、ウィジェットは「ウィジェット設定取得API」にリクエストします。そこでは、ウィジェットの更新頻度およびメンテナンスモードかどうかのウィジェットの設定情報を取得します。メンテナンスモードの場合は、それ以降のAPIリクエストは行わず再度1分後に「ウィジェット設定取得API」にリクエストします。メンテナンスモードでない場合は、「日替わりクーポン情報取得API」にリクエストします。日替わりクーポンに関する情報を取得後、先程取得したウィジェットの更新頻度に基づき、次のリクエストタイミングを設定します。
具体的なタイムラインのコードは以下の通りです。
struct DailyCouponTimelineProvider: TimelineProvider {
// ウィジェットの設定取得API
private let widgetSettingRepository = WidgetSettingRepositoryImpl()
// 日替わりクーポン情報取得API
private let dailyCouponRepository = DailyCouponRepositoryImpl()
...
func getTimeline(in context: Context, completion: @escaping (Timeline<DailyCouponEntry>) -> Void) {
widgetSettingRepository.request { widgetSettingResponse in
// メンテナンスモード判定
guard !widgetSettingResponse.isMaintenance else {
// メンテナンスモードだった場合は、1分後に更新する
let currentDate = Date()
guard let refreshDate = Calendar(identifier: .gregorian).date(byAdding: .minute, value: 1, to: currentDate) else { return }
createEntry(widgetSettingResponse: nil, dailyCouponResponse: nil, date: currentDate) { entry in
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
completion(timeline)
}
return
}
dailyCouponRepository.request { dailyCouponResponse in
let currentDate = Date()
// timelineIntervalMinutes 分後に更新する、値はウィジェットの設定取得APIから取得している
guard let refreshDate = Calendar(identifier: .gregorian).date(byAdding: .minute, value: widgetSettingResponse.timelineIntervalMinutes, to: currentDate) else { return }
// エントリーの作成
createEntry(widgetSettingResponse: widgetSettingResponse, dailyCouponResponse: dailyCouponResponse, date: currentDate) { entry in
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
completion(timeline)
}
}
}
}
...
private func createEntry(
widgetSettingResponse: WidgetSettingResponse?,
dailyCouponResponse: DailyCouponResponse?,
date: Date,
completion: @escaping (_ entry: DailyCouponEntry) -> Void
) {
let couponTitle = dailyCouponResponse?.couponTitle ?? "クーポンの情報が取得できませんでした。"
let entry = DailyCouponEntry(date: date, dailyCouponEntryType: .normal(title: couponTitle), linkUrl: dailyStatusResponse?.couponLinkUrl)
completion(entry)
}
}
上記のように、メンテナンスモードや更新頻度を外部から簡単に操作できるように実装したことで、負荷が大きくなってきた時に更新頻度を抑えることが可能ですし、メンテナンスモードにすることで日替わりクーポン取得APIへのアクセスを一時的に中断し他への影響を抑えることが可能です。
おわりに
いかがでしたか? ロック画面ウィジェット自体の実装は簡単ですが、他のプラットフォームに影響が出にくい設計をしています。ロック画面ウィジェットの実装の参考になれば幸いです。
Yahoo!ショッピングでは、iOSの新機能を積極的にキャッチアップし、早い段階で検証を重ね、いち早く便利な体験を提供すべく開発に取り組んでいます。また、今後も引き続き改善を続けていきますので、どうぞよろしくお願いします。
また、Yahoo!ショッピングでは一緒にサービスを盛り上げてくれるメンバーを募集中です! iOSエンジニアとして新しい技術を用いた開発に挑戦したい方は、ぜひ採用ページをご覧ください!
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました