格式化程序不工作的SwiftUI文本字段?
我试图更新一个数字字段,所以我使用了一个带有formatter:parameter集的TextField。它将数字格式化到输入字段中,但在编辑时不会更新绑定值。在没有指定格式化程序的情况下,TextField可以正常工作(在字符串上)。这是一个错误还是我遗漏了什么 更新:从Xcode 11 beta 3开始,它可以正常工作。现在,如果编辑数字文本字段,则在单击return后,绑定值将更新。每次按键后,字符串文本字段仍会更新。我猜他们不想在每次按键时都将要格式化的值发送到格式化程序,或者可能会有一个TextField的修饰符来告诉它这样做 请注意,API略有变化;旧的TextField init()已被弃用,并添加了一个新的titleKey字符串字段作为第一个参数,它在字段中显示为占位符文本格式化程序不工作的SwiftUI文本字段?,swiftui,Swiftui,我试图更新一个数字字段,所以我使用了一个带有formatter:parameter集的TextField。它将数字格式化到输入字段中,但在编辑时不会更新绑定值。在没有指定格式化程序的情况下,TextField可以正常工作(在字符串上)。这是一个错误还是我遗漏了什么 更新:从Xcode 11 beta 3开始,它可以正常工作。现在,如果编辑数字文本字段,则在单击return后,绑定值将更新。每次按键后,字符串文本字段仍会更新。我猜他们不想在每次按键时都将要格式化的值发送到格式化程序,或者可能会有一
struct TestView:View{
@State var someText=“更改我!”
@状态变量someNumber=123.0
var body:一些观点{
形式{
//Xcode 11 beta 2
//TextField($someText)
//TextField($someNumber,格式化程序:NumberFormatter())
//Xcode 11 beta 3
TextField(“Text”,Text:$someText)
TextField(“Number”,值:$someNumber,格式化程序:NumberFormatter())
垫片()
//如果更改第一个TextField值,更改将显示在此处
//如果更改第二个(数字),
//直到你点击回车键,它才开始*
文本(“文本:\(self.someText),数字:\(self.someNumber)”)
//按钮也会执行相同的操作,但会登录到控制台
按钮(操作:{print(“text:\(self.someText),number:\(self.someNumber)”)}){
文本(“日志值”)
}
}
}
}
如果键入第一个(字符串)文本字段,文本视图中的值将立即更新。如果编辑第二个(数字),则不会发生任何事情。
类似地,点击按钮会显示字符串的更新值,但不会显示数字。我只在模拟器中尝试过这一点。似乎在使用
值:
作为输入时,SwiftUI不会为用户点击的任何键重新加载视图。而且,正如您所提到的,当用户退出字段或提交它时,它会重新加载视图
另一方面,只要按下一个键,SwiftUI就会使用文本:
作为输入(立即)重新加载视图。我没有想到别的
在我的例子中,我为someNumber2
做了如下操作:
struct ContentView: View {
@State var someNumber = 123.0
@State var someNumber2 = "123"
var formattedNumber : NSNumber {
let formatter = NumberFormatter()
guard let number = formatter.number(from: someNumber2) else {
print("not valid to be converted")
return 0
}
return number
}
var body: some View {
VStack {
TextField("Number", value: $someNumber, formatter: NumberFormatter())
TextField("Number2", text: $someNumber2)
Text("number: \(self.someNumber)")
Text("number: \(self.formattedNumber)")
}
}
}
您可以使用绑定转换TextField的DoubleString
struct TestView:View{
@状态变量someNumber=123.0
var body:一些观点{
让someNumberProxy=绑定(
获取:{String(格式:%.02f),Double(self.someNumber))},
设置:{
如果let value=NumberFormatter().number(from:$0){
self.someNumber=value.doubleValue
}
}
)
返回VStack{
文本字段(“数字”,文本:someNumberProxy)
文本(“编号:\(someNumber)”)
}
}
}
您可以使用计算属性方法来解决此问题。(谢谢@icomputerbreak)
struct TestView:View{
@状态变量someNumber=123.0
var someNumberProxy:绑定{
装订(
获取:{String(格式:%.02f),Double(self.someNumber))},
设置:{
如果let value=NumberFormatter().number(from:$0){
self.someNumber=value.doubleValue
}
}
)
}
var body:一些观点{
VStack{
文本字段(“数字”,文本:someNumberProxy)
文本(“编号:\(someNumber)”)
}
}
}
我知道这有一些公认的答案,但上面的答案在输入值时似乎有着油滑的UX结果(至少对于双精度)。所以我决定写我自己的解决方案。它很大程度上受到了这里答案的启发,所以我会先尝试其他示例,然后再尝试这个示例,因为它有更多的代码
警告虽然我已经是一名iOS开发人员很长时间了,但我对SwiftUI还是相当陌生的。因此,这远远不是专家的建议。我很乐意
对我的方法的反馈,但要友善。到目前为止,这是可行的
好的,我的新项目。然而,我怀疑这是否和苹果的格式化程序一样有效
要执行此操作,您可以如下方式使用此新组件:
NewTextField(
"Value",
value: $bodyData.doubleData.value,
formatter: DoubleFormatter()
)
以下是我能想到的一些其他用法:
/// Just a simple passthrough formatter to use on a NewTextField
struct PassthroughFormatter: NewFormatter {
func toString(object: String) -> String {
return object
}
func toObject(string: String) -> String {
return string
}
func isFinal(string: String) -> Bool {
return true
}
func allowChange(to string: String) -> Bool {
return true
}
}
/// A formatter that converts empty strings to nil values
struct EmptyStringFormatter: NewFormatter {
func toString(object: String?) -> String {
return object ?? ""
}
func toObject(string: String) -> String? {
if !string.isEmpty {
return string
} else {
return nil
}
}
func isFinal(string: String) -> Bool {
return true
}
func allowChange(to string: String) -> Bool {
return true
}
}
方案B.由于使用
value:
和NumberFormatter
不起作用,我们可以使用定制的文本字段
。我已经将TextField
包装在struct
中,以便您可以尽可能透明地使用它
我对Swift和SwiftUI都很陌生,所以毫无疑问有一个更优雅的解决方案
struct IntField:视图{
@绑定变量int:int
@国家私有变量intString:String=“”
var body:一些观点{
返回文本字段(“,text:$intString)
.onReceive(Just(intString)){中的值
如果设i=Int(value){Int=i}
else{intString=“\(int)”}
}
.onAppear(表演:{
intString=“\(int)”
})
}
}
在ContentView中:
struct ContentView:View{
@状态变量testInt:Int=0
var body:一些观点{
回钉{
文本(“编号:”)
IntField(int:$testInt);
文本(“值:\(测试)”)
}
}
}
基本上,我们使用TextField(“…”,text:…)
,它的行为符合需要,并使用代理文本字段
与使用value:
和NumberFormatter
的版本不同,.onReceive
方法会立即响应,我们使用它来设置实际的整数值,它是绑定的。当我们这么做的时候,我们
NewTextField(
"Value",
value: $bodyData.doubleData.value,
formatter: DoubleFormatter()
)
/// Just a simple passthrough formatter to use on a NewTextField
struct PassthroughFormatter: NewFormatter {
func toString(object: String) -> String {
return object
}
func toObject(string: String) -> String {
return string
}
func isFinal(string: String) -> Bool {
return true
}
func allowChange(to string: String) -> Bool {
return true
}
}
/// A formatter that converts empty strings to nil values
struct EmptyStringFormatter: NewFormatter {
func toString(object: String?) -> String {
return object ?? ""
}
func toObject(string: String) -> String? {
if !string.isEmpty {
return string
} else {
return nil
}
}
func isFinal(string: String) -> Bool {
return true
}
func allowChange(to string: String) -> Bool {
return true
}
}
struct TextFieldRow<T>: View {
var value: Binding<T>
var title: String
var subtitle: String?
var valueProxy: Binding<String> {
switch T.self {
case is String.Type:
return Binding<String>(
get: { self.value.wrappedValue as! String },
set: { self.value.wrappedValue = $0 as! T } )
case is String?.Type:
return Binding<String>(
get: { (self.value.wrappedValue as? String).bound },
set: { self.value.wrappedValue = $0 as! T })
case is Double.Type:
return Binding<String>( get: { String(self.value.wrappedValue as! Double) },
set: {
let doubleFormatter = NumberFormatter()
doubleFormatter.numberStyle = .decimal
doubleFormatter.maximumFractionDigits = 3
if let doubleValue = doubleFormatter.number(from: $0)?.doubleValue {
self.value.wrappedValue = doubleValue as! T
}
}
)
default:
fatalError("not supported")
}
}
var body: some View {
return HStack {
VStack(alignment: .leading) {
Text(title)
if let subtitle = subtitle, subtitle.isEmpty == false {
Text(subtitle)
.font(.caption)
.foregroundColor(Color(UIColor.secondaryLabel))
}
}
Spacer()
TextField(title, text: valueProxy)
.multilineTextAlignment(.trailing)
}
}
}
struct UserDetails: View {
@ObservedObject var userViewModel: UserViewModel
init(user: PedalUserViewModel) {
userViewModel = user
}
var body: some View {
VStack {
Form {
Section(header: Text("Personal Information")) {
TextField("Age", text: $userViewModel.userAge)
.keyboardType(.numberPad)
.modifier(DoneButton())
}
}
}
}
}
class UserViewModel: ObservableObject {
@ObservedObject var currentUser: User
var anyCancellable: AnyCancellable?
init(currentUser: User) {
self.currentUser = currentUser
self.anyCancellable = self.currentUser.objectWillChange.sink{ [weak self] (_) in
self?.objectWillChange.send()
}
}
var userAge: String {
get {
String(currentUser.userAge)
}
set {
currentUser.userAge = Int(newValue) ?? 0
}
}
}
import Foundation
import SwiftUI
struct FormattedTextField<T: Equatable>: View {
let placeholder: LocalizedStringKey
@Binding var value: T
let formatter: Formatter
var valueChanged: ((T) -> Void)? = nil
var editingChanged: ((Bool) -> Void)? = nil
var onCommit: (() -> Void)? = nil
@State private var isUpdated = false
var proxy: Binding<String> {
Binding<String>(
get: {
formatter.string(for: value) ?? ""
},
set: {
var obj: AnyObject? = nil
formatter.getObjectValue(&obj, for: $0, errorDescription: nil)
if let newValue = obj as? T {
let notifyUpdate = newValue == value
value = newValue
valueChanged?(value)
if notifyUpdate {
isUpdated.toggle()
}
}
}
)
}
var body: some View {
TextField(
placeholder,
text: proxy,
onEditingChanged: { isEditing in
editingChanged?(isEditing)
},
onCommit: {
onCommit?()
}
)
.tag(isUpdated ? 0 : 1)
}
}