I want to create a custom textfield for SwiftUI by using UIViewRepresentable
. To handle the didBeginEditing
and didFinishEditing
actions I subscribed to the publishers that I previously created as an extension to the UITextField
class. So I put the cancellables
variable as an inout argument but get the error "Cannot use mutating member on immutable value: 'self' is immutable"
.
Is there any other way to do this?
My custom TextField
struct TextFieldView: UIViewRepresentable {
private var cancellables = Set<AnyCancellable>()
private let label: String
private let activeIcon: String
private let inactiveIcon: String
private let helperText: String?
init(_ label: String, activeIcon: String, inactiveIcon: String, helperText: String?) {
self.label = label
self.activeIcon = activeIcon
self.inactiveIcon = inactiveIcon
self.helperText = helperText
}
func makeUIView(context: Context) -> MDCOutlinedTextField {
return MDCOutlinedTextField()
}
func updateUIView(_ uiView: MDCOutlinedTextField, context: Context) {
uiView.tintColor = .black
// ...
uiView.didBeginEditingPublisher.sink { _ in
uiView.leadingView = UIImageView(image: UIImage(named: activeIcon))
}.store(in: &cancellables) // ERROR: Cannot use mutating member on immutable value: 'self' is immutable
uiView.didFinishEditingPublisher.sink { _ in
uiView.leadingView = UIImageView(image: UIImage(named: inactiveIcon))
}.store(in: &cancellables) // ERROR: Cannot use mutating member on immutable value: 'self' is immutable
}
}
UITextField extension
public extension UITextField {
var didBeginEditingPublisher: AnyPublisher<String, Never> {
NotificationCenter.default
.publisher(for: UITextField.textDidBeginEditingNotification, object: self)
.map { ($0.object as? UITextField)?.text ?? "" }
.eraseToAnyPublisher()
}
var didFinishEditingPublisher: AnyPublisher<String, Never> {
NotificationCenter.default
.publisher(for: UITextField.textDidEndEditingNotification, object: self)
.map { ($0.object as? UITextField)?.text ?? "" }
.eraseToAnyPublisher()
}
}
I solved the problem in the following way: Used the Coordinator
in which I implemented the UITextFieldDelegate
methods to catch the moment when user begins and finish editing
Thanks jnpdx for the tip
struct TextFieldView: UIViewRepresentable {
private var cancellables = Set<AnyCancellable>()
private let label: String
private let activeIcon: String
private let inactiveIcon: String
private let helperText: String?
init(_ label: String, activeIcon: String, inactiveIcon: String, helperText: String?) {
self.label = label
self.activeIcon = activeIcon
self.inactiveIcon = inactiveIcon
self.helperText = helperText
}
func makeCoordinator() -> Coordinator {
Coordinator(activeIcon: activeIcon, inactiveIcon: inactiveIcon)
}
func makeUIView(context: Context) -> MDCOutlinedTextField {
let view = MDCOutlinedTextField()
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: MDCOutlinedTextField, context: Context) {
uiView.tintColor = .black
// ...
}
}
extension TextFieldView {
class Coordinator: NSObject, UITextFieldDelegate {
private var activeIcon: String
private var inactiveIcon: String
init(activeIcon: String, inactiveIcon: String) {
self.activeIcon = activeIcon
self.inactiveIcon = inactiveIcon
}
func textFieldDidBeginEditing(_ textField: UITextField) {
let textField = textField as? MDCOutlinedTextField
textField?.leadingView = UIImageView(image: UIImage(named: activeIcon))
}
func textFieldDidEndEditing(_ textField: UITextField) {
let textField = textField as? MDCOutlinedTextField
textField?.leadingView = UIImageView(image: UIImage(named: inactiveIcon))
}
}
}