是否可以为2019-06-06 16:03:48.315 1930-2875/? I/GnssLocationProvider: WakeLock acquired by sendMessage(REPORT_SV_STATUS, 0, com.android.server.location.GnssLocationProvider$SvStatusInfo@494d84f)
2019-06-06 16:03:48.316 1930-1944/? I/GnssLocationProvider: WakeLock released by handleMessage(REPORT_SV_STATUS, 0, com.android.server.location.GnssLocationProvider$SvStatusInfo@494d84f)
设置最大长度?我当时正在考虑使用TextField
事件来处理它,但仅在用户开始/完成编辑时才调用它,而在用户键入时不调用它。我也阅读了文档,但还没有找到任何东西。有什么解决方法吗?
onEditingChanged
答案 0 :(得分:12)
使用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)
})
}
}
通过让模型负责这一点,UI代码变得更加简单,您不必担心会通过其他代码将更长的值分配给textValue
;该模型根本不允许这样做。
为了使场景使用数据模型对象,请将对rootViewController
中的SceneDelegate
的分配更改为类似
UIHostingController(rootView: ContentView().environmentObject(UserData()))
答案 1 :(得分:8)
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)
}
}
您需要做的是ObservableObject
包装TextField字符串。可以将其视为一个解释器,该解释器每次发生更改时都会得到通知,并且能够将修改发送回TextField。但是,无需创建PassthroughSubject
,使用@Published
修饰符将以更少的代码获得相同的结果。
一个提及,鉴于我们在不需要文本时会覆盖文本,因此您需要使用didSet
,而不是willSet
,否则您可能会陷入递归循环。
答案 2 :(得分:8)
您可以使用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))
}
}
}
答案 3 :(得分:4)
要使其灵活,可以将绑定包装在另一个绑定中,该绑定将应用所需的任何规则。在下面,它采用与Alex解决方案相同的方法(设置值,然后将其设置为无效值,然后将其设置回旧值),但是不需要更改@State
属性的类型。我想把它像Paul那样放在一个集合中,但是我找不到一种方法来告诉Binding更新所有观察者(并且TextField会缓存该值,因此您需要执行一些操作来强制进行更新)。
请注意,所有这些解决方案均不如包装UITextField。在我的解决方案和Alex的解决方案中,由于我们使用了重新分配,因此如果您使用箭头键移至该字段的另一部分并开始键入,即使字符没有变化,光标也会移动,这确实很奇怪。在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
}
})
}
}
使用此方法,您可以将TextField初始化更改为:
TextField($text.allowing { $0.count <= 10 }, ...)
答案 4 :(得分:3)
我所知道的在 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))
}
}
}
答案 5 :(得分:2)
写一个自定义格式化程序,并像这样使用它:
class LengthFormatter: Formatter {
//Required overrides
override func string(for obj: Any?) -> String? {
if obj == nil { return nil }
if let str = (obj as? String) {
return String(str.prefix(10))
}
return nil
}
override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
obj?.pointee = String(string.prefix(10)) as AnyObject
error?.pointee = nil
return true
}
}
}
现在是TextField:
struct PhoneTextField: View {
@Binding var number: String
let myFormatter = LengthFormatter()
var body: some View {
TextField("Enter Number", value: $number, formatter: myFormatter, onEditingChanged: { (isChanged) in
//
}) {
print("Commit: \(self.number)")
}
.foregroundColor(Color(.black))
}
}
您将看到正确的文本长度被分配给$ number。另外,无论输入任意长度的文本,都会在“提交”时将其截断。
答案 6 :(得分:1)
关于@ Paulw11的答复,对于最新的Beta版,我使UserData类再次像这样工作:
final class UserData: ObservableObject {
let didChange = PassthroughSubject<UserData, Never>()
var textValue = "" {
didSet {
textValue = String(textValue.prefix(8))
didChange.send(self)
}
}
}
我将willSet
更改为didSet
,因为该前缀立即被用户的输入所覆盖。因此,将这种解决方案与didSet配合使用,您将意识到在用户键入输入内容后,输入内容便会被裁剪。
答案 7 :(得分:0)
我能找到的最简单的解决方案是覆盖 didSet
:
@Published var text: String = "" {
didSet {
if text.count > 10 {
text.removeLast()
}
}
}
以下是使用 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())
}
}
答案 8 :(得分:0)
使用 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
}
}
示例
struct DemoView: View {
@State private var textField = ""
var body: some View {
TextField("8 Char Limit", text: self.$textField.max(8)) // Here
.padding()
}
}