Estoy creando una aplicación para macOS en la que tengo un objeto Formatter
observable que usa @AppStorage
para almacenar la configuración de dígitos significativos (ver Formatter.swift
). El formateador de números se pasa a las otras vistas como un objeto de entorno utilizando la estructura principal de la aplicación MyApp.swift
. En la ventana de preferencias SettingsView.swift
, los dígitos significativos se ajustan mediante escaladores. Finalmente, el formateador de números se asigna a los campos de texto en ContentView.swift
para formatear la entrada.
El problema es que los campos de texto en la vista de contenido no actualizan automáticamente su formato cuando se cambian los dígitos significativos en la vista de configuración. Las etiquetas de texto se actualizan automáticamente porque leen los valores de almacenamiento de la aplicación directamente. Pero los campos de texto no observan el cambio en el formateador de números. Si cambio la configuración, reinicio la aplicación, los campos de texto mostrarán correctamente el formato actualizado. Pero, ¿cómo le digo al campo de texto que se actualice cuando cambien los dígitos significativos del formateador?
import Foundation import SwiftUI class Formatter: ObservableObject { @AppStorage("minSigDigits") var minSigDigits = 1 @AppStorage("maxSigDigits") var maxSigDigits = 6 var numberFormatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.usesSignificantDigits = true formatter.minimumSignificantDigits = self.minSigDigits formatter.maximumSignificantDigits = self.maxSigDigits return formatter } func resetValues() { self.minSigDigits = 1 self.maxSigDigits = 6 } }
import SwiftUI @main struct MyApp: App { @StateObject var formatter = Formatter() var body: some Scene { WindowGroup { ContentView().environmentObject(formatter) } Settings { SettingsView().environmentObject(formatter) } } }
import SwiftUI struct ContentView: View { @State private var min: Double = 0.0 @State private var max: Double = 1.0 @EnvironmentObject var formatter: Formatter var body: some View { VStack { Text("Min Sig. Digits = \(formatter.minSigDigits)") TextField("enter min value", value: $min, formatter: formatter.numberFormatter) Text("Max Sig. Digits = \(formatter.maxSigDigits)") TextField("enter max value", value: $max, formatter: formatter.numberFormatter) } .padding() .frame(width: 400, height: 300) } }
import SwiftUI struct SettingsView: View { @EnvironmentObject var formatter: Formatter var body: some View { VStack { Stepper("Min Significant Digits: \(formatter.minSigDigits)", value: $formatter.minSigDigits, in: 1...5) Stepper("Max Significant Digits: \(formatter.maxSigDigits)", value: $formatter.maxSigDigits, in: 6...10) Button("Reset Values") { formatter.resetValues() } } .padding() .frame(width: 300, height: 200) } }
Agregue un .id
a TextFields, que forzará un redibujado en el cambio:
Text("Min Sig. Digits = \(formatter.minSigDigits)") TextField("enter min value", value: $min, formatter: formatter.numberFormatter) .id(formatter.minSigDigits) Text("Max Sig. Digits = \(formatter.maxSigDigits)") TextField("enter max value", value: $max, formatter: formatter.numberFormatter) .id(formatter.maxSigDigits)
EDITAR: ... o establecer un .id
en la vista completa para volver a dibujar todo:
VStack { Text("Min Sig. Digits = \(formatter.minSigDigits)") TextField("enter min value", value: $min, formatter: formatter.numberFormatter) Text("Max Sig. Digits = \(formatter.maxSigDigits)") TextField("enter max value", value: $max, formatter: formatter.numberFormatter) } .id(formatter.maxSigDigits + formatter.minSigDigits)
La lógica básica es que, cada vez que cambie la identificación, SwiftUI volverá a dibujar esa parte.
Su formatter.numberFormatter
getter está creando un objeto cada vez (en realidad, 2 objetos porque se llama dos veces) se llama en el cuerpo, no puede hacer eso en SwiftUI. El cuerpo debe ser rápido porque se llama repetidamente y la creación de objetos que se descartan inmediatamente lo ralentiza.
Debe rediseñar para que, en lugar de notificar a SwiftUI cuando cambie el valor predeterminado con @AppStorage
, deba escuchar los valores predeterminados usted mismo, actualizar el formateador de números con los nuevos valores y decirle a SwiftUI que vuelva a dibujar.
Una forma sería hacer que numberFormatter
@Published
. Luego use Combine para escuchar los cambios en los dos valores predeterminados de usuario que le interesan, cree un nuevo formateador usando los valores, luego asigne el final de la canalización a la propiedad publicada numberFormatter
que notificará a SwiftUI. Esto es realmente para lo que está diseñado el ObservableObject
de Combine. Puede optimizar esto aún más al no tenerlo @Published
y en su lugar solo un let y luego enviar manualmente el objectWillChange()
y luego actualizar el formateador let para usar los nuevos parámetros de los valores predeterminados.