To bring my widgets to iPad lock screen, i need to detect my widget that has a background to display different contents, watch the wwdc 23 video it tells we can use .showsWidgetContainerBackground environment to apply that goal, but the problem is this code wouldn't compile because .showsWidgetContainerBackground only available in iOS 17
@available(iOS 14.0, *)
struct MyWidgetView: View
{
@Environment(\.showsWidgetContainerBackground) var showsBackground
var body: some View {
...
}
}
So, my solution is use a environment wrapper, like this:
@available(iOS 17.0, *)
struct EnvironmentWrapper: View
{
@Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground
var body: some View {
VStack(alignment: .leading) {
Text("Wrapper Has Background: \(showsWidgetContainerBackground ? "YES" : "NO")")
}
}
}
extension View
{
var showsWidgetContainerBackground: Bool {
if #available(iOS 17.0, *)
{
return EnvironmentWrapper().showsWidgetContainerBackground
}
else
{
return true
}
}
}
But it didn't work, this is the example:
@available(iOS 17.0, *)
struct EnvironmentWrapper: View
{
@Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground
var body: some View {
VStack(alignment: .leading) {
Text("Wrapper has background: \(showsWidgetContainerBackground ? "YES" : "NO")")
}
}
}
@available(iOS 17.0, *)
struct ExampleView: View
{
@Environment(\.showsWidgetContainerBackground) var showsBackground
var body: some View {
VStack(alignment: .leading) {
Text("Read from self: \(showsBackground ? "YES" : "NO")")
EnvironmentWrapper()
Text("Read from wrapper: \(EnvironmentWrapper().showsWidgetContainerBackground ? "YES" : "NO")")
}
.font(.system(size: 12, weight: .medium))
}
}
As you can see, if i read it directly from self, it works, but if i read from outside, it always return true
i also try to load a view first in the wrapper, but it didn't work either
func getShowsBackground() -> Bool
{
let _ = body
return showsWidgetContainerBackground
}
Ok, after a little digging(actually a lot...), i think i finally have a solution, just in case anyone dealing the some issue:
// First we create a fallback variable environment
struct WidgetEnvShowsContainerBackgroundKey: EnvironmentKey
{
static let defaultValue: Bool = true
}
extension EnvironmentValues
{
var widgetEnvShowsContainerBackground: Bool {
set { self[WidgetEnvShowsContainerBackgroundKey.self] = newValue }
get { self[WidgetEnvShowsContainerBackgroundKey.self] }
}
}
extension View
{
func envShowsContainerBackground(_ value: Bool) -> some View
{
environment(\.widgetEnvShowsContainerBackground, value)
}
}
// Magic happens here
struct WidgetEnvironmentReader<Content: View>: View
{
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content)
{
self.content = content
}
var body: some View {
if #available(iOS 17.0, *)
{
WidgetEnvironmentViewBuilder(content: content)
}
else
{
content()
}
}
}
extension WidgetEnvironmentReader
{
@available(iOS 17.0, *)
struct WidgetEnvironmentViewBuilder<C: View>: View
{
@Environment(\.showsWidgetContainerBackground) var showsWidgetContainerBackground
var content: () -> C
init(@ViewBuilder content: @escaping () -> C)
{
self.content = content
}
var body: some View {
content()
.envShowsContainerBackground(showsWidgetContainerBackground)
}
}
}
// Now we are free to use .showsWidgetContainerBackground in the older os version
@available(iOS 14.0, *)
struct WidgetView: View
{
@Environment(\.widgetEnvShowsContainerBackground) var widgetEnvShowsContainerBackground: Bool
var body: some View {
Text("Background: \(widgetEnvShowsContainerBackground ? "YES" : "No"), \(Date(), style: .relative)")
}
}
@available(iOS 14.0, *)
struct WidgetEntryView: View
{
let entry: Entry
var body: some View {
WidgetEnvironmentReader {
WidgetView()
}
.makeContainerBackground()
}
}