🎼

Xcode15でWidgetのマージンが変わった件をプレビューと共に対応した

2023/09/25に公開

先日リリースされたばかりのXcode15でこれまで動かしていたアプリをビルドすると、Widgetの表示内容が意図しない状態になります。具体的には、Widgetの周囲に余分な余白が追加され、レイアウトが崩れます。

これはとある拙作の記念日リマインダーという素敵なアプリなんですが、こういう風にまわりの余白が変わってきます(テキストの内容が若干異なることは気にしないでください)。

Xcode14 Xcode15

マージンを無効にして解決

これを解決するのはとても簡単で、WidgetのConfigurationに.contentMarginsDisabled()を指定してあげるだけです。

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

   var body: some WidgetConfiguration {
      StaticConfiguration(kind: kind, provider: NextAnniversaryProvider()) { entry in
         NextAnniversaryWidgetEntryView(entry: entry)
      }
      .supportedFamilies([.systemSmall])
      .configurationDisplayName("Next Anniversary")
      .description("It shows next anniversary.")
      .contentMarginsDisabled()
   }
}

iOS17以降だけをターゲットにするのであれば、.contentMarginsDisabled()を使わずにちょうどいいレイアウトを作るほうがいいなとは思いますが、急に新旧OS対応するのもしんどいので。

対応内容については以下を参考にしました。ありがとうございます。

https://qiita.com/an_apco/items/4ab26c90ab3c76120163

分岐処理は不要

ちなみに、iOS16以下でも動作するように、#available(iOSApplicationExtension 17.0, *)などを使って分岐する処理を書いている例も見受けられましたが、以前のOSの場合は何も動作しないようになっているらしいので不要です。

https://developer.apple.com/documentation/swiftui/widgetconfiguration/contentmarginsdisabled()

This modifier has no effect on operation system versions prior to iOS 17, watchOS 10, or macOS 14.

プレビューで動かない問題に直面

ここまでで対応完了してめでたしめでたしと思いきや、Xcodeのプレビューで見ると相変わらず余計な余白が入ってます。これをなんとかしたかったんですが、うまくいかず結構ハマりました。

もともとは以下のようなイメージのコードで、PreviewProviderを使ってサンプルデータを複数用意しつつプレビュー用のViewを用意していました。Widgetのデビュー時からある伝統的なやり方ですね。が、この方式では.contentMarginsDisabled()相当のことができません。

struct NextAnniversaryWidget_Previews: PreviewProvider {
   static var previews: some View {
      let anniversaries:[[AnniversaryPoint]] = [
            // なんかサンプルデータを沢山用意する
      ]

      ForEach(0..<anniversaries.count, id: \.self) { i in
         NextAnniversaryWidgetEntryView(entry: AnniversaryEntry(anniversaries: anniversaries[i], date: Date()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
            .environment(\.colorScheme, ColorScheme.light)
      }
   }

もしかしたら#Previewマクロを使ったプレビュー方法だとうまくいくかも?と思ってコードを書いてたんですけど、そもそもどう頑張ってもコンパイルエラーになってしまい、動作しなくて試せないという問題に直面。結構途方にくれました。

こういう書き方ですね。

#Preview("Entry1", as: .systemSmall) {
   NextAnniversaryWidget()
} timeline: {
   let data = [PreviewEntry.sample("太郎さんの誕生日"), PreviewEntry.sample("当日記念日",0)]
   AnniversaryEntry(anniversaries: data, date: Date())
}

最終的にフォーラムのこちらのスレッドで解決策を見つけたんですが、なんとiOS17以降を指定した@available属性をつけてあげるだけでよかった模様。

https://developer.apple.com/forums/thread/738034?answerId=765644022#765644022

@available(iOS 17.0, *)
#Preview("Entry1", as: .systemSmall) {
   NextAnniversaryWidget()
} timeline: {
   let data = [PreviewEntry.preview("太郎さんの誕生日"), PreviewEntry.preview("当日記念日",0)]
   AnniversaryEntry(anniversaries: data, date: Date())
}

うわー、まじかー、全然わからんかった。というわけで、めでたく動作している内容とプレビューの内容が一致するようになりましたということです。

お疲れさまでした。

GitHubで編集を提案

Discussion