观察 SwiftUI TextField 中 NumberFormatter 的变化
Observe changes to NumberFormatter in SwiftUI TextField
我正在构建一个 macOS 应用程序,其中有一个可观察的 Formatter
对象,该对象使用 @AppStorage
来存储有效数字设置(请参阅 Formatter.swift
)。使用主应用程序结构 MyApp.swift
,将数字格式化程序作为环境对象传递给其他视图。在首选项 window SettingsView.swift
中,使用步进器调整有效数字。最后,数字格式化程序被分配给 ContentView.swift
中的文本字段以格式化输入。
问题是当设置视图中的有效数字发生变化时,内容视图中的文本字段不会自动更新它们的格式。文本标签会自动更新,因为它们直接读取应用存储值。但是文本字段没有观察到数字格式化程序的变化。如果我更改设置,重新启动应用程序,那么文本字段将正确显示更新后的格式。但是,当格式化程序的有效数字发生变化时,如何让文本字段更新?
Formatter.swift
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
}
}
MyApp.swift
import SwiftUI
@main
struct MyApp: App {
@StateObject var formatter = Formatter()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(formatter)
}
Settings {
SettingsView().environmentObject(formatter)
}
}
}
ContentView.swift
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)
}
}
SettingsView.swift
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)
}
}
向 TextFields 添加一个 .id
,这将在更改时强制重绘:
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)
编辑:
...或将 .id
设置为整个视图以重绘所有视图:
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)
基本逻辑是,每次id改变,SwiftUI都会重绘那部分。
你的 formatter.numberFormatter
getter 每次都在创建一个对象(实际上是 2 个对象,因为它被调用了两次)它在主体中被调用,你不能在 SwiftUI 中这样做。 Body 需要快,因为它被重复调用并且创建立即丢弃的对象会减慢它的速度。
您需要重新设计,以便在 @AppStorage
的默认设置更改时无需通知 SwiftUI,您需要自己收听默认设置,使用新值更新 numberFormatter,并告诉 SwiftUI 重绘。
一种方法是将 numberFormatter
设为 @Published
。然后使用 Combine 来监听您感兴趣的两个用户默认值的变化,使用这些值创建一个新的格式化程序,然后将管道的末端分配给 numberFormatter
已发布的 属性 ,这将通知 SwiftUI .这实际上就是 Combine 的 ObservableObject
的设计目的。您可以通过不使用它 @Published
而只是一个 let 来进一步优化它,然后手动发送 objectWillChange()
然后更新 let 格式化程序以使用默认值中的新参数。
我正在构建一个 macOS 应用程序,其中有一个可观察的 Formatter
对象,该对象使用 @AppStorage
来存储有效数字设置(请参阅 Formatter.swift
)。使用主应用程序结构 MyApp.swift
,将数字格式化程序作为环境对象传递给其他视图。在首选项 window SettingsView.swift
中,使用步进器调整有效数字。最后,数字格式化程序被分配给 ContentView.swift
中的文本字段以格式化输入。
问题是当设置视图中的有效数字发生变化时,内容视图中的文本字段不会自动更新它们的格式。文本标签会自动更新,因为它们直接读取应用存储值。但是文本字段没有观察到数字格式化程序的变化。如果我更改设置,重新启动应用程序,那么文本字段将正确显示更新后的格式。但是,当格式化程序的有效数字发生变化时,如何让文本字段更新?
Formatter.swift
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
}
}
MyApp.swift
import SwiftUI
@main
struct MyApp: App {
@StateObject var formatter = Formatter()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(formatter)
}
Settings {
SettingsView().environmentObject(formatter)
}
}
}
ContentView.swift
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)
}
}
SettingsView.swift
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)
}
}
向 TextFields 添加一个 .id
,这将在更改时强制重绘:
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)
编辑:
...或将 .id
设置为整个视图以重绘所有视图:
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)
基本逻辑是,每次id改变,SwiftUI都会重绘那部分。
你的 formatter.numberFormatter
getter 每次都在创建一个对象(实际上是 2 个对象,因为它被调用了两次)它在主体中被调用,你不能在 SwiftUI 中这样做。 Body 需要快,因为它被重复调用并且创建立即丢弃的对象会减慢它的速度。
您需要重新设计,以便在 @AppStorage
的默认设置更改时无需通知 SwiftUI,您需要自己收听默认设置,使用新值更新 numberFormatter,并告诉 SwiftUI 重绘。
一种方法是将 numberFormatter
设为 @Published
。然后使用 Combine 来监听您感兴趣的两个用户默认值的变化,使用这些值创建一个新的格式化程序,然后将管道的末端分配给 numberFormatter
已发布的 属性 ,这将通知 SwiftUI .这实际上就是 Combine 的 ObservableObject
的设计目的。您可以通过不使用它 @Published
而只是一个 let 来进一步优化它,然后手动发送 objectWillChange()
然后更新 let 格式化程序以使用默认值中的新参数。