Xcode 15 iOS 17小组件适配必看:Widget needs to adopt container background

您所在的位置:网站首页 小组件开发 Xcode 15 iOS 17小组件适配必看:Widget needs to adopt container background

Xcode 15 iOS 17小组件适配必看:Widget needs to adopt container background

2023-11-23 01:31| 来源: 网络整理| 查看: 265

Image.png

当你更新了 Xcode 15,如果你的 app 中有小组件的代码,在 preview 的页面就会出现上图的预览错误。提示你:Widget needs to adopt container background。虽然这种 breaking change 看起来很吓人,但是适配起来还是很容易的,下面将一一列出 iOS17 小组件有关的 适配方案。

containerBackground

Image.png

iOS 17 中新增了一个模式 stand by。这个模式下手机横屏,可以并排显示两个小号的小组件。因为手机此时属于息屏状态,因此苹果建议小组件的背景图层隐藏,这样整体的风格也更搭。

Image.png

同时锁屏小组件也带到了iPadOS 17 上。相比 iPhone 的锁屏小组件,iPad 因为尺寸更大,因此锁屏小组件的尺寸也支持了一个更大的尺寸。可以显示小号的方形小组件。

IMG_0086.png

苹果为了强推小组件这两个功能,要求所有小组件都必须声明适配接口告知系统小组件的背景图层。这样当小组件显示在 standby 和 iPad 锁屏上时,渲染时可以隐藏背景图层。

如果你的 app 只需要支持 iOS 17(不会真有人这么幸福吧),那么你只需要在 view 实现这个 containerBackground 就可以了,把背景图层像 background 一样放在 content 闭包里。

