dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

TRILLアプリでiOS14のWidgetに対応しました & Tips集

TRILL開発部の石田です。

TRILLでは、ver.3.5.0でiOS14で新しく登場したWidgetに対応しました。

f:id:trill_tech:20201007160943p:plain:w400

もともとToday Extensionには対応していたのですが、Widget Extensionは新しい機能ということでデザインや実装を見直しました。

Widget自体はWidgetKitフレームワークとSwiftUI用のウィジェットAPIを使って実装していくのですが、以下ではWidgetの実装で悩みやすい部分についてサンプル実装を紹介したいと思います。

サイズごとに別のViewを実装する

WidgetにはSmall、Medium、Largeの3種類のサイズがあります。

f:id:trill_tech:20201007161020p:plain

f:id:trill_tech:20201007161031p:plain

f:id:trill_tech:20201007161040p:plain

デフォルトでは、EntryViewで定義したViewがそれぞれのサイズに伸縮され描画されますが、WidgetFamilyを使って場合分けすることでサイズごとに別のViewを設定することができます。

例えば以下のようになります。

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

    @ViewBuilder
    var body: some View {
        switch family { 
        case .systemSmall: Text("Small")
        case .systemMedium: Text("Medium")
        case .systemLarge: Text("Large")
        default: Text("NotAvailable")
        }
    }
}

特定のサイズのみ対応する

上述の通り、Widgetには3種類のサイズがあります。 その中でもLargeには対応せず、SmallとMediumのみに対応したい場合があると思います。 そのときは、 supportedFamilies(_:))を使うことで、対応するサイズを定義することができます。

@main
struct SampleWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "widget", provider: Provider()) { entry in
            SampleWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

同じサイズで別のViewを設定する

Widgetは同じサイズでも別のViewを設定することができます。 例えば天気アプリのWidgetを考えたとき、Smallサイズでも「現在の天気」と「雨雲レーダー」の2種類を実装したいという場合です。 その場合、WidgetBundleを使い、Widgetを複数定義することで実装することができます。

@main
struct SampleWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        WeatherWidget()
        RainRadarWidget()
    }
}

定期実行する

ニュースアプリなどのWidgetでは、定期的にサーバにアクセスし、最新の情報をWidgetで提示したいと思います。 その場合、TimelineのTimelineReloadPolicyに所望の時間を設定することで実現できます。 下記は15分ごとに実行するサンプルとなります。

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    let currentDate = Date()
    let refreshDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)!
        
    ...
        
    let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
    completion(timeline)
}

Deep Linkを設定する

Widgetはデフォルトではタップするとアプリを開くだけです。 特定の画面に遷移させるにはwidgetURL(_:))を設定することで実現できます。

Smallサイズではタップ領域は1つだけですが、Mediumサイズ、Largeサイズには複数のタップ領域を設けることができます。 その場合、Linkを使うことで複数の導線を設定できます。

例えば下記のようになります。

var body: some View {
    VStack {
        Link(destination: hogeDeepLink) {
            Text("Hoge")
        }
        Link(destination: fugaDeepLink) {
            Text("Fuga")
        }
    }.widgetURL(piyoDeeplink)
}

まとめ

Widgetの実装において、「これってどうやるんだ?」という機能のサンプル実装を紹介しました。 Widgetはサイズが固定されており、機能的な制限もありますが、既存の機能とは異なる新しい価値をユーザに提供できるのではないかと思います。 まだ登場したばかり対応しているアプリは少ないですが、今後どのようなWidgetが出てくるかウォッチしながら、さらなるブラッシュアップをしていきたいと考えています。

TRILLではiOS限らずAndroid、サーバなどでエンジニアを積極採用中です。 Widgetなど新しい機能にも積極的に取り組めますので、ご興味がありましたら採用ページよりご連絡ください!