I have an array of object which I used to create a checkbox. The model has an id, name. I created a stackView to handle checkbox with id now I want to append items of selected checkbox to an array and be able to remove them when deselected. I am able to present all the views and it works well
below is my code
NetworkAdapter.instance.getFeaturesAmeneities()
.subscribe(onNext: {feat in
guard let data = feat.data else {return}
self.features.append(contentsOf: data)
self.stackFeature.axis = .vertical
self.stackFeature.distribution = .fill
self.stackFeature.spacing = 8
data.forEach {
print($0.id)
self.stv = CheckboxStackView()
self.stv?.label.text = $0.name
self.stv?.checkBox.tag = $0.id ?? 0
self.stackFeature.addArrangedSubview(self.stv!)
}
}).disposed(by: disposeBag)
Any help is appreciated
In order to answer this question, we first have to make the stack view reactive and declarative. This means that we have to be able to set the view using a single assignment and that needs to be an observer. This is just like what the RxCocoa library does for UICollectionView, UITableView and UIPickerView.
Writing the function is a bit advanced. First we take the signature from the other views above to define the shape of the function.
func items<Sequence: Swift.Sequence, Source: ObservableType>(_ source: Source) -> (_ viewForRow: @escaping (Int, Sequence.Element, UIView?) -> UIView) -> Disposable where Source.Element == Sequence
The above probably looks daunting. It's a function that takes a source sequence of sequences and returns a function that takes a closure for assembling the views and returns a Dispoable.
The completed function looks like this:
extension Reactive where Base: UIStackView {
func items<Sequence: Swift.Sequence, Source: ObservableType>(_ source: Source) -> (_ viewForRow: @escaping (Int, Sequence.Element, UIView?) -> UIView) -> Disposable where Source.Element == Sequence {
return { viewForRow in
return source.subscribe { event in
switch event {
case .next(let values):
let views = self.base.arrangedSubviews
let viewsCount = views.count
var valuesCount = 0
for (index, value) in values.enumerated() {
if index < viewsCount {
// update views that already exist
_ = viewForRow(index, value, views[index])
}
else {
// add new views if needed
let view = viewForRow(index, value, nil)
self.base.addArrangedSubview(view)
}
valuesCount = index
}
if valuesCount + 1 < viewsCount {
for index in valuesCount + 1 ..< viewsCount {
// remove extra views if necessary
self.base.removeArrangedSubview(views[index])
views[index].removeFromSuperview()
}
}
case .error(let error):
fatalError("Errors can't be allowed: \(error)")
case .completed:
break
}
}
}
}
}
The above can be used like this:
self.stackFeature.axis = .vertical
self.stackFeature.distribution = .fill
self.stackFeature.spacing = 8
let features = NetworkAdapter.instance.getFeaturesAmeneities()
.map { $0.data }
.share(replay: 1)
features
.bind(onNext: { [weak self] in self?.features.append(contentsOf: $0) })
.disposed(by: disposeBag)
features
.bind(to: stackFeature.rx.items) { (row, element, view) in
let myView = (view as? CheckboxStackView) ?? CheckboxStackView()
myView.label.text = element.name
myView.checkBox.tag = element.id ?? 0
return myView
}
.disposed(by: disposeBag)