如何创建只接受数字和单个点的SwiftUI TextField? [英] How to create SwiftUI TextField that accepts only numbers and a single dot?

查看:76
本文介绍了如何创建只接受数字和单个点的SwiftUI TextField?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何创建一个swiftui文本字段,允许用户仅输入数字和单个点?换句话说,它会在用户输入时逐位检查数字,如果输入是数字或点,并且文本字段没有其他点,则接受该数字,否则将忽略该数字条目.不能使用步进器.

How to create a swiftui textfield that allows the user to only input numbers and a single dot? In other words, it checks digit by digit as the user inputs, if the input is a number or a dot and the textfield doesn't have another dot the digit is accepted, otherwise the digit entry is ignored. Using a stepper isn't an option.

推荐答案

SwiftUI不允许您为 TextField 指定一组允许的字符.实际上,这与UI本身无关,而与您如何管理背后的模型有关.在这种情况下,模型是 TextField 后面的文本.因此,您需要更改视图模型.

SwiftUI doesn't let you specify a set of allowed characters for a TextField. Actually, it's not something related to the UI itself, but to how you manage the model behind. In this case the model is the text behind the TextField. So, you need to change your view model.

如果在 @Published 属性上使用 $ 符号,则可以访问 @Published 属性本身.然后,您可以将自己的订阅者附加到发布者,并执行所需的任何检查.在这种情况下,我使用了 sink 函数将基于闭包的订阅者附加到发布者:

If you use the $ sign on a @Published property you can get access to the Publisher behind the @Published property itself. Then you can attach your own subscriber to the publisher and perform any check you want. In this case I used the sink function to attach a closure based subscriber to the publisher:

/// Attaches a subscriber with closure-based behavior.
///
/// This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber.
/// - parameter receiveValue: The closure to execute on receipt of a value.
/// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream.
public func sink(receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable

实现:

import SwiftUI
import Combine

class ViewModel: ObservableObject {
    @Published var text = ""
    private var subCancellable: AnyCancellable!
    private var validCharSet = CharacterSet(charactersIn: "1234567890.")

    init() {
        subCancellable = $text.sink { val in
            //check if the new string contains any invalid characters
            if val.rangeOfCharacter(from: self.validCharSet.inverted) != nil {
                //clean the string (do this on the main thread to avoid overlapping with the current ContentView update cycle)
                DispatchQueue.main.async {
                    self.text = String(self.text.unicodeScalars.filter {
                        self.validCharSet.contains($0)
                    })
                }
            }
        }
    }

    deinit {
        subCancellable.cancel()
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        TextField("Type something...", text: $viewModel.text)
    }
}

重要说明:

  • $ text ( @Published 属性上的 $ 符号)为我们提供了 Published< String>类型的对象.即发布者
  • $ viewModel.text ( @ObservableObject 上的 $ 符号)为我们提供了 Binding< String>
  • $text ($ sign on a @Published property) gives us an object of type Published<String>.Publisher i.e. a publisher
  • $viewModel.text ($ sign on an @ObservableObject) gives us an object of type Binding<String>

那是完全不同的两件事.

That are two completely different things.

编辑:如果需要,您甚至可以使用此行为创建自己的自定义 TextField .假设您要创建一个 DecimalTextField 视图:

EDIT: If you want you can even create you own custom TextField with this behaviour. Let's say you want to create a DecimalTextField view:

import SwiftUI
import Combine

struct DecimalTextField: View {
    private class DecimalTextFieldViewModel: ObservableObject {
        @Published var text = ""
        private var subCancellable: AnyCancellable!
        private var validCharSet = CharacterSet(charactersIn: "1234567890.")

        init() {
            subCancellable = $text.sink { val in                
                //check if the new string contains any invalid characters
                if val.rangeOfCharacter(from: self.validCharSet.inverted) != nil {
                    //clean the string (do this on the main thread to avoid overlapping with the current ContentView update cycle)
                    DispatchQueue.main.async {
                        self.text = String(self.text.unicodeScalars.filter {
                            self.validCharSet.contains($0)
                        })
                    }
                }
            }
        }

        deinit {
            subCancellable.cancel()
        }
    }

    @ObservedObject private var viewModel = DecimalTextFieldViewModel()

    var body: some View {
        TextField("Type something...", text: $viewModel.text)
    }
}

struct ContentView: View {
    var body: some View {
        DecimalTextField()
    }
}

通过这种方式,您可以在编写时使用自定义文本字段:

This way you can use your custom text field just writing:

DecimalTextField()

,您可以在任何需要的地方使用它.

and you can use it wherever you want.

这篇关于如何创建只接受数字和单个点的SwiftUI TextField?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