是否可以为设置最大长度TextField?我当时正在考虑使用onEditingChanged事件来处理它,但仅在用户开始/完成编辑时才调用它,而在用户键入时不调用它。我也阅读了文档,但还没有找到任何东西。有什么解决方法吗?
TextField($text, placeholder: Text("Username"), onEditingChanged: { _ in
print(self.$text)
}) {
print("Finished editing")
}
Run Code Online (Sandbox Code Playgroud)
Raj*_*han 47
使用Binding扩展。
extension Binding where Value == String {
func max(_ limit: Int) -> Self {
if self.wrappedValue.count > limit {
DispatchQueue.main.async {
self.wrappedValue = String(self.wrappedValue.dropLast())
}
}
return self
}
}
Run Code Online (Sandbox Code Playgroud)
例子
struct DemoView: View {
@State private var textField = ""
var body: some View {
TextField("8 Char Limit", text: self.$textField.max(8)) // Here
.padding()
}
}
Run Code Online (Sandbox Code Playgroud)
gab*_*zsa 35
它基本上是现代 API 的单行代码(iOS 14+)
let limit = 10
//...
TextField("", text: $text)
.onChange(of: text) { _ in
text = String(text.prefix(limit))
}
Run Code Online (Sandbox Code Playgroud)
小智 30
你可以用Combine一种简单的方式做到这一点。
像这样:
import SwiftUI
import Combine
struct ContentView: View {
@State var username = ""
let textLimit = 10 //Your limit
var body: some View {
//Your TextField
TextField("Username", text: $username)
.onReceive(Just(username)) { _ in limitText(textLimit) }
}
//Function to keep text length in limits
func limitText(_ upper: Int) {
if username.count > upper {
username = String(username.prefix(upper))
}
}
}
Run Code Online (Sandbox Code Playgroud)
Nev*_*oon 21
只要 iOS 14+ 可用,就可以通过以下方式执行此操作onChange(of:perform:)
struct ContentView: View {
@State private var text: String = ""
var body: some View {
VStack {
TextField("Name", text: $text, prompt: Text("Name"))
.onChange(of: text, perform: {
text = String($0.prefix(1))
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice(.init(rawValue: "iPhone SE (1st generation)"))
}
}
Run Code Online (Sandbox Code Playgroud)
怎么运行的。每次text更改时,onChange回调都会确保文本不长于指定长度(使用prefix)。在示例中,我不希望text超过 1。
对于这个具体示例,最大长度为 1。每当第一次输入文本时,onChange都会调用一次。如果尝试输入另一个字符,onChange将被调用两次:第一次回调参数将为,例如,,aa因此text将被设置为a。第二次将使用参数a和 set调用text,该参数已经a具有相同的值a,但这不会触发任何更多回调,除非输入值发生更改,因为onChange验证了下面的相等性。
所以:
"a" != "",一次调用onChange会将文本设置为与已有的值相同的值。"a" == "a",不再调用onChange"aa" != "a",第一次调用 onChange,文本被调整并设置为a,"a" != "aa",第二次调用 onChange 并调整值,"a" == "a",onChange 不执行onChange两次Pau*_*w11 16
使用SwiftUI,UI元素(如文本字段)绑定到数据模型中的属性。数据模型的工作是实现业务逻辑,例如限制字符串属性的大小。
例如:
import Combine
import SwiftUI
final class UserData: BindableObject {
let didChange = PassthroughSubject<UserData,Never>()
var textValue = "" {
willSet {
self.textValue = String(newValue.prefix(8))
didChange.send(self)
}
}
}
struct ContentView : View {
@EnvironmentObject var userData: UserData
var body: some View {
TextField($userData.textValue, placeholder: Text("Enter up to 8 characters"), onCommit: {
print($userData.textValue.value)
})
}
}
Run Code Online (Sandbox Code Playgroud)
通过让模型负责这一点,UI代码变得更加简单,您不必担心会textValue通过其他代码将更长的值分配给用户。该模型根本不允许这样做。
为了让您的场景中使用的数据模型对象,分配改变你rootViewController在SceneDelegate喜欢的东西
UIHostingController(rootView: ContentView().environmentObject(UserData()))
Run Code Online (Sandbox Code Playgroud)
kga*_*dis 10
这是 iOS 15 的快速修复(将其包装在异步调度中):
@Published var text: String = "" {
didSet {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
while self.text.count > 80 {
self.text.removeLast()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:目前 iOS 15 中存在一个错误/更改,其中以下代码不再起作用。
我能找到的最简单的解决方案是覆盖didSet:
@Published var text: String = "" {
didSet {
if text.count > 10 {
text.removeLast()
}
}
}
Run Code Online (Sandbox Code Playgroud)
以下是使用 SwiftUI 预览进行测试的完整示例:
class ContentViewModel: ObservableObject {
@Published var text: String = "" {
didSet {
if text.count > 10 {
text.removeLast()
}
}
}
}
struct ContentView: View {
@ObservedObject var viewModel: ContentViewModel
var body: some View {
TextField("Placeholder Text", text: $viewModel.text)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: ContentViewModel())
}
}
Run Code Online (Sandbox Code Playgroud)
Paulw11的答案稍短一些:
class TextBindingManager: ObservableObject {
@Published var text = "" {
didSet {
if text.count > characterLimit && oldValue.count <= characterLimit {
text = oldValue
}
}
}
let characterLimit = 5
}
struct ContentView: View {
@ObservedObject var textBindingManager = TextBindingManager()
var body: some View {
TextField("Placeholder", text: $textBindingManager.text)
}
}
Run Code Online (Sandbox Code Playgroud)
您只需要一个ObservableObjectTextField字符串的包装即可。可以将其视为一个解释器,该解释器每次发生更改时都会得到通知,并且能够将修改发送回TextField。但是,无需PassthroughSubject使用@Published修饰符来创建,只需较少的代码即可获得相同的结果。
提及一句,鉴于我们在不需要的时候覆盖了文本,因此您需要使用didSet,而不是,willSet否则您可能会陷入递归循环。
为了使其灵活,您可以将 Binding 包装在另一个应用您想要的任何规则的 Binding 中。在下面,这采用了与 Alex 的解决方案相同的方法(设置值,然后如果它无效,则将其设置回旧值),但它不需要更改@State属性的类型。我想把它变成像 Paul 一样的单一集合,但我找不到一种方法来告诉 Binding 更新它的所有观察者(并且 TextField 缓存了该值,所以你需要做一些事情来强制更新)。
请注意,所有这些解决方案都不如包装 UITextField。在我和亚历克斯的解决方案中,由于我们使用重新分配,如果您使用箭头键移动到字段的另一部分并开始输入,即使字符没有改变,光标也会移动,这真的很奇怪。在 Paul 的解决方案中,由于它使用prefix()了 ,因此字符串的末尾将被无声地丢失,这可以说是更糟。我不知道有什么方法可以实现 UITextField 阻止您打字的行为。
extension Binding {
func allowing(predicate: @escaping (Value) -> Bool) -> Self {
Binding(get: { self.wrappedValue },
set: { newValue in
let oldValue = self.wrappedValue
// Need to force a change to trigger the binding to refresh
self.wrappedValue = newValue
if !predicate(newValue) && predicate(oldValue) {
// And set it back if it wasn't legal and the previous was
self.wrappedValue = oldValue
}
})
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个,您可以将 TextField 初始化更改为:
TextField($text.allowing { $0.count <= 10 }, ...)
Run Code Online (Sandbox Code Playgroud)
将一堆答案组合成我满意的东西。
在 iOS 14+ 上测试
用法:
class MyViewModel: View {
@Published var text: String
var textMaxLength = 3
}
Run Code Online (Sandbox Code Playgroud)
struct MyView {
@ObservedObject var viewModel: MyViewModel
var body: some View {
TextField("Placeholder", text: $viewModel.text)
.limitText($viewModel.text, maxLength: viewModel.textMaxLength)
}
}
Run Code Online (Sandbox Code Playgroud)
extension View {
func limitText(_ field: Binding<String>, maxLength: Int) -> some View {
modifier(TextLengthModifier(field: field, maxLength: maxLength))
}
}
Run Code Online (Sandbox Code Playgroud)
struct TextLengthModifier: ViewModifier {
@Binding var field: String
let maxLength: Int
func body(content: Content) -> some View {
content
.onReceive(Just(field), perform: { _ in
let updatedField = String(
field
// do other things here like limiting to number etc...
.enumerated()
.filter { $0.offset < maxLength }
.map { $0.element }
)
// ensure no infinite loop
if updatedField != field {
field = updatedField
}
})
}
}
Run Code Online (Sandbox Code Playgroud)
我所知道的在 TextField 上设置字符限制的最优雅(和简单)的方法是使用本机发布者事件collect()。
用法:
struct ContentView: View {
@State private var text: String = ""
var characterLimit = 20
var body: some View {
TextField("Placeholder", text: $text)
.onReceive(text.publisher.collect()) {
text = String($0.prefix(characterLimit))
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1754 次 |
| 最近记录: |