Situation
I am implementing a filter view that has a button to apply the filters selected. The button shows how many results applying the current filters would yield.
Like this:
The label contains the count variable, the expectation is that when the count updates the button will reflect the new number of results.
Current setup
I am passing the data (count displayed in the button) like this: ViewModel -> View (SwiftUI) -> View (SwiftUI) -> UIViewControllerRepresentable -> UIViewController.
I have validated that the variable that holds the count updates whenever a filter is selected, but the button is not re-rendered to display the new value.
Here are some simplified code snippets (In the order described above):
ViewModel:
@MainActor class ViewModel: ObservableObject {
@Published var resultCount: Int = 0
@Published var status: String = ""
@Published var type: String = ""
init(){
self.addSubscribers()
})
}
func addSubscribers() {
// code here used to filter uses sink based on filters (status and type) selected, this will update resultCount
}
}
ContainerView:
struct ContainerView: View {
@EnvironmentObject private var vm: ViewModel
var body: some View {
VStack {
FilterView(status: $vm.status, type: $vm.type, resultCount: $vm.resultCount)
}
}
}
FilterView:
struct FilterView: View {
@Binding var status: String
@Binding var type: String
@Binding var resultCount: Int
var body: some View {
VStack {
FormViewControllerRepresentable(status: $status, type: $type, resultCount: $resultCount, showFilterSelectionView: $showFilterSelectionView)
}
}
}
FormViewControllerRepresentable:
struct FormViewControllerRepresentable: UIViewControllerRepresentable {
@Binding var status: String
@Binding var type: String
@Binding var resultCount: Int
func makeUIViewController(context: Context) -> FilterFormViewController {
FilterFormViewController(status: $status, type: $type, resultCount: $resultCount)
}
func updateUIViewController(_ uiViewController: FilterFormViewController, context: Context) {
uiViewController.status = status
uiViewController.type = type
uiViewController.resultCount = resultCount
}
}
UIViewController
class FilterFormViewController: UIViewController, UITextFieldDelegate {
@Binding var status: String
@Binding var type: String
@Binding var resultCount: Int
@Binding var showFilterSelectionView: Bool
init(status: Binding<String>, type: Binding<String>, resultCount: Binding<Int>, showFilterSelectionView: Binding<Bool>) {
self._status = status
self._type = type
self._resultCount = resultCount
self._showFilterSelectionView = showFilterSelectionView
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var formView = Form()
let stackView = UIStackView()
let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
// set delegate for Picker (filters)
formView.statusPicker.textField.delegate = self
formView.statusPicker.delegate = self
formView.typePicker.textField.delegate = self
formView.typePicker.delegate = self
stackView.addArrangedSubview(getButtonsStackView())
scrollView.addSubviews([stackView])
view.addSubview(scrollView)
}
func getButtonsStackView() -> UIStackView {
// HERE is the button. even though resultCount updates, the button is not re-rendered
// UIButton here is actually a custom component that conforms to UIButton. In the custom component setTitle(label, for: .normal) is called
let submitButton = UIButton( label: "Show \(self.resultCount) results")
submitButton.isEnabled = true
submitButton.addTarget(self, action: #selector(hideFilters), for: .touchUpInside)
let buttonsView = UIStackView(
arrangedSubviews: [
submitButton
]
)
return buttonsView
}
func textFieldDidEndEditing(_ textField: UITextField) {
// getters here are working well and setting the value for status & type is updating the variable in the viewModel
// THE PROBLEM IS: While this updates the value for the filters and that in turns updates the result count, the button won't re-render with the new value
status = formView.getStatus()
type = formView.getType()
}
}
Selecting any option will automatically load the page