Cam Scen
Cam Scen

Reputation: 652

SwiftUI: Add ClearButton to TextField

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)
            }
        }
    }
}

Upvotes: 46

Views: 24611

Answers (12)

Richard D
Richard D

Reputation: 5665

How to Add a Clear Button to a TextField in iOS 14 (without UIViewRepresentable or @FocusState), in 2024+

After experimenting with different solutions and checking out some of the answers here, I ended up combining a few ideas to get something that works today (2024). It mimics the UITextField clear button with smooth animations, while still supporting older APIs like iOS 14. Checking focus became easier with @FocusState but unfortunately that requires iOS15.

If you're like me you probably felt that using using UIViewRepresentable from UIKit is overkill for just adding a clear button. I wanted to stick with SwiftUI and keep things simple.

Solution:

import SwiftUI

/// A custom TextField with a clear button that mimics iOS 15+ behavior, but works with iOS 14.
struct ClearableTextField: View {
    let placeholder: String
    @Binding var text: String

    @State private var isTextFieldFocused: Bool = false

    var body: some View {
        HStack {
            TextField(placeholder, text: $text, onEditingChanged: { isEditing in
                isTextFieldFocused = isEditing
            })

            Image(systemName: "xmark.circle.fill")
                .foregroundColor(Color(UIColor.tertiaryLabel)) // Semantic colors for light/dark mode
                .opacity(!text.isEmpty && isTextFieldFocused ? 1 : 0) // Fade animation
                .onTapGesture { text = "" }
                .animation(.easeInOut(duration: 0.2), value: text)
                .animation(.easeInOut(duration: 0.2), value: isTextFieldFocused)
            }
        }
    }
}

#Preview {
    @State var text = "Example text"
    Form {
        ClearableTextField(placeholder: "Enter text here", text: $text)
    }
}

Key Points:

  • The clear button only shows when the text field is focused and has text, disappearing when it’s not needed.
  • Smooth animation to mimic the system behavior.
  • Works on iOS 14 and up, no need for UIViewRepresentable.
  • Uses semantic colors that adapt to light and dark mode.
  • Clear button doesn’t overlap text, for better UX.

If you can update your minimum iOS version, definitely: do that! But if you’re still supporting iOS 14, hope this helps!

Upvotes: 0

George Valkov
George Valkov

Reputation: 1459

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)
                        .padding(.vertical, 8)
                        .contentShape(Rectangle())
                }
            }
        }
    }
}

clear button inside TextView

Upvotes: 48

Mahi Al Jawad
Mahi Al Jawad

Reputation: 1010

Works in iOS 18.0 too

Recently I have noticed in some older solutions that the clear button was not tappable, rather it was triggering the textField's text. The following solution fixes the problem:

// Make a ViewModifier

import SwiftUI

struct TextFieldClearButton: ViewModifier {
    @Binding var text: String
    
    func body(content: Content) -> some View {
        ZStack(alignment: .trailing) {
            content
            Button {
                text = ""
            } label: {
                Image(systemName: "multiply.circle.fill")
            }
            .opacity(text.isEmpty ? 0 : 1)
            .buttonStyle(.plain)
            .foregroundColor(.secondary)
            .padding(.trailing, 4)
        }
    }
}

extension TextField {
    func clearButton(on text: Binding<String>) -> some View {
        modifier(TextFieldClearButton(text: text))
    }
}


Use in your TextField's:


    TextField("Add your note", text: $text)
                        .clearButton(on: $text)
    // Here we have a `@State var text: String = ""` which is bind to the TextField

Mainly, making the clear button to buttonStyle(.plain) fixes the tap not triggering issue on the older solutions.

Upvotes: 0

hstdt
hstdt

Reputation: 6243

Solution 1 (BEST): SwiftUI-Introspect

@_spi(Advanced) import SwiftUIIntrospect

TextField("", text: $text)
    .introspect(.textField, on: .iOS(.v13...)) { textField in
        textField.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

Upvotes: 32

Medhi
Medhi

Reputation: 3225

For a clean Textfield in SwiftUI, with no overlapping between user inputted text and clear button, here an example:

                ZStack(alignment: .trailing)
                {
                    TextField("Tapez une url", text: $myViewModel.urlString, onCommit: {
                        // action
                    })
                    .submitLabel(.done)
                    .disableAutocorrection(true)
                    .padding(.trailing, 30)
                    .padding(.top, 3)
                    .padding(.leading, 5)
                    .frame(height: 30, alignment: .top)
                    .background(.white, in: .rect(cornerRadius: 5))
                    .overlay(
                        Button(action: {
                            myViewModel.urlString = ""
                        }) {
                            Image(systemName: "delete.left")
                            .opacity(myViewModel.urlString.isEmpty ? 0 : 1)
                            .symbolRenderingMode(.palette)
                            .foregroundColor(.black)
                        }
                        .padding(.trailing, 5),
                        alignment: .trailing
                    )
                }

Upvotes: 0

mohammed.r.ghate
mohammed.r.ghate

Reputation: 81

If you want to have multiple TextField and also want to tap on the screen and hide the proper clear button you can do like this:

    struct ClearTextFiledButton: ViewModifier {
    
    @Binding var text: String
    @FocusState private var showDeleteButton: Bool
    
    func body(content: Content) -> some View {
        content
            .focused($showDeleteButton)
            .overlay(alignment: .trailing) {
                if showDeleteButton {
                    Button {
                        text = ""
                    } label: {
                        Image(systemName: "xmark.circle.fill").foregroundColor(Color.gray)
                    }
                    .padding(.trailing,8)
                    .opacity(text.isEmpty ? 0 : 1)
                }
            }
    }
    
}

extension View {
    func showClearButton(_ text: Binding<String>) -> some View {
        return self.modifier(ClearTextFiledButton(text: text))
    }
}

Upvotes: 0

usama n
usama n

Reputation: 210

Its simple to Just do like this

TextField("Please enter", text: $viewModel.label).onAppear { UITextField.appearance().clearButtonMode = .whileEditing }

Upvotes: 0

shariebg
shariebg

Reputation: 109

I found this answer from @NigelGee on "Hacking with Swift".

.onAppear {
    UITextField.appearance().clearButtonMode = .whileEditing
}

It really helped me out.

Upvotes: 7

Fred Klein
Fred Klein

Reputation: 658

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)
        }
    }
}

enter image description here

Upvotes: 2

Sorin Lica
Sorin Lica

Reputation: 7636

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)
}

Upvotes: 41

Karoly Nyisztor
Karoly Nyisztor

Reputation: 3595

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")
        }
    }
}

Upvotes: 5

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119214

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))

Upvotes: 7

Related Questions