.containerBackground(for: .widget) { // 背景view Color.black }

但是做 iOS 的开发者运气都不会太差,你大概率会得到一个 error:

Image.png

所以你需要自定义一个类似的方法,判断系统版本以向前兼容:

extension View { @ViewBuilder func widgetBackground(_ backgroundView: some View) -> some View { if #available(iOS 17.0, *) { containerBackground(for: .widget) { backgroundView } } else { background(backgroundView) } } }

如果你的小组件view不在 app 中展示,那么上述的方法已经足够用了。但是如果你的小组件要在 app 中展示,比如我目前的情况,小组件会在 app 中展示以让用户进行一些主题设置。那么你就会发现 containerBackground 的背景 view 在 app 中不会展示。

Image.png

因此需要再加一层判断,如果在 app 中正常显示背景图层。

extension View { @ViewBuilder func widgetBackground(_ backgroundView: some View) -> some View { if Bundle.main.bundlePath.hasSuffix(".appex"){ if #available(iOS 17.0, *) { containerBackground(for: .widget) { backgroundView } } else { background(backgroundView) } } else { background(backgroundView) } } } contentMarginsDisabled

在配置完 containerBackground 后小组件可以正常运行了,但是很快你就发现一个问题:小组件尺寸变小了。比如下图里黑色是小组件的背景色,外围的一圈是 safeArea。

Simulator Screenshot - iPhone 14 Pro - 2023-08-23 at 17.21.05.png

原因和上一节讲的一样,因为小号小组件会出现在 standby 中,然而 standby 的尺寸更大。因此为了让小组件可以适配不同的尺寸,系统统一给小组件加了一个 safeArea。因此我们的小组件变小了。

Image.jpeg

如果你的小组件可以针对尺寸大小自适应的话,或者不在乎 standby 中的样式,可以直接在 WidgetConfiguration 中配置关闭系统统一发放的边距。需要注意的是这个配置在 widget 上,不在 view 上。

StaticConfiguration(kind: WorkerWidgetKind.workerSticker.rawValue, provider: WorkerStickerProvider()) { entry in WorkerStickerEntryView(entry: entry) } .contentMarginsDisabled()

如果你打算针对不同的 margin 处理布局,你也可以通过全局变量获取到 margin 值。

@Environment(\.widgetContentMargins) var margins extension EnvironmentValues { /// A property that identifies the content margins of a widget. /// /// The content margins of a widget depend on the context in which it appears. The /// system applies default content margins. However, if you disable automatic application of /// default content margins with ``WidgetConfiguration/contentMarginsDisabled()``, the /// system uses the `widgetContentMargins` property in combination with ``View/padding(_)`` /// to selectively apply default content margins. /// @available(iOS 17.0, watchOS 10.0, macOS 14.0, *) @available(tvOS, unavailable) public var widgetContentMargins: EdgeInsets { get } }

但是用这个值会有点痛苦,因为苹果常规操作这个全局变量 iOS 17 only。View 相关全局变量的如果要向前兼容需要包在一个 container view 里,有些小麻烦。

showsWidgetContainerBackground

如果你的小组件某些 UI 要针对在无背景场景做调整,需要通过 showsWidgetContainerBackground 全局变量来判断。

以我的小组件周五日历为例,本来有背景中间的标题文字视觉就是居中的。但是如果没有背景,标题文字的视觉平衡就不在中间了。而且我的标题文字本来有一个透明度,但是在锁屏上因为没有背景了,有透明度反而让文字看不清了。

Image.png Image.png

因此我需要针对在锁屏上做一点区分处理。如果在锁屏上就在背景上画一个边框。

struct FridayWidgetView: View { @Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground var body: some View { ZStack { if !showsWidgetContainerBackground { RoundedRectangle(cornerRadius: 12) .stroke(Color.black, lineWidth: 3) } } .widgetBackground(viewModel.config.theme.coverView) } }

Image.png

特大喜讯,苹果工程师良心发现这个全局变量可以向前兼容。

下图是适配以后的样式。

截屏 2023-08-23 17.09.59 2.jpeg containerBackgroundRemovable

小号的方形小组件可以在展示在锁屏上又引入了另外一个问题,锁屏中系统会对图片进行黑白处理,某些小组件的核心内容是图片的话不适合展示在图片上。

Image.png

下面的示例图是我开发的打工人小组件,可以看到显示在 iPad 锁屏上显示效果差到无法用。

Image.png Image.png

为了解决这个问题,需要在小组件配置中声明containerBackgroundRemovable(false)。

struct WeekCalendarWidget: Widget { var body: some WidgetConfiguration { IntentConfiguration(kind: WorkerWidgetKind.weekCalendar.rawValue, intent: WeekCalendarIntent.self, provider: WeekCalendarTimelineProvider()) { entry in WeekCalendarEntryView(entry: entry) } .configurationDisplayName("打工人周历") .description("熬夜可以,熬夜工作可不行") .containerBackgroundRemovable(false) .contentMarginsDisabled() } }

配置了这个选项后小组件就不会出现在 iPad 锁屏小组件列表中。

Image.png widgetRenderingMode

如果要针对图片在不同场景中做单独处理,也可以通过 widgetRenderingMode 这个全局变量判断当前的渲染模式。坏消息:兼容性iOS 16 +。

Image.png

@available(iOS 16.0, watchOS 9.0, macOS 13.0, *) @available(tvOS, unavailable) extension EnvironmentValues { /// The widget's rendering mode, based on where the system is displaying it. /// /// You can read the rendering mode from the environment values using this /// key. /// /// ``` swift /// @Environment(\.widgetRenderingMode) var widgetRenderingMode /// ``` /// /// Then modify the widget's appearance based on the mode. /// /// ``` swift /// var body: some View { /// ZStack { /// switch renderingMode { /// case .fullColor: /// Text("Full color") /// case .accented: /// ZStack { /// Circle(...) /// VStack { /// Text("Accented") /// .widgetAccentable() /// Text("Normal") /// } /// } /// case .vibrant: /// Text("Full color") /// default: /// ... /// } /// } /// } /// ``` public var widgetRenderingMode: WidgetRenderingMode } Reference

Bring widgets to new places



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3