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.