SwiftUI -仅为一个TextField添加键盘工具栏按钮会为所有TextField添加该按钮

yruzcnhs  于 2022-11-21  发布在  Swift
关注(0)|答案(6)|浏览(181)

背景
我有两个TextField,其中一个的键盘类型为.decimalPad
鉴于使用十进制小键盘关闭时没有“完成”按钮,而不是像标准键盘的回车键,我想在SwiftUI中的键盘 * 上方的工具栏中添加一个“完成”按钮,仅用于十进制键盘 *。

问题

由于某种原因,将.toolbar * 添加到任何TextField * 会将其添加到所有TextField!我尝试过条件修饰符,使用焦点状态并检查Field值(但由于某种原因,检查时没有设置它,可能是排序问题?),但它仍然会为两个TextField添加键盘上方的工具栏。
我怎么可能只有一个接受数字的TextField,而另一个接受字符串的TextField没有.toolbar呢?
编号

  • 请注意,我试着做了一个最小的例子,你可以直接复制并粘贴到Xcode中,然后自己运行它。对于Xcode 13.2,在显示TextField s的键盘方面有一些问题,特别是在一个表单中,所以可能需要模拟器来正确运行它,并使用cmd+K调出键盘。*
import SwiftUI

struct TestKeyboard: View {
    @State var str: String = ""
    @State var num: Float = 1.2

    @FocusState private var focusedField: Field?
    private enum Field: Int, CaseIterable {
        case amount
        case str
    }

    var body: some View {
        VStack {
            Spacer()
            
            // I'm not adding .toolbar here...
            TextField("A text field here", text: $str)
                .focused($focusedField, equals: .str)

            // I'm only adding .toolbar here, but it still shows for the one above..
            TextField("", value: $num, formatter: FloatNumberFormatter())
                .keyboardType(.decimalPad)
                .focused($focusedField, equals: .amount)
                .toolbar {
                    ToolbarItem(placement: .keyboard) {
                        Button("Done") {
                            focusedField = nil
                        }
                    }
                }

            Spacer()
        }
    }
}

class FloatNumberFormatter: NumberFormatter {
    override init() {
        super.init()
        
        self.numberStyle = .currency        
        self.currencySymbol = "€"
        self.minimumFractionDigits = 2
        self.maximumFractionDigits = 2
        self.locale = Locale.current
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}

// So you can preview it quickly
struct TestKeyboard_Previews: PreviewProvider {
    static var previews: some View {
        TestKeyboard()
    }
}
7gyucuyw

7gyucuyw1#

尝试使工具栏内容有条件并将工具栏移出,如下所示。(现在不可能测试-只是想法)
注:在真实的器械上进行测试

var body: some View {
    VStack {
        Spacer()
        
        TextField("A text field here", text: $str)
            .focused($focusedField, equals: .str)

        TextField("", value: $num, formatter: FloatNumberFormatter())
            .focused($focusedField, equals: .amount)
            .keyboardType(.decimalPad)

        Spacer()
    }
    .toolbar {          // << here !!
        ToolbarItem(placement: .keyboard) {
            if field == .amount {             // << here !!
               Button("Done") {
                  focusedField = nil
               }
            }
        }
    }

}
ybzsozfc

ybzsozfc2#

使用introspect,您可以在View的任何部分执行类似的操作:

.introspectTextField { textField in
                    textField.inputAccessoryView = UIView.getKeyboardToolbar {
                        textField.resignFirstResponder()
                    }
            }

以及对于getKeyboardToolbar

extension UIView {

static func getKeyboardToolbar( _ callback: @escaping (()->()) ) -> UIToolbar {
        let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 44))
        let doneButton = CustomBarButtonItem(title: "Done".localized, style: .done) { _ in
            callback()
        }
        
        let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        toolBar.items = [space, doneButton]
        
        return toolBar
    }

}

对于CustomBarButtonItem,这是一个条形按钮项,它采用闭包

import UIKit

class CustomBarButtonItem: UIBarButtonItem {
    typealias ActionHandler = (UIBarButtonItem) -> Void

    private var actionHandler: ActionHandler?

    convenience init(image: UIImage?, style: UIBarButtonItem.Style, actionHandler: ActionHandler?) {
        self.init(image: image, style: style, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    convenience init(title: String?, style: UIBarButtonItem.Style, actionHandler: ActionHandler?) {
        self.init(title: title, style: style, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, actionHandler: ActionHandler?) {
        self.init(barButtonSystemItem: systemItem, target: nil, action: #selector(barButtonItemPressed(sender:)))
        target = self
        self.actionHandler = actionHandler
    }

    @objc func barButtonItemPressed(sender: UIBarButtonItem) {
        actionHandler?(sender)
    }
}
ubof19bj

ubof19bj3#

我试了很多次,但最后还是在下面的一个。

.focused($focusedField, equals: .zip)
                .toolbar{
                    ToolbarItem(placement: .keyboard) {
                        switch focusedField{
                        case .zip:
                            HStack{
                                Spacer()
                                Button("Done"){
                                    focusedField = nil
                                }
                            }
                        default:
                                Text("")
                        }
                    }
                }
rlcwz9us

rlcwz9us4#

这就是我的解决方案:

func textFieldSection(title: String,
                      text: Binding<String>,
                      keyboardType: UIKeyboardType,
                      focused: FocusState<Bool>.Binding,
                      required: Bool) -> some View {
    TextField(
        vm.placeholderText(isRequired: required),
        text: text
    )
    .focused(focused)
    .toolbar {
        ToolbarItemGroup(placement: .keyboard) {
            if focused.wrappedValue {
                Spacer()
                Button {
                    focused.wrappedValue = false
                } label: {
                    Text("Done")
                }
            }
        }
    }
}

对于我的项目,我在一个View上有五个TextField视图,所以我在View的扩展中创建了这个方法。
我传递了唯一的FocusState<Bool>.Binding值,并在ToolbarItemGroup闭包中使用它来确定是否应该显示内容(Spacer、Button)。如果特定的TextField是focused,则显示工具栏内容(所有其他非焦点TextField都不会显示)。

kwvwclae

kwvwclae5#

我发现将每个TextField Package 在它自己的NavigationView中会给每个TextField提供它自己的上下文,因此会有一个唯一的工具栏。感觉不太对,我在控制台中看到了约束警告。使用如下内容:

var body: some View {
    VStack {
        Spacer()
        
        // I'm not adding .toolbar here...
        NavigationView {
          TextField("A text field here", text: $str)
            .focused($focusedField, equals: .str)
        }
        // I'm only adding .toolbar here, but it still shows for the one above..
        NavigationView {
          TextField("", value: $num, formatter: FloatNumberFormatter())
            .keyboardType(.decimalPad)
            .focused($focusedField, equals: .amount)
            .toolbar {
                ToolbarItem(placement: .keyboard) {
                    Button("Done") {
                        focusedField = nil
                    }
                }
            }
        }
        Spacer()
    }
}
5jdjgkvh

5jdjgkvh6#

有工作。但其他TextField仍会显示工具列。
--- update ---嗨,我更新了代码以使用ViewModifier,使代码更易于使用,这次代码确实编译并运行〉_〈

struct ToolbarItemWithShow<Toolbar>: ViewModifier where Toolbar: View {
    var show: Bool
    let toolbar: Toolbar
    let placement: ToolbarItemPlacement
    
    func body(content: Content) -> some View {
        content.toolbar {
            ToolbarItemGroup(placement: placement) {
                ZStack(alignment: .leading) {
                    if show {
                        HStack { toolbar }
                            .frame(width: UIScreen.main.bounds.size.width - 12)
                    }
                }
            }
        }
    }
}

extension View {
    func keyboardToolbar<ToolBar>(_ show: Bool, @ViewBuilder toolbar: () -> ToolBar) -> some View where ToolBar: View {
        modifier(ToolbarItemWithShow(show: show, toolbar: toolbar(), placement: .keyboard))
    }
}

struct ContentView: View {
    private enum Field: Hashable {
        case name
        case age
        case gender
    }
    
    @State var name = "Ye"
    @State var age = "14"
    @State var gender = "man"
    
    @FocusState private var focused: Field?

    
    var body: some View {
        VStack {
            TextField("Name", text: $name)
                .focused($focused, equals: .name)
                .keyboardToolbar(focused == .name) {
                    Text("Input Name")
                }
            
            TextField("Age", text: $age)
                .focused($focused, equals: .age)
                .keyboardToolbar(focused == .age) {
                    Text("Input Age")
                }

            TextField("Gender", text: $gender)
                .focused($focused, equals: .gender)
                .keyboardToolbar(focused == .gender) {
                    Text("Input Sex")
                }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

---老---

struct TextFieldWithToolBar<Label, Toolbar>: View where Label: View, Toolbar: View {
    @Binding public var text: String
    public let toolbar: Toolbar?

    @FocusState private var focus: Bool

    var body: some View {
        TextField(text: $text, label: { label })
            .focused($focus)
            .toolbar {
                ToolbarItemGroup(placement: .keyboard) {
                    ZStack(alignment: .leading) {
                        if focus {
                            HStack {
                                toolbar
                                Spacer()
                                Button("Done") {
                                    focus = false
                                }
                            }
                            .frame(width: UIScreen.main.bounds.size.width - 12)
                        }
                    }
                }
            }
    }
}

TextFieldWithToolBar("Name", text: $name)
TextFieldWithToolBar("Name", text: $name){
    Text("Only Brand")
}
TextField("Name", "Set The Name", text: $name)

with Donewith Toolbarwithout

相关问题