How to return nil from a TextField? - ios

I am trying to return nil if the TextField is empty.
This is my code:
import SwiftUI
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 })
}
struct SomeView: View {
#State private var string: String?
var body: some View {
VStack {
TextField("Enter something", text: $string ?? "")
Text(string ?? "This string is nil.")
}
}
}
I am trying to get a nil value if the TextField is empty, but instead it just returns an empty string. How can I go about returning nil instead of an empty string? Any help is greatly appreciated.

Related

Laggy typing and cursor jumps with TextField?

My app uses TextFields everywhere to modify CoreData entities' String attributes. They work very poorly - typing a space or getting an auto correct event seems to make the cursor jump to the end of the window. Keystrokes are missed and the whole experience is laggy. TextEditors, on the other hand, work fine. The behavior doesn't appear on the simulator, only on (multiple) real devices.
What am I doing wrong here? Am I using TextFields wrong?
Code is below, it's basically the starter Xcode app with a "text: String?" attribute added to the "item" CoreData entity.
struct Detail: View {
#ObservedObject var item: Item
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $item.text ?? "")
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $item.text ?? "")
}, header: {
Text("TextEditor")
})
}
}
}
}
// Optional binding used
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}
Update:
I ended up just putting the TextFields into a subview and then writing their value back to the NSManagedObject via a binding every time the value changes.
I have no idea why, but this fixes the problem for me.
struct CustomTextField: View {
#Binding var string: String?
#State var localString: String
let prompt: String
init(string: Binding<String?>, prompt: String) {
_string = string
_localString = State(initialValue: string.wrappedValue ?? "")
self.prompt = prompt
}
var body: some View {
TextField(prompt, text: $localString, axis: .vertical)
.onChange(of: localString, perform: { _ in
string = localString
})
}
}
Example of using onSubmit, which does not cause CoreData to save the data on every input by the keyboard.
struct Detail: View {
#ObservedObject var item: Item
#State var text: String = "" // for starting with an empty textfield
// Alternative if you want the data from item:
// #State var text: String = item.text.wrappedValue // This only works if text is a binding.
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextEditor")
})
}
}
}
}
If that does not help, it would be nice to know how Item looks like.

Load Various Custom Views inside 'some View '

