I have a custom TextField where I detect when the user taps on it to change the look of the field, but I tried to add the same look for the TextEditor, but it doesn't have an onEditingChanged option.
This is my text field:
struct CustomTextField : View {
#Binding var text : String
#State private var isEditing = false
#State var isDisabled = false
init(text : Binding<String>, isDisabled : Bool = false ){
self._text = text
self.isDisabled = isDisabled
}
var body: some View {
TextField("", text: $text, onEditingChanged: {
isEditing = $0
})
.padding(8)
.clipShape(RoundedRectangle(cornerRadius: 4))
.background (
RoundedRectangle(cornerRadius: 4)
.stroke(isEditing ? Color("Accent") : Color("Gray"), lineWidth: isDisabled ? 0 : 1)
)
}
}
I thought of adding a tap gesture recognizer, but it will only detect when I click, not when I leave the text editor.
Is there a way to detect the focus state of a TextEditor?
Starting with the familiar bad news '#available', you can use the focused modifier, available from iOS 15.
struct ContentView: View {
#State private var text = "Hello focused!"
#FocusState private var isFocused: Bool
var body: some View {
TextEditor(text: $text)
.focused($isFocused)
.foregroundColor(isFocused ? .red : .black)
}
}
Note:
This does not work in the canvas preview, run on Simulator.
This is a simple use of FocusState. FocusState has many more options.
Related
I want to open a Sheet in SwiftUI that 1) contains a TextField which 2) accepts focus immediately. Unfortunately, as demonstrated in the attached clip, there’s a short but noticeable delay before the keyboard pops up. This makes the transition into the focused state not as smooth as I would like (UI shifting to make room for the keyboard).
Minimum reproducible example:
struct TestRootView: View {
#State private var sheet = false
var body: some View {
Button("Open sheet") {
sheet.toggle()
}
.sheet(isPresented: $sheet) {
SheetView()
}
}
}
struct SheetView: View {
#Environment(\.dismiss) var dismiss
#FocusState var isFocused
#State var text: String = ""
var body: some View {
TextField("title", text: $text)
.focused($isFocused)
.onAppear {
isFocused = true
}
Button("Close") {
dismiss()
}.font(.title)
.padding()
.background(.black)
}
}
I'm using TabView to display a scrollable tab list of players to update. I am trying to focus the newScoreEntry field upon scrolling to the next TabView. I am having trouble figuring out how to focus the right field based upon my selectedTab. I think I have to somehow define my focusfield as an array of fields or something?
struct ScoreRoundView: View {
#StateObject var game: Game
#State var newScoreEntry: [String] = Array(repeating: "", count: 50)
#State var selectedTab = 0
#FocusState private var focusScore: Bool
var body: some View {
TabView(selection: $selectedTab) {
ForEach(Array(zip(game.playersArray.indices, game.playersArray)), id: \.1) { i, player in
TextField("Round Score", text: $newScoreEntry[i])
.focused($focusScore)
}.tag(i)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.onChange(of: selectedTab, perform: { value in
focusScore = true
})
}
}
I figured out what i was looking for, so i thought i would post the solution. I had to use focusable field with an id associated with it.
enum Focusable: Hashable {
case tabView(id: Int)
}
#FocusState var focusedField: Focusable?
#StateObject var game: Game
#State var newScoreEntry: [String] = Array(repeating: "", count: 50)
#State var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
ForEach(Array(zip(game.playersArray.indices, game.playersArray)), id: \.1) { i, player in
TextField("Round Score", text: $newScoreEntry[i])
.focused($focusedField, equals: .tabView(id: i))
}.tag(i)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.onChange(of: selectedTab, perform: { value in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.focusedField = .tabView(id: value)
}
})
I am trying to edit the text of a binding value from another view. But text editor does not allow me to do so.
First I need to see the previous value of selectedNote.title in TextEditor and then I should be able to edit the title.
struct EditNotePageView: View {
#Binding var selectedNote: CDNoteModel!
#State var text = ""
var body: some View {
TextEditor(selectedNote.title!, text: $text)
}
}
I also tried like this. I can see my selectedNote.title in the simulator but when I tap on it it does nothing.
struct EditNotePageView: View {
#Binding var selectedNote: CDNoteModel!
#State var text = ""
var body: some View {
Text(selectedNote.title!)
.onTapGesture {
text = selectedNote.title!
TextEditor(text: $text)
}
}
}
ZStack Text and textEditor solved the problem with a little pinch of .onAppear
struct EditNotePageView: View {
#Binding var selectedNote: CDNoteModel!
#State var text = ""
var body: some View {
ZStack(alignment: .leading){
Text(selectedNote.title!)
.onAppear {
text = selectedNote.title!
}
TextEditor(text: $text)
}
}
}
You can use onChange property
struct EditNotePageView: View {
#Binding var selectedNote: CDNoteModel!
#State var text = ""
var body: some View {
TextEditor("", text: $text)
.onChange(of: text, perform: { value in
selectedNote.title = value
}
}
}
you could try this:
struct EditNotePageView: View {
#Binding var selectedNote: CDNoteModel
var body: some View {
TextEditor(text: $selectedNote.title)
}
}
I am trying to update the style of the HStack containing my text box when the Textbox is selected. In the example below, I want the text box to have a red border when selected, otherwise the border should be gray.
My issue is that the textbox seems to go through an intermediate transition that I don't want, which is the border is updated to red, but the keyboard doesn't pop up until I select the textbox again (The textbox moves up a bit and then goes back down). It seems that there is some issue with the ordering of how the view refresh happens.
#State private var text: String
#State private var textFieldSelected: Bool = false
var body: some View {
let stack = HStack {
TextField("Enter name", text: $text, onEditingChanged: {
(changed) in
textFieldSelected = changed
})
}
if (textFieldSelected) {
stack
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.red, lineWidth: 1))
} else {
stack
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
}
}
Here's a video example of the existing behavior:
Make it even simpler by using ternary condition for the border and the issue won't appear
struct ContentView : View {
#State private var text: String = ""
#State private var textFieldSelected: Bool = false
var body: some View {
HStack {
TextField("Enter name", text: $text, onEditingChanged: {
(changed) in
textFieldSelected = changed
})
}
.overlay(RoundedRectangle(cornerRadius: 10).stroke(textFieldSelected ? Color.red : Color.gray, lineWidth: 1))
}
}
Tested on iPhone 8 Plus iOS 14
I am trying to add a ClearButton to TextField in SwiftUI when the particular TextField is selected.
The closest I got was creating a ClearButton ViewModifier and adding it to the TextField using .modifer()
The only problem is ClearButton is permanent and does not disappear when TextField is deselected
TextField("Some Text" , text: $someBinding).modifier(ClearButton(text: $someBinding))
struct ClearButton: ViewModifier {
#Binding var text: String
public func body(content: Content) -> some View {
HStack {
content
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
}
}
}
}
Use ZStack to position the clear button appear inside the TextField.
TextField("Some Text" , text: $someBinding).modifier(ClearButton(text: $someBinding))
struct ClearButton: ViewModifier
{
#Binding var text: String
public func body(content: Content) -> some View
{
ZStack(alignment: .trailing)
{
content
if !text.isEmpty
{
Button(action:
{
self.text = ""
})
{
Image(systemName: "delete.left")
.foregroundColor(Color(UIColor.opaqueSeparator))
}
.padding(.trailing, 8)
}
}
}
}
Use .appearance() to activate the button
var body: some View {
UITextField.appearance().clearButtonMode = .whileEditing
return TextField(...)
}
For reuse try with this:
func TextFieldUIKit(text: Binding<String>) -> some View{
UITextField.appearance().clearButtonMode = .whileEditing
return TextField("Nombre", text: text)
}
=== solution 1(best): Introspect https://github.com/siteline/SwiftUI-Introspect
import Introspect
TextField("", text: $text)
.introspectTextField(customize: {
$0.clearButtonMode = .whileEditing
})
=== solution 2: ViewModifier
public struct ClearButton: ViewModifier {
#Binding var text: String
public init(text: Binding<String>) {
self._text = text
}
public func body(content: Content) -> some View {
HStack {
content
Spacer()
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
.opacity(text == "" ? 0 : 1)
.onTapGesture { self.text = "" } // onTapGesture or plainStyle button
}
}
}
Usage:
#State private var name: String
...
Form {
Section() {
TextField("NAME", text: $name).modifier(ClearButton(text: $name))
}
}
=== solution 3: global appearance
UITextField.appearance().clearButtonMode = .whileEditing
You can add another Binding in your modifier:
#Binding var visible: Bool
then bind it to opacity of the button:
.opacity(visible ? 1 : 0)
then add another State for checking textField:
#State var showClearButton = true
And lastly update the textfield:
TextField("Some Text", text: $someBinding, onEditingChanged: { editing in
self.showClearButton = editing
}, onCommit: {
self.showClearButton = false
})
.modifier( ClearButton(text: $someBinding, visible: $showClearButton))
Not exactly what you're looking for, but this will let you show/hide the button based on the text contents:
HStack {
if !text.isEmpty {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle")
}
}
}
After initializing a new project we need to create a simple view modifier which we will apply later to our text field. The view modifier has the tasks to check for content in the text field element and display a clear button inside of it, if content is available. It also handles taps on the button and clears the content.
Let’s have a look at that view modifier:
import SwiftUI
struct TextFieldClearButton: ViewModifier {
#Binding var text: String
func body(content: Content) -> some View {
HStack {
content
if !text.isEmpty {
Button(
action: { self.text = "" },
label: {
Image(systemName: "delete.left")
.foregroundColor(Color(UIColor.opaqueSeparator))
}
)
}
}
}
}
The code itself should be self explanatory and easy to understand as there is no fancy logic included in our tasks.
We just wrap the textfield inside a HStack and add the button, if the text field is not empty. The button itself has a single action of deleting the value of the text field.
For the clear icon we use the delete.left icon from the SF Symbols 2 library by Apple, but you could also use another one or even your own custom one.
The binding of the modifier is the same as the one we apply to the text field. Without it we would not be able to check for content or clear the field itself.
Inside the ContentView.swift we now simply add a TextField element and apply our modifier to it — that’s all!
import SwiftUI
struct ContentView: View {
#State var exampleText: String = ""
var body: some View {
NavigationView {
Form {
Section {
TextField("Type in your Text here...", text: $exampleText)
.modifier(TextFieldClearButton(text: $exampleText))
.multilineTextAlignment(.leading)
}
}
.navigationTitle("Clear button example")
}
}
}
The navigation view and form inside of the ContentView are not required. You could also just add the TextField inside the body, but with a form it’s much clearer and beautiful. 🙈
And so our final result looks like this:
I found this answer from #NigelGee on "Hacking with Swift".
.onAppear {
UITextField.appearance().clearButtonMode = .whileEditing
}
It really helped me out.
Simplest solution I came up with
//
// ClearableTextField.swift
//
// Created by Fred on 21.11.22.
//
import SwiftUI
struct ClearableTextField: View {
var title: String
#Binding var text: String
init(_ title: String, text: Binding<String>) {
self.title = title
_text = text
}
var body: some View {
ZStack(alignment: .trailing) {
TextField(title, text: $text)
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
.onTapGesture {
text = ""
}
}
}
}
struct ClearableTextField_Previews: PreviewProvider {
#State static var text = "some value"
static var previews: some View {
Form {
// replace TextField("Original", text: $text) with
ClearableTextField("Clear me", text: $text)
}
}
}