SwiftUI TextField Simple Example not working - swift

I tried to create a very simple TextField in SwiftUI but I cannot get it to work and I don't understand what I am doing wrong.
Xcode gives me an error message that says:
"Unable to infer complex closure return type; add explicit type to disambiguate."
I am not sure what to do. I found some other code examples for TextFields with SwiftUI on StackOverflow but keep getting the same error.
struct TextFieldExample : View {
#State var email: String = "Enter email address"
var body: some View {
VStack {
TextField($email)
Text("Your email is \(email)!")
}
}
}
struct ButtonTextField : View {
#State var text: String = ""
var body: some View {
HStack {
TextField($text,
placeholder: Text("type something here..."))
Button(action: {
// Closure will be called once user taps your button
print(self.$text)
}) {
Text("SEND")
}
}
}
}
Expected results = working TextField
Actual result = Error in Xcode

It seems the TextField view has been changed in a recent beta release. You should be able to create one using something like this:
struct MyView {
#State var myInput: String = ""
var body: some View {
TextField("placeholder text", text: $myInput)
}
}

In the recent beta release of Xcode TextField has been changed.
#State var email: String = ""
var body: some View {
TextField("Email", text: $email, onEditingChanged: { (isChanges) in
// On Editing Changed
}) {
// On Commit
}
.padding(.leading, 13).padding(.trailing, 13).padding(.top, UIScreen.main.bounds.size.height / 2)
.textContentType(.emailAddress)
.textFieldStyle(RoundedBorderTextFieldStyle.init())
}

First of all do you really need to combine these Views into a custom view? If yes than:
#State and BindableObject should be passed into the view to the property marked with #Binding keyword
Don't use the same name as some of the native classes have
struct MyTextField : View {
#Binding var email: String
var body: some View {
VStack {
Text("Your email is \(email)!")
TextField($email, placeholder: Text("Enter your email"))
}
}
}
Call it like this
#State private var email: String = ""
var body: some View {
MyTextField(email: $email)
}

TextField like SearchView - XCODE 11.3
struct SearchBarV: View {
#State var text: String = ""
var onEditingChanged: (Bool) -> Void = { _ in }
var onCommit: () -> Void = { }
var body: some View {
GeometryReader { metrics in
TextField("placeholder", text: self.$text, onEditingChanged: self.onEditingChanged, onCommit: self.onCommit)
.background(Color.gray.opacity(0.1))
.padding(EdgeInsets(top: 0.0, leading: 16.0, bottom: 0, trailing: 16.0))
.frame(width: metrics.size.width, height: 50)
.keyboardType(.emailAddress)
}
}
}
struct SearchBarV_Previews : PreviewProvider {
static var previews: some View {
SearchBarV()
}
}

Related

How do I validate dynamically added textFields on a button click in SwiftUI?

I have the following InputView struct and add those InputViews dynamically within a foreach loop in another view:
struct InputView: View {
#State private var input: String = ""
var correct_input: Int
var body: some View {
TextField("?", text: $input)
.foregroundColor(setColor())
}
func setColor() -> Color {
if (Int(input) == correct_input) {
return Color.green
}
return Color.red
}
}
Up to now it is shown immediately whether the input is correct. However, I would like to add a button so that the input of all InputViews is only validated when it is clicked. How can I achieve this in SwiftUI?
You can be done this by making a model of text fields and use one isValid flag for each InputView for the track.
Here, is the possible demo solution.
struct TextFieldModel: Identifiable {
var id = UUID()
var input: String
var correctInput: Int
var isValidate: Bool = true
}
struct InputView: View {
#Binding var input: TextFieldModel
var body: some View {
TextField("?", text: $input.input)
.foregroundColor(input.isValidate ? Color.blue : Color.red)
}
}
struct ContentViewTextFields: View {
#State var arrTextFields: [TextFieldModel] = [
.init(input: "", correctInput: 5),
.init(input: "", correctInput: 10),
.init(input: "", correctInput: 1)
]
#State var isValidate: Bool = true
var body: some View {
VStack{
ForEach(arrTextFields.indices) { index in
InputView(input: $arrTextFields[index])
.background(Color.gray.opacity(0.2))
.padding()
}
Spacer()
Button("Validate") {
// Here validate all text
arrTextFields.indices.forEach({arrTextFields[$0].isValidate = (Int(arrTextFields[$0].input) == arrTextFields[$0].correctInput) })
}
}
}
}
You can have a button to check the input, setting some #State variable like correct to true if it is correct.
Example:
struct ContentView: View {
var body: some View {
InputView(correctInput: 5)
}
}
struct InputView: View {
#State private var input = ""
#State private var correct = false
let correctInput: Int
var body: some View {
VStack {
TextField("?", text: $input)
.foregroundColor(correct ? .green : .red)
Button("Check answer") {
correct = Int(input) == correctInput
}
}
}
}

Use object property as binding variable [duplicate]

I have the following InputView struct and add those InputViews dynamically within a foreach loop in another view:
struct InputView: View {
#State private var input: String = ""
var correct_input: Int
var body: some View {
TextField("?", text: $input)
.foregroundColor(setColor())
}
func setColor() -> Color {
if (Int(input) == correct_input) {
return Color.green
}
return Color.red
}
}
Up to now it is shown immediately whether the input is correct. However, I would like to add a button so that the input of all InputViews is only validated when it is clicked. How can I achieve this in SwiftUI?
You can be done this by making a model of text fields and use one isValid flag for each InputView for the track.
Here, is the possible demo solution.
struct TextFieldModel: Identifiable {
var id = UUID()
var input: String
var correctInput: Int
var isValidate: Bool = true
}
struct InputView: View {
#Binding var input: TextFieldModel
var body: some View {
TextField("?", text: $input.input)
.foregroundColor(input.isValidate ? Color.blue : Color.red)
}
}
struct ContentViewTextFields: View {
#State var arrTextFields: [TextFieldModel] = [
.init(input: "", correctInput: 5),
.init(input: "", correctInput: 10),
.init(input: "", correctInput: 1)
]
#State var isValidate: Bool = true
var body: some View {
VStack{
ForEach(arrTextFields.indices) { index in
InputView(input: $arrTextFields[index])
.background(Color.gray.opacity(0.2))
.padding()
}
Spacer()
Button("Validate") {
// Here validate all text
arrTextFields.indices.forEach({arrTextFields[$0].isValidate = (Int(arrTextFields[$0].input) == arrTextFields[$0].correctInput) })
}
}
}
}
You can have a button to check the input, setting some #State variable like correct to true if it is correct.
Example:
struct ContentView: View {
var body: some View {
InputView(correctInput: 5)
}
}
struct InputView: View {
#State private var input = ""
#State private var correct = false
let correctInput: Int
var body: some View {
VStack {
TextField("?", text: $input)
.foregroundColor(correct ? .green : .red)
Button("Check answer") {
correct = Int(input) == correctInput
}
}
}
}

Pass view as parameter to Button triggering it as a Modal

I'd like to have a custom button struct that receives a view as a parameter that will be shown as modal when the button is clicked. However, the view parameter is always empty, and I can't find the mistake I'm doing. My button struct looks like that:
struct InfoButton<Content:View>: View {
#State private var showingInfoPage: Bool
private var infoPage: Content
init(infoPage: Content, showingInfoPage: Bool) {
self.infoPage = infoPage
_showingInfoPage = State(initialValue: showingInfoPage)
}
var body: some View {
return
Button(action: {
self.showingInfoPage.toggle()
}) {
Image(systemName: "info.circle")
.resizable()
.frame(width: 25, height: 25)
.foregroundColor(.white)
.padding()
}.sheet(isPresented: self.$showingInfoPage) {
self.infoPage
}.frame(minWidth: 0, maxWidth: .infinity, alignment: .topTrailing)
}
}
This button is placed in a navigation bar from a template I'm creating for multiple other views.
I think the most relevant parts of that template are these:
protocol TrainingView {
var title: String { get }
var subheadline: String { get }
var asAnyView: AnyView { get }
var hasInfoPage: Bool { get }
var infoPage: AnyView { get }
}
extension TrainingView where Self: View {
var asAnyView: AnyView {
AnyView(self)
}
var hasInfoPage: Bool {
false
}
var infoPage: AnyView {
AnyView(EmptyView())
}
}
struct TrainingViewTemplate: View {
#State var showInfoPage: Bool = false
#State var viewIndex: Int = 0
var body: some View {
//the views that conform to the template
let views: [TrainingView] = [
ExerciseView(),
TrainingSessionSummaryView()
]
return NavigationView {
ViewIterator(views, self.$viewIndex) { exerciseView in
VStack {
VStack {
Text(exerciseView.title)
.font(.title)
.fontWeight(.semibold).zIndex(1)
Text(exerciseView.subheadline)
.font(.subheadline)
Spacer()
exerciseView.asAnyView.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}.navigationBarItems(trailing: (exerciseView.hasInfoPage == true ? InfoButton(infoPage: exerciseView.infoPage, showingInfoPage: self.showInfoPage) : nil))
}
}
}
I debugged to the point, where the navigationBarItems are initialized. At that point, the exercise view has content for "hasInfoPage" and "infoPage" itself.
One exemplary Exercise View has a header like that:
struct ExerciseView: View, TrainingView {
var title: String = "Strength Session"
var subheadline: String = "Pushups"
var numberOfExercise: Int = 1
#State var ratingValue: Double = 0
#Environment(\.presentationMode) var presentationMode
var hasInfoPage: Bool = true
var infoPage = ExerciseDetailView()
So in this view, the infoPage gets initialized with the ExercieDetailView() which I receive in the TemplateView, but as soon as the InfoButton is clicked, the debugger shows an empty infoPage, even though the "showingInfoPage" variable contains the right value.
You don't confirm to protocol, so default infoPage from extension TrainingView is shown.
The solution is
struct ExerciseView: View, TrainingView {
// .. other code here
var infoPage = AnyView(ExerciseDetailView()) // << here !!
``

How to pass a string to a child view?

I want to pass the text in the textBox to the child view and create a scrollable Button there. As for the output status, we hope that 'a ~ c' are arranged vertically and that each is a button.
struct ContentView: View {
var textBox = ["a","b","c"]
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0..<textBox.count) { number in
ScrollText(text: self.textBox[number].lowercased())
}
}
}
}
}
struct ScrollText: View {
#Binding var text: String
#State private var flag: Bool = false
var body: some View {
Button(action: {
self.flag.toggle()
}) {
Text(text)
}
}
}
I'm not totally clear what the problem is, or what you want, but I solved some compiler errors in your code, and it's showing three buttons as expected:
struct ContentView : View {
var textBox = ["a","b","c"]
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false){
ForEach(textBox, id: \.self) { letter in
ScrollText(text: letter)
}
}
}
}
}
struct ScrollText: View {
var text: String
#State private var flag: Bool = false
var body: some View {
Button(action: {
self.flag.toggle()
}, label: {
Text(text)
})
}
}
Your question was how to pass a string, so you don't need #Binding for that. Just pass a string :)
If you're going to keep ScrollText untouched the here is possible modifications in ContentView which uses it
struct ContentView: View {
#State private var textBox = ["a","b","c"] // < make State, so modifiable
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0..<textBox.count) { number in
ScrollText(text: self.$textBox[number]) // < pass Binding as intended
}
}
}
}
}

List selection as Set<String> - how to use?

Am playing around with SwiftUI and am obviously not getting it.
Basic example which works and is just displaying the selected name.
struct ContentView: View {
let names = ["Joe", "Jim", "Paul"]
#State var selectedName = Set<String>()
var body: some View {
VStack {
List(names, id: \.self, selection: $selectedName) { name in
Text(name)
}
if !selectedName.isEmpty {
Text(selectedName.first!) // <-- this line
}
}
}
}
What I want is a textfield where that name can be changed. Tried many ways but getting another error every time.
TextField("Name", text: $selectedName)
Gives this error: Cannot convert value of type 'Binding<Set<String>>' to expected argument type 'Binding<String>'
TextField("Name", text: $selectedName.first!)
Cannot force unwrap value of non-optional type 'Binding<((String) throws -> Bool) throws -> String?>'
How would I do this?
You may make a binding by yourself:
TextField("Name", text: Binding<String>(get: {self.selectedName.first!}, set: { _ in}) )
Obviously you can't pass Binding<Set<String>> to Binding<String>. Here gives you an idea or solution to change selectedName variable using TextField:
I added a new variable which is Binding<String>. Then I change the selectedName inside the TextField's onCommit closure.
struct ContentView: View {
let names = ["Joe", "Jim", "Paul"]
#State var selectedName = Set<String>()
#State var textFieldName = ""
var body: some View {
VStack {
List(names, id: \.self, selection: $selectedName) { name in
Text(name)
}
if !selectedName.isEmpty {
Text(selectedName.first!)
}
Text(textFieldName)
TextField("Name", text: $textFieldName, onEditingChanged: { (Bool) in
//onEditing
}) {
//onCommit
self.selectedName.insert(self.textFieldName)
}
}
}
}
Ok, here is my alternate if I'd needed to edit some value of names having in one screen and list and edit field and make them all synchronised and not confuse each other.
Here is full testable module (tested on Xcode 11.2/iOS 13.2). As I tested it for iOS there are API requirement for put List into EditMode to process selection, so this included.
struct TestChangeSelectedItem: View {
#State var names = ["Joe", "Jim", "Paul"] // made modifiable
#State var selectedName: String? = nil // only one can be edited, so single selection
#State var editMode: EditMode = .active // Tested for iOS, so it is needed
var body: some View {
VStack {
List(selection: $selectedName) {
ForEach(names, id: \.self) { name in
Text(name)
}
}
.environment(\.editMode, $editMode) // Tested for iOS, so it is needed
if selectedName != nil {
Divider()
Text(selectedName!) // Left to see updates for selection
editor(for: selectedName!) // Separated to make more clear
}
}
}
private func editor(for selection: String) -> some View {
let index = names.firstIndex(of: selection)!
var editedValue = selection // local to avoid cycling in refresh
return HStack {
Text("New name:")
TextField("Name", text: Binding<String>(get: { editedValue }, set: { editedValue = $0}), onCommit: {
self.names[index] = editedValue
self.selectedName = editedValue
})
}
}
}
struct TestChangeSelectedItem_Previews: PreviewProvider {
static var previews: some View {
TestChangeSelectedItem()
}
}