SwiftUI:在TextField中添加清除按钮

41

我正在尝试在SwiftUI的TextField中添加ClearButton,当特定的TextField被选中时。

最接近的方法是创建一个ClearButton ViewModifier,并使用.modifer()将其添加到TextField中。

唯一的问题是ClearButton是永久性的,并且在TextField未被选中时不会消失。

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

为什么你取消了我回答的采纳? - Mojtaba Hosseini
@MojtabaHosseini 我猜是因为被接受的答案是对Cam现有代码的修改。它似乎也更加健壮,根据数据源本身进行更新,而不是跟踪新的派生状态。 - Ky -
10个回答

44

使用ZStack将清除按钮定位在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)
            }
        }
    }
}

TextView内的清除按钮


13
最好使用HStack替换ZStack,否则较长的文本将会出现在图片下方。 - norekhov
哎呀,它不行了。我认为这个按钮在新的iOS版本上不再触发。 - George Valkov
TextField("名称", text: $newFolderName) .onAppear { UITextField.appearance().clearButtonMode = .whileEditing } 这对我有用。 - Shrikant Phadke

40

使用.appearance()来激活按钮

var body: some View {
    UITextField.appearance().clearButtonMode = .whileEditing
    return TextField(...)
}

尝试使用以下内容进行重复使用:

func TextFieldUIKit(text: Binding<String>) -> some View{
    UITextField.appearance().clearButtonMode = .whileEditing
    return TextField("Nombre", text: text)
}

这对我很有效。我很好奇你是怎么找到这个解决方案的? - Trev14
与'TapGesture()'结合使用时,它不会清除文本! - Peter Kreinz
@PeterKreinz,这是因为TapGesture()捕获了点击而不是清除按钮,请尝试使用.simultaneosGesture(...) - Sorin Lica
9
这个解决方案会有副作用。如果你使用UIAppearance,你会为所有UITextField实例设置clearButtonMode属性,而不仅仅是你想要更改的那一个。 - SiPe
不清除文本iOS 16(无点击手势) - zslavman

30

=== 解决方案1(最佳): Introspect https://github.com/siteline/SwiftUI-Introspect

import Introspect
TextField("", text: $text)
    .introspectTextField(customize: {
        $0.clearButtonMode = .whileEditing
    })

=== 解决方案 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
        }
    }
}

用法:

@State private var name: String

...

Form {
    Section() {
        TextField("NAME", text: $name).modifier(ClearButton(text: $name))
    }
}

=== 解决方案 3:全局外观

UITextField.appearance().clearButtonMode = .whileEditing

我尝试使用这个,但绑定的值没有改变。在 ClearButton 块内,第一次按下时 text 的值是 TextField 的值。然后,它确实被清除了,但清除后的 text 值没有设置到 TextField 上。所以,我正在努力理解为什么我们需要 _text 属性。我仍在努力理解 SwiftUI,如果显而易见,请原谅。 - raf
为什么带有“on tap”功能的图像更好? - TruMan1

7
您可以在您的修饰符(modifier)中添加另一个绑定(Binding):
@Binding var visible: Bool

然后将它绑定到按钮的不透明度:
.opacity(visible ? 1 : 0)

然后再添加另一个“State”来检查“textField”:
@State var showClearButton = true

最后更新文本字段:
TextField("Some Text", text: $someBinding, onEditingChanged: { editing in
    self.showClearButton = editing
}, onCommit: {
    self.showClearButton = false
})
.modifier( ClearButton(text: $someBinding, visible: $showClearButton))

谢谢!我正在使用多个TextField作为Form的一部分,是否有一种解决方案,不需要为每个TextField创建一个showClearButton @State - Cam Scen
是的,为您的自定义TextField创建自定义View - Mojtaba Hosseini

7

我在“Hacking with Swift”上找到了这个答案,来自@NigelGee

该答案介绍了如何给文本框添加一个清除按钮。
.onAppear {
    UITextField.appearance().clearButtonMode = .whileEditing
}

它真的帮助了我。


5

虽然不完全符合您的要求,但这将允许您根据文本内容显示/隐藏按钮:

HStack {
    if !text.isEmpty {
        Button(action: {
            self.text = ""
        }) {
            Image(systemName: "multiply.circle")
        }
    }
}

4

在初始化新项目之后,我们需要创建一个简单的视图修饰符,稍后我们将应用于文本字段。视图修饰符的任务是检查文本字段元素中的内容,并在其中显示清除按钮(如果有内容)。此外,它还处理按钮上的轻击并清除内容。 现在来看一下该视图修饰符:

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

我们的代码应该是自说明的,没有包含任何复杂的逻辑,易于理解。 只需将文本字段包装在HStack中并添加按钮,如果文本字段不为空。按钮本身只有一个动作,即删除文本字段的值。

对于清除图标,我们使用了苹果的SF Symbols 2库中的delete.left图标,但您也可以使用其他图标甚至自定义图标。

修饰符的绑定与我们应用于文本字段的绑定相同。如果没有它,我们将无法检查内容或清除字段本身。

现在,在ContentView.swift文件中,我们只需添加一个TextField元素并将我们的修饰符应用于它 - 就这样!

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

ContentView中的导航视图和表单不是必需的。您也可以将TextField直接添加到body中,但借助表单,它更清晰、美观。

最终结果如下:

enter image description here


如果文本字段使用了.textFieldStyle(.roundedBorder),那么当文本字段中有文本时,文本字段会向左缩小,以便删除按钮出现在文本字段外部。 - Jonny

1

我想到的最简单的解决方案

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


0

这很简单,只需要像这样做:

TextField("请输入", text: $viewModel.label).onAppear { UITextField.appearance().clearButtonMode = .whileEditing }


如果您在“View”上这样做,一旦此文本字段出现在应用程序的生命周期中,其他视图中的所有文本字段在编辑时也会开始显示清除按钮。如果我们只想在特定的“TextField”上更改行为,这并不理想。 - undefined

0
如果您想要有多个文本字段,并且希望在屏幕上轻触并隐藏适当的清除按钮,可以按照以下方式操作:
    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))
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接