I'm still a beginner in using Combine. I practice it on and off. Anyway, I have a view model to see changes in two text fields in my view controller as follows.
// ViewModel //
import Foundation
import Combine
class LoginViewModel {
var cancellable = [AnyCancellable]()
init(username: String, password: String) {
myUsername = username
myPassword = password
}
@Published var myUsername: String?
@Published var myPassword: String?
func validateUser() {
print("\(myUsername)")
print("\(myPassword)")
}
}
And my view controller goes as follows.
// ViewController //
import UIKit
import Combine
class HomeViewController: UIViewController {
// MARK: - Variables
var cancellable: AnyCancellable?
// MARK: - IBOutlet
@IBOutlet var usernameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
cancellable = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField)
.sink(receiveValue: { result in
if let textField = result.object as? UITextField {
if let text = textField.text {
let loginViewModel = LoginViewModel(username: text, password: "")
loginViewModel.validateUser()
}
}
})
}
}
So I use NSNotification as a publisher to see text changes over one of the text fields. And I cannot see text changes over two of them at the same time. Is there a better approach in seeing text changes over two text fields at the same time using Combine?
Muchos thankos.
I'm still a beginner in using Combine.
Is there a better approach in seeing text changes over two text fields at the same time using Combine?
Unfortunately, it is hard to find what would be the best practice when using Combine in UIKit apps, as, you may know, there are no simple ways to integrate Combine and UIKit. So, every developer is a beginner.
But one thing critically bad in your example is that you create a new instance of LoginViewModel
at each time Notification is received.
Other than that, I cannot say which is the better as there are very few examples on the web.
The following is just that I would write something like this in cases you described:
ViewModel
import UIKit
import Combine
class LoginViewModel {
//↓Use _plural_ form
var cancellables = Set<AnyCancellable>()
init() {
$myUsername
.removeDuplicates()
.combineLatest($myPassword.removeDuplicates())
.sink(receiveValue: {username, password in
self.validateUser(username: username, password: password)
})
.store(in: &cancellables)
}
convenience init(username: String, password: String) {
self.init()
myUsername = username
myPassword = password
}
@Published var myUsername: String?
@Published var myPassword: String?
func validateUser(username: String?, password: String?) {
print("\(username ?? "")")
print("\(password ?? "")")
//If you need to update some UI, add another publisher to which the ViewController can subscribe to.
//...
}
}
import UIKit
extension LoginViewModel {
func bind(_ textField: UITextField, to property: ReferenceWritableKeyPath<LoginViewModel, String?>) {
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: textField)
.sink(receiveValue: { result in
if let textField = result.object as? UITextField {
self[keyPath: property] = textField.text
}
})
.store(in: &cancellables)
}
}
ViewController
import UIKit
import Combine
class HomeViewController: UIViewController {
// MARK: - Variables
let viewModel = LoginViewModel()
// MARK: - IBOutlet
@IBOutlet var usernameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
viewModel.bind(usernameTextField, to: \.myUsername)
viewModel.bind(passwordTextField, to: \.myPassword)
}
}