Search code examples
iosswiftrx-swift

setting ids on item in Stackview


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


Solution

  • 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)