Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

324
Vistas
How to use @State and @Environment properties in a struct that can produce a view but isn't a view?

I’m trying to create a view style for customizing a custom SwiftUI view that I’m developing. I declared a protocol with a makeBody function, I’m storing the style as a @State property so it can be updated, and I’m calling makeBody from inside a view. This technique closely follows what SwiftUI already does but there’s one thing that I can’t figure out.

Problem

There’s one important behavior that the first-party view styles have that I can’t replicate — that’s using @State, @Environment, or any other SwiftUI property wrapper inside the custom view style.

For example, I can implement the following custom ButtonStyle that just displays the value of the current color scheme as the button’s content.

struct MyButtonStyle: ButtonStyle {

    @Environment(\.colorScheme) var colorScheme

    func makeBody(configuration: Configuration) -> some View {
        Text(String(describing: colorScheme))
    }

}

struct MyView: View {

    var body: some View {
        Button("foo", action: {}).buttonStyle(MyButtonStyle())
    }

}

When I preview MyView with a .dark preferred color scheme, the colorScheme property of MyButtonStyle is updated, the makeBody function is called again, and the text reads “dark”. This is all expected.

Now, let’s take a look at the custom view style that I created.

struct MyCustomStyle /* : CustomStyle */ {

    @Environment(\.colorScheme) var colorScheme

    @ViewBuilder func makeBody(configuration: CustomStyleConfiguration) -> some View {
        Text(String(describing: colorScheme))
    }

}

struct MyView: View {

    @State var style = MyCustomStyle()

    var body: some View {
        style.makeBody(configuration: .init())
    }

}

In the above scenario, when I preview MyView with a .dark preferred color scheme, the colorScheme property is not updated, and the text reads “light” even though the label's foreground color has correctly picked up the change.

Question

How to make @State and @Environment properties work in the custom view style, just as they work in ButtonStyle and in views, without introducing any boilerplate to the implementation of makeBody function?

I'm certain that it's possible because all the first-party view styles already support this, without styles conforming to View, and without them implementing the private _makeView API that manually adds dependencies to SwiftUI's graph (at least according to the module interface of SwiftUI).

What I Tried That Didn't Work

I tried initializing the view style inside a computable body of a view that wraps MyView. That doesn't work either.

struct ContentView: View {

    var body: some View {
        MyView(style: MyCustomStyle())
    }

}

Unacceptable Answers

Wrapping the return value of the custom makeBody function in a separate view, with colorScheme property being part of that view, is a known workaround that results in correct behavior. Answers suggesting this as a workaround will not be accepted because this question is all about avoiding that.

Answers saying that it is impossible to use @Environment or @State outside of a type conforming to View will not be accepted (because it is possible, see ButtonStyle), unless they can prove that private API is what drives this.

over 4 years ago · Santiago Trujillo
1 Respuestas
Responde la pregunta

0

We don't have access to whatever the EnvironmentKey is that corresponds with ColorScheme, but its defaultValue must be light. That's why you can ever get a value at all, outside of a view. But your type won't ever actually be supplied with EnvironmentValues unless it's a View, ViewModifier, ButtonStyle, …Apple doesn't actually offer an exhaustive list of protocols that are considered "part of the view hierarchy".

But if you don't conform to one of them, then relying not on the environment, but rather, dependency injection, is necessary. E.g.

struct MyCustomStyle {
  init(environment: EnvironmentValues) {
    colorScheme = environment.colorScheme
  }

  private let colorScheme: ColorScheme

  @ViewBuilder func makeBody() -> some View {
    Text(String(describing: colorScheme))
  }
}
struct ContentView: View {
  @Environment(\.self) private var environment

  var style: MyCustomStyle { .init(environment: environment) }

  var body: some View {
    style.makeBody()
  }
}

Your question also mentions State, but you didn't supply an example for it. You probably just need to conform to DynamicProperty for whatever you're doing with State.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda