3

Ok I know how to wrap UIKit components like TextField to use with SwiftUI using UIViewRepresentable and Coordinator. And it all works well. But how can I customise this components from SwiftUI perspective in way that is native to SwiftUI i.e. using modifiers?

I have example wrapper for UITextField to enable some additional delegate methods handling. And this works similar to native approach with closures passed to constructor. But how I can then apply some styling to this UITextField using SwiftUI modifiers?

struct PasswordField: UIViewRepresentable {

    @Binding var text: String
    @Binding var isSecured: Bool

    let onBeginEditing: () -> Void
    let onEndEditing: () -> Void
    let onEditingChanged: (Bool) -> Void
    let onCommit: () -> Void


    init(text: Binding<String>, isSecured : Binding<Bool> = .constant(true),
         onBeginEditing: @escaping () -> Void = { },
         onEndEditing: @escaping () -> Void = { },
         onEditingChanged: @escaping (Bool) -> Void = { _ in },
         onCommit: @escaping () -> Void = { }) {

        self._text = text
        self._isSecured = isSecured
        self.onBeginEditing = onBeginEditing
        self.onEndEditing = onEndEditing
        self.onEditingChanged = onEditingChanged
        self.onCommit = onCommit
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(onBeginEditing: onBeginEditing, onEndEditing: onEndEditing, onEditingChanged: onEditingChanged, onCommit: onCommit)
    }

    func makeUIView(context: UIViewRepresentableContext<PasswordField>) -> UITextField {

        let textField = UITextField()
        textField.delegate = context.coordinator
        textField.addTarget(context, action: #selector(Coordinator.valueChanged(sender:)), for: .valueChanged)
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<PasswordField>) {

        uiView.text = text
        uiView.isSecureTextEntry = isSecured
    }
}

// MARK: - Coordinator
extension PasswordField {

    class Coordinator: NSObject, UITextFieldDelegate {

        let onBeginEditing: () -> Void
        let onEndEditing: () -> Void
        let onEditingChanged: (Bool) -> Void
        let onCommit: () -> Void

        init(onBeginEditing: @escaping () -> Void,
             onEndEditing: @escaping () -> Void,
             onEditingChanged: @escaping (Bool) -> Void,
             onCommit: @escaping () -> Void) {
            self.onBeginEditing = onBeginEditing
            self.onEndEditing = onEndEditing
            self.onEditingChanged = onEditingChanged
            self.onCommit = onCommit
        }

        @objc func valueChanged(sender: UITextField) {
            onEditingChanged(true)
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            textField.resignFirstResponder()
            onCommit()
            return true
        }

        func textFieldDidBeginEditing(_ textField: UITextField) {
            onBeginEditing()
            onEditingChanged(true)
        }

        func textFieldDidEndEditing(_ textField: UITextField) {
            onEditingChanged(false)
            onEndEditing()
        }
    }
}

struct PasswordField_Previews: PreviewProvider {
    static var previews: some View {
        PasswordField(text: .constant("PaSSword"), isSecured: .constant(true))
    }
}

Now I would like to use it like this:

PasswordField(text: self.$validator.value, onEditingChanged: { editing in
                            self.onEditingChanged?(editing)
                            self.isEdited = true
                        }, onCommit: {
                            self.validator.validateField()
                            self.validator.validateFieldAsync()
                        })
                        .font(.custom("AvenirNext-Light", size: 13)) // THIS IS IMPORTANT
                        .foregroundColor(Color("BodyText")) // THIS IS IMPORTANT
                        .frame(maxHeight: geometry.size.height)
                        .offset(x: 0, y: 16)
                        .padding(10)

Ok I know I can hardcode this style in my wrapper, or pass this via constructor. yeah but this is not how SwiftUI do this natively.

Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143

1 Answers1

-1

You need to implement your own custom modifiers. See the documentation for ViewModifier protocol, although the SwiftUI docs are still very thin.

However, you don't have to implement your own TextField for handling password input, because SwiftUI now supports natively what you want: SecureField.

Claudiu
  • 1
  • 1
  • Link only answers are not considered good practice. Please see https://stackoverflow.com/help/how-to-answer – Marcello B. Feb 18 '20 at 18:55
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/25387947) – Marcello B. Feb 18 '20 at 18:55
  • @SuitBoyApps, thanks for pointing it out. I have removed the links, however I can't provide a more complete answer at this time. – Claudiu Feb 18 '20 at 19:33