I've been working on a SwiftUI project that generates UIs based on Server responses using JSON
In order to do that I have created a FormBuilder
Now I want to show various types of custom fields like TextField, DateField, TextArea
all fields have
text, title, etc as common properties but will have different Validating procedures
in order to program this,
I have used a protocol name "FormField", "SectionView" struct to load fields,
some structs as Fields comforting to FormField
and "FieldView" struct to load different Fields.
In the FieldView protocol I'm getting following error when I try to show various Views based on the type I get via json
I Have commented the line that shows the error in FieldView class
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Can anyone help me to find a work around with this
Any help will be so much appreciated !
FormField Protocol
protocol FormField : View {
var id : String! { get set }
var title : String? { get set }
var placeholder : String? { get set }
var text : String? { get set }
var validation : String? { get set }
var keyboardType : String? { get set }
init(json : JSON)
func validate()
func showError()
func isValid() -> Bool
}
FieldView struct
Have commented the line that shows the error
struct FieldView: View {
private let TEXT = "text"
private let TEXTAREA = "textarea"
private let RADIO = "radio"
// could have more type
let id = UUID().uuidString
private var type : String!
private let fieldJson : JSON
init(json : JSON) {
self.type = json["type"].stringValue
self.fieldJson = json
}
var body: some View {
field
}
private var field : some FormField{
// Errors Comes from above Line
if type == TEXT {
FormTextField(json: fieldJson)
}
if type == TEXTAREA {
FormTextArea(json: fieldJson)
}
if type == RADIO {
FormRadio(json: fieldJson)
}
}
func validate() {
field.validate()
}
func showError() {
field.showError()
}
func isValid() -> Bool{
return field.isValid()
}
}
FormTextField struct
struct FormTextField: FormField {
var id : String!
var title : String?
var placeholder : String?
#State var text : String?
var validation : String?
var keyboardType : String?
init(json: JSON) {
self.id = json["id"].string
self.title = json["name"].string
self.placeholder = json["placeholder"].string
self.text = json["text"].string
self.validation = json["validation"].string
self.keyboardType = json["keyboardType"].string
}
var body: some View {
Text("Text Field: \(title!)")
.font(.headline)
.padding()
.background(Color.red)
}
func validate() {
if title!.isEmpty {
print("FormTextField Error")
}
}
func showError() {
}
func isValid() -> Bool{
return title!.isEmpty && text!.isEmpty
}
}
FormTextArea struct
struct FormTextArea: FormField {
var id : String!
var title : String?
var placeholder : String?
#State var text : String?
var validation : String?
var keyboardType : String?
init(json: JSON) {
self.id = json["id"].string
self.title = json["name"].string
self.placeholder = json["placeholder"].string
self.text = json["text"].string
self.validation = json["validation"].string
self.keyboardType = json["keyboardType"].string
}
var body: some View {
Text("Text Area: \(title!)")
.font(.headline)
.padding()
.background(Color.red)
}
func validate() {
print("Form Text Area")
}
func showError() {
}
func isValid() -> Bool{
return title!.isEmpty
}
}
FormRadio and other Fields also as same as this
Following SectionView struct was used to add fields inside of a VStack
SectionView struct
struct SectionView: View {
var id = UUID().uuidString
public var title : String?
public var fields = [FieldView]()
init(json : JSON) {
self.title = json["sectionName"].string
fields = (json["fields"].array ?? []).map({return FieldView.init(json: $0)})
}
var body: some View {
VStack{
Text(title ?? "Section")
.font(.title)
ForEach(fields,id:\.id){field in
field
}
}
.padding()
}
}

Make iOS SwiftUI TextField reject bad numeric input

My iOS SwiftUI TextField allows the user to input an Int. If the user types bad input characters such as "abc" instead of "123", I would like to display the bad characters in an error message in my Text (where I wrote textField.text), and I would like to keep the TextField's keyboard on the screen until the user provides correct input. How to make this happen? Thank you in advance. Xcode 11.6 on macOS Catalina 10.15.6.
struct ContentView: View {
#State private var value: Int? = nil;
#State private var text: String = "";
var body: some View {
VStack {
TextField(
"Enter an integer:",
value: $value,
formatter: NumberFormatter(),
onCommit: {
guard let value: Int = self.value else {
self.text = "Bad input \"\(textField.text)\".";
//Do not dismiss the TextField's keyboard.
return;
}
//Dismiss the TextField's keyboard.
self.text = "The product is \(2 * value).";
}
)
.keyboardType(.numbersAndPunctuation)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text(text)
}
.padding([.leading, .trailing], 16)
}
}
Below code gives you an idea for validating your text inside the textfield while user is typing.
struct ContentView: View {
#State private var textNumber : String = ""
#State private var isNumberValid : Bool = true
var body: some View {
VStack {
TextField("Enter an integer:", text: numberValidator())
.keyboardType(.numbersAndPunctuation)
.textFieldStyle(RoundedBorderTextFieldStyle())
if !self.isNumberValid {
Text("Bad input \"\(textNumber)\".")
.font(.callout)
.foregroundColor(Color.red)
}
}
.padding([.leading, .trailing], 16)
}
private func numberValidator() -> Binding<String> {
return Binding<String>(
get: {
return self.textNumber
}) {
if CharacterSet(charactersIn: "1234567890").isSuperset(of: CharacterSet(charactersIn: $0)) {
self.textNumber = $0
self.isNumberValid = true
} else {
self.textNumber = $0
//self.textNumber = ""
self.isNumberValid = false
}
}
}
}
The following code snippet gives you an idea to validate your text inside the text field as the user is typing using Get, Set value
let checkValue = Binding<String>(
get: {
self.value
},
set: {
self.value = $0
}
)
I hope it can be of help to you
struct ContentView: View {
#State private var value: String = ""
#State private var text: String = ""
var body: some View {
let checkValue = Binding<String>(
get: {
self.value
},
set: {
self.value = $0
}
)
return VStack {
TextField("Enter an integer:",text: checkValue)
.keyboardType(.numbersAndPunctuation)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("Bad interger: \(Int(self.value) != nil ? "" : self.value)").foregroundColor(Color.red)
}
.padding([.leading, .trailing], 16)
}
}

How to change complex data source in swiftUI List

App crash when I change data source like I tap “change data” button in APIView or delete item in QueryParametersView.list
console log:
This class 'SwiftUI.AccessibilityNode' is not a known serializable
element and returning it as an accessibility element may lead to
crashes
Fatal error: Index out of range: file
/AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift,
line 444
class URLComponentsModel: ObservableObject {
#Published var urlComponents = URLComponents.init()
var urlQueryItems: [URLQueryItem] {
get {
urlComponents.queryItems ?? [URLQueryItem].init()
}
set {
urlComponents.queryItems = newValue
}
}
}
struct APIView: View {
#ObservedObject var urlComponentsModel = URLComponentsModel.init()
var body: some View {
Button.init("change data") {
self.urlComponentsModel.urlComponents.queryItems?.removeFirst()
}
QueryParametersView.init(parameters: self.$urlComponentsModel.urlQueryItems)
}
}
struct QueryParametersView: View {
#Binding var parameters: [URLQueryItem]
var body: some View {
List {
ForEach(self.parameters.indices, id: \.self) { i in
HStack {
ParameterView.init(urlQueryItem: self.$parameters[i])
Text.init("delete")
.onTapGesture {
self.parameters.remove(at: i)
}
}
}
.onDelete { indices in
indices.forEach {
self.parameters.remove(at: $0)
}
}
}
}
struct ParameterView: View {
#Binding var urlQueryItem: URLQueryItem
var body: some View {
ZStack {
...
HStack {
...
if self.urlQueryItem.value != nil {
TextField("Value", text: Binding.init(get: {
(self.urlQueryItem.value ?? "")
}, set: { (value) in
self.urlQueryItem.value = value
}))
}
}
}
}
}
why? anybody help me?
removeFirst()
It says
The collection must not be empty.
If the collection is empty when you call removeFirst, app crashes with index out of range.

How to extract String value from Observed Object in Swift

I want to extract String value from Observed Object
This is example code
import SwiftUI
import Combine
class SetViewModel : ObservableObject {
private static let userDefaultTextKey = "textKey"
#Published var text: String = UserDefaults.standard.string(forKey: SetViewModel.userDefaultTextKey) ?? ""
private var canc: AnyCancellable!
init() {
canc = $text.debounce(for: 0.2, scheduler: DispatchQueue.main).sink { newText in
UserDefaults.standard.set(newText, forKey: SetViewModel.userDefaultTextKey)
}
}
deinit {
canc.cancel()
}
}
struct SettingView: View {
#ObservedObject var viewModel = SettingViewModel()
var body: some View {
ZStack {
Rectangle().foregroundColor(Color.white).edgesIgnoringSafeArea(.all).background(Color.white)
VStack {
TextField("test", text: $viewModel.text).textFieldStyle(BottomLineTextFieldStyle()).foregroundColor(.red)
Text($viewModel.text) //I want to get String Value from $viewModel.text
}
}
}
}
I want to use "$viewModel.text"'s String value. How can I do this?
Here is fix
Text(viewModel.text) // << use directly, no $ needed, it is for binding
try this:
struct SettingView: View {
#ObservedObject var viewModel = SetViewModel()
var body: some View {
ZStack {
Rectangle().foregroundColor(Color.white).edgesIgnoringSafeArea(.all).background(Color.white)
VStack {
TextField("test", text: self.$viewModel.text)
.textFieldStyle(PlainTextFieldStyle())
.foregroundColor(.red)
Text(viewModel.text) //I want to get String Value from $viewModel.text
}
}
}
}

Resources