I am learning SwiftUI (New framework provided by Apple with iOS 13 and Xcode 11 : SwiftUI by Apple).
I want to add Button and TextField in ListView with action. I want one textfield in that user can add any one number from 1 to 10 and then hit SEND button. Anyone have any idea how to add button in it and also how can we handle touch event of Button with SwiftUI ?
Any help would be appreciate.
Here is a simple view what contains a textfield and a button in a horizontal stack.
To handle the user interaction with in your Button, just overwrite the action closure.
import SwiftUI
struct ButtonAndTextFieldView : 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")
}
}
}
}
#if DEBUG
struct ButtonWithTextFieldView_Previews : PreviewProvider {
static var previews: some View {
ButtonWithTextFieldView()
}
}
#endif
For the Login page design you can use this code section. With textFieldStyle border textfield and content type set.
struct ButtonAndTextFieldView : View {
#State var email: String = ""
#State var password: String = ""
var body: some View {
VStack {
TextField($email,
placeholder: Text("email"))
.textFieldStyle(.roundedBorder)
.textContentType(.emailAddress)
TextField($password,
placeholder: Text("password"))
.textFieldStyle(.roundedBorder)
.textContentType(.password)
Button(action: {
//Get Email and Password
print(self.$email)
print(self.$password)
}) {
Text("Send")
}
}
}
You can add button like that
Button(action: {}) {
Text("Increment Total")
}
And text field.
#State var bindingString: Binding<String> = .constant("")
TextField(bindingString,
placeholder: Text("Hello"),
onEditingChanged: { editing in
print(editing)
}).padding(.all, 40)
You can write a custom TextField which will return you the event in the closure once the user taps on the button. This Custom textfield would contain a HStack with a textfield and a button. Like this.
struct CustomTextField : View {
#Binding var text: String
var editingChanged: (Bool)->() = { _ in }
var commit: ()->() = { }
var action : () -> Void
var buttonTitle : String
var placeholder: String
var isSecuredField = false
var body : some View {
HStack {
if isSecuredField {
SecureField(placeholder, text: $text, onCommit: commit)
} else {
TextField(placeholder, text: $text, onEditingChanged: editingChanged, onCommit: commit)
}
Button(action: action) {
Text(buttonTitle)
}
}
}
}
And to you can use this custom TextField like this. I have used an example from the above-listed answers to make it more clear.
struct ListView: View {
#State var text: String = ""
var body: some View {
List {
ForEach (1..<2) {_ in
Section {
CustomTextField(
text: self.$text,
action: {
print("number is .....\(self.text)")
},
buttonTitle: "Submit",
placeholder: "enter your number")
}
}
}
}
}
ListView with textfield and button. You will need an identifier for each row in case you want to have multiple rows in the List.
struct ListView: View {
#State var text: String = ""
var body: some View {
List {
ForEach (1..<2) {_ in
Section {
HStack(alignment: .center) {
TextField(self.$text, placeholder: Text("type something here...") ).background(Color.red)
Button(action: {
print(self.$text.value)
} ) {
Text("Send")
}
}
}
}
}
}
}
Related
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.
I have two view file. I have textfield. I want to supply string value to the textfield from another view
File 1 :- Place where form is created
struct ContentView: View {
#State var subjectLine: String = ""
var body: some View {
form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine
}
}
}
}
File 2 :- Place where I want to provide value to the string and that will show in the textfield UI
struct CalenderView: View {
#Binding var subjectLine: String
var body : some View {
Button(action: {
subjectLine = "Assigned default value"
}, label: {
Text("Fill textfield")
}
}
})
}
}
This is not working. Any other way we can supply value to the textfield in other view file.
As i can understand you have binding in CalenderView
that means you want to navigate there when you navigate update there.
struct ContentView: View {
#State private var subjectLine: String = ""
#State private var showingSheet: Bool = false
var body: some View {
NavigationView {
Form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine)
}
}
.navigationBarItems(trailing: nextButton)
.sheet(isPresented: $showingSheet) {
CalenderView(subjectLine: $subjectLine)
}
}
}
var nextButton: some View {
Button("Next") {
showingSheet.toggle()
}
}
}
CalendarView
struct CalenderView: View {
#Binding var subjectLine: String
#Environment(\.dismiss) private var dismiss
var body: some View {
Button {
subjectLine = "Assigned default value"
dismiss()
} label: {
Text("Fill textfield")
}
}
}
Please help me get rid of this problem:
Steps to reproduce my problem:
Tap "Edit My Name" Button
Inside .sheet, tap on the TextField and then with keyboard still shown, scroll all the way down
Tap on the Button "Delete Name"
Here is the problem:
confirmationDialog appears only for one second, and then
disappears, not giving the user any chance (or less than one second chance)
to tap one of the confirmationDialog's Buttons!
Here's my code:
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var myName = "Joe"
#State private var isEditingName = false
var body: some View {
Text("My name is: \(myName)")
Button("Edit My Name") {
isEditingName = true
}
.padding()
.sheet(isPresented: $isEditingName) {
EditView(name: $myName)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
EditView.swift
import SwiftUI
struct EditView: View {
#Binding var name: String
#State private var isShowingConfirmationDialog = false
var body: some View {
Form {
Section {
TextField("Name", text: $name)
}
Section {
VStack {
ForEach(0..<50, id: \.self) { number in
Text("\(number)")
}
}
}
Section {
deleteNameWithConfirmationDialog
}
}
}
private var deleteNameWithConfirmationDialog: some View {
Button("Delete Name", role: .destructive) {
isShowingConfirmationDialog = true
}
.confirmationDialog("Are you sure you want to delete name?", isPresented: $isShowingConfirmationDialog) {
Button("Delete Name", role: .destructive) {
name = ""
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Are you sure you want to delte name?")
}
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView(name: .constant(String("Joe")))
}
}
It works if you move the .confirmationDialogue out of the Form:
struct EditView: View {
#Binding var name: String
#State private var isShowingConfirmationDialog = false
var body: some View {
Form {
Section {
TextField("Name", text: $name)
}
Section {
VStack {
ForEach(0..<50, id: \.self) { number in
Text("\(number)")
}
}
}
Section {
Button("Delete Name", role: .destructive) {
isShowingConfirmationDialog = true
}
}
}
.confirmationDialog("Are you sure you want to delete name?", isPresented: $isShowingConfirmationDialog) {
Button("Delete Name", role: .destructive) {
name = ""
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Are you sure you want to delete name?")
}
}
}
If you want to keep .confirmationDialog inside of the Form, or if you are using .confirmationDialog to manage deleting items within a List, you can also avoid the immediate dismissal by excluding the .destructive role from the deleteButton in .swipeActions.
var body: some View {
List { // this could also be a Form
ForEach(listItems) { item in
ItemRow(item: item) // confirmationDialog is in ItemRow
}.swipeActions {
Button(action: { /* deleteMethodHere */ }) {
Image(systemName: "trash")
}.tint(.red) // to keep the swipe button red
}
}
}
When using NavigationLink on the bottom of a view after ForEach it won't work if it is not visible.
I have a list of Buttons. If a button is pressed, it sets a Bool to true. This bool value now shows a NavigationLink which immediately activates because the passed binding is set to true.
However, the link won't work if the array is too long because it will be out of sight once one of the first buttons is pressed.
This is my Code:
import SwiftUI
struct TestLinkView: View {
#State private var linkIsActive = false
var body: some View {
NavigationView {
VStack {
Button(action: {
linkIsActive = true
}) {
Text("Press")
}
NavigationLink(destination: ListView(linkIsActive: $linkIsActive), isActive: $linkIsActive) {
Text("Navigation Link")
}
}
}
}
}
struct ListView: View {
var nameArray = ["Name1","Name2","Name3","Name4","Name5","Name6","Name7","Name8","Name9","Name10","Name11","Name12","Name13","Name14","Name15","Name16","Name17","Name18","Name19","Name20" ]
#State private var showLink: Bool = false
#State private var selectedName: String = ""
#Binding var linkIsActive: Bool
var body: some View {
Form {
ForEach(nameArray, id: \.self) { name in
Button(action: {
selectedName = name
showLink = true
}) {
Text(name)
}
}
if showLink {
NavigationLink(destination: NameView(selectedName: selectedName), isActive: $linkIsActive) {
EmptyView()
}
}
}
.navigationBarTitle("ListView")
}
}
struct NameView: View {
var selectedName: String
var body: some View {
Text(selectedName)
.navigationBarTitle("NameView")
}
}
What would work is to pass the NavigationLink with the if-condition inside the button label. However if I do that, the animation won't work anymore.
You don't need it in Form, which is like a List don't create views far outside of visible area. In your case the solution is to just move link into background of Form (because it does not depend on form internals).
The following tested as worked with Xcode 12 / iOS 14.
Form {
ForEach(nameArray, id: \.self) { name in
Button(action: {
selectedName = name
showLink = true
}) {
Text(name)
}
}
}
.background(Group{
if showLink {
NavigationLink(destination: NameView(selectedName: selectedName), isActive: $linkIsActive) {
EmptyView()
}
}
})
TextField is disabled (not editable) when placed in List on macOS. When the same code is build for iOS and ran in Simulator, it works as expected.
Is this a bug, or am I missing something?
The code:
struct ContentView : View {
#State private var text: String = ""
var body: some View {
VStack {
List {
// TextField is not editable when this code is ran on macOS
TextField($text, placeholder: Text("Entry text"))
Text("Entered text: \(text)")
}
// TextField is editable on both macOS as well as iOS
TextField($text, placeholder: Text("Entry text"))
}
}
}
That's because the list is taking clicks to drive selection, which you're not using here. TextField becomes editable in the list on macOS only when the row in which it is housed has selection.
If you change your code to something like this
struct ContentView : View {
#State private var text: String = "Hello"
#State private var selection: Int? = nil
var body: some View {
VStack {
List(selection: $selection) {
ForEach(0..<5) { _ in
TextField(self.$text)
}
}
TextField($text)
}
}
}
then run the code, the first click on the cell will cause it to get selected and the second click will cause the text field to receive focus.
Create the TextField using the following code
struct ContentView : View {
#State private var helloText: String = "Hello"
#State private var selection: Int? = nil
var body: some View {
VStack {
List(selection: $selection) {
ForEach(0..<5) { _ in
TextField(.constant(self.helloText), placeholder: Text("Entry text")).textFieldStyle(.roundedBorder)
}
}
}
}
}