SwiftUI inputAccesoryView Implementation

后端 未结 4 1003
感动是毒
感动是毒 2021-01-07 07:58

I am trying to implement an inputAccessoryView on a TextField in SwiftUI. The goal is to have a \"Done\" Button appear above the Keyboard which when pressed gets rid of the

相关标签:
4条回答
  • 2021-01-07 08:11
    struct InputAccessory: UIViewRepresentable  {
    
        var placeHolder: String
    
        func makeUIView(context: Context) -> UITextField {
            let toolbar = UIToolbar()
            toolbar.setItems([
                    // just moves the Done item to the right
                    UIBarButtonItem(
                        barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace
                        , target: nil
                        , action: nil
                    )
                    , UIBarButtonItem(
                        title: "Done"
                        , style: UIBarButtonItem.Style.done
                        , target: self
                        , action: nil
                    )
                ]
                , animated: true
            )
            toolbar.barStyle = UIBarStyle.default
            toolbar.sizeToFit()
    
            let customView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
            customView.backgroundColor = UIColor.red
            let sampleTextField =  UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
            sampleTextField.inputAccessoryView = toolbar
            sampleTextField.placeholder = placeHolder
    
            return sampleTextField
        }
        func updateUIView(_ uiView: UITextField, context: Context) {
            uiView.endEditing(true)
        }
    }
    
    struct ContentView : View {
    
        @State var email:String = "e"
    
        var body: some View {
    
            HStack{
            Circle()
                InputAccessory(placeHolder: "hello")
    
            Circle()
            }
        }
    }
    PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
    

    Now you can hide and show the textfield with the "showInput" state. The next problem is, that you have to open your keyboard at a certain event and show the textfield. That's again not possible with SwiftUI and you have to go back to UiKit and making it first responder. Overall, at the current state it's not possible to work with the keyboard or with the certain textfield method.

    0 讨论(0)
  • 2021-01-07 08:18

    I've solved this problem using 99% pure SwiftUI on iOS 14. That's my implementation:

    import SwiftUI
    
     struct ContentView: View {
    
        @State private var showtextFieldToolbar = false
        @State private var text = ""
    
        var body: some View {
        
            ZStack {
                VStack {
                    TextField("Write here", text: $text) { isChanged in
                        if isChanged {
                            showtextFieldToolbar = true
                        }
                    } onCommit: {
                        showtextFieldToolbar = false
                    }
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                }
            
                 VStack {
                    Spacer()
                    if showtextFieldToolbar {
                        HStack {
                            Spacer()
                            Button("Close") {
                                showtextFieldToolbar = false
                                UIApplication.shared
                                        .sendAction(#selector(UIResponder.resignFirstResponder),
                                                to: nil, from: nil, for: nil)
                            }
                            .foregroundColor(Color.black)
                            .padding(.trailing, 12)
                        }
                        .frame(idealWidth: .infinity, maxWidth: .infinity,
                               idealHeight: 44, maxHeight: 44,
                               alignment: .center)
                        .background(Color.gray)   
                    }
                }
            }
        }
    }
    
     struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    0 讨论(0)
  • 2021-01-07 08:24

    Here is a demo with custom toolbar & binding for entered text, but simplified by excluding on dismiss callback (as it is not important for approach demo), just to have less code. Hope it will be helpful.

    import SwiftUI
    import UIKit
    import Combine
    
    struct CustomInputTextField : UIViewRepresentable {
    
        @Binding var text: String
    
        let textField = UITextField(frame: CGRect(x:0, y:0, width: 100, height: 32)) // just any
    
        func makeUIView(context: UIViewRepresentableContext<CustomInputTextField>) -> UITextField {
            return textField
        }
    
        func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomInputTextField>) {
            self.textField.text = text
        }
    
        func makeCoordinator() -> CustomInputTextField.Coordinator {
            let coordinator = Coordinator(self)
    
            // configure a toolbar with a Done button
            let toolbar = UIToolbar()
            toolbar.setItems([
                // just moves the Done item to the right
                UIBarButtonItem(
                    barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace
                    , target: nil
                    , action: nil
                )
                , UIBarButtonItem(
                    title: "Done"
                    , style: UIBarButtonItem.Style.done
                    , target: coordinator
                    , action: #selector(coordinator.onSet)
                )
                ]
                , animated: true
            )
            toolbar.barStyle = UIBarStyle.default
            toolbar.sizeToFit()
    
            textField.inputAccessoryView = toolbar
            return coordinator
        }
    
        typealias UIViewType = UITextField
    
        class Coordinator: NSObject {
            let owner: CustomInputTextField
            private var subscriber: AnyCancellable
    
            init(_ owner: CustomInputTextField) {
                self.owner = owner
                subscriber = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: owner.textField)
                    .sink(receiveValue: { _ in
                        owner.$text.wrappedValue = owner.textField.text ?? ""
                    })
            }
    
            @objc fileprivate func onSet() {
                owner.textField.resignFirstResponder()
            }
    
        }
    }
    
    struct DemoCustomKeyboardInput : View {
    
        @State var email:String = ""
    
        var body: some View {
            VStack{
                CustomInputTextField(text: $email).border(Color.black)
                    .padding(.horizontal)
                    .frame(maxHeight: 32)
                Divider()
                Text("Entered text: \(email)")
            }
        }
    }
    
    struct DemoCustomKeyboardInput_Previews: PreviewProvider {
        static var previews: some View {
            DemoCustomKeyboardInput()
        }
    }
    
    0 讨论(0)
  • 2021-01-07 08:36

    I use this code multi line textfield.

    SwiftUI
    Swift5
    Version 11.3 (11C29)

    struct MultiLineTextField: UIViewRepresentable {
        @Binding var text: String
        let onEditingChanged: (Bool) -> Void
    
        init(text: Binding<String>, onEditingChanged: @escaping (Bool) -> Void = {_ in}) {
            self._text = text
            self.onEditingChanged = onEditingChanged
        }
    
        func makeCoordinator() -> MultiLineTextField.Coordinator {
            return MultiLineTextField.Coordinator(parent1: self)
        }
    
        func makeUIView(context: UIViewRepresentableContext<MultiLineTextField>) -> UITextView {
            let textView = UITextView()
    
            textView.isEditable = true
            textView.isUserInteractionEnabled = true
            textView.isScrollEnabled = true
    
            textView.font = .systemFont(ofSize: 20)
            textView.delegate = context.coordinator
            textView.text = self.text
    
            /******* toolbar add **********/
    
            let toolbar = UIToolbar()
            toolbar.setItems(
                [
                    UIBarButtonItem(
                        title: "Done",
                        style: UIBarButtonItem.Style.done,
                        target: self,
                        action: nil
                    )
                ]
                , animated: true
            )
            toolbar.barStyle = UIBarStyle.default
            toolbar.sizeToFit()
    
            textView.inputAccessoryView = toolbar
    
            /******* toolbar add **********/
    
            return textView
        }
    
        func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<MultiLineTextField>) {
            if uiView.text != self.text {
                uiView.text = self.text
            }
        }
    
        class Coordinator: NSObject, UITextViewDelegate {
            var parent: MultiLineTextField
            let onEditingChanged: (Bool) -> Void
    
            init(parent1: MultiLineTextField, onEditingChanged: @escaping (Bool) -> Void = {_ in}) {
                self.parent = parent1
                self.onEditingChanged = onEditingChanged
            }
    
            func textViewDidChange(_ textView: UITextView) {
                self.parent.text = textView.text
            }
    
            func textViewDidBeginEditing(_ textView: UITextView) {
                onEditingChanged(true)
            }
    
            func textViewDidEndEditing(_ textView: UITextView) {
                onEditingChanged(false)
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题