I'm new to RxSwift. This is quite tricky.
I'm creating like ToDoList that views which are tableView and add-item view are separated by TabBarController. I have successfully displayed the list array and added a new item into tableView.
I also wanted to display the number of array's count and favourite count in the view that has tableView so that I have displayed it by throwing a value with .just
.
But displaying a value based on the result of the array displayed by SearchBar, the value is not reflected as I expected.
In MainViewModel, I made sure if I could get the number of array's count properly by print
, but apparently the value was fine.
It is just not reflected in the View.
// Model
struct Item: Codable {
var name = String()
var detail = String()
var tag = String()
var memo = String()
var fav = Bool()
var cellNo = Int()
init(name: String, detail: String, tag: String, memo: String, fav: Bool, celllNo: Int) {
self.name = name
self.detail = detail
self.tag = tag
self.memo = memo
self.fav = fav
self.cellNo = celllNo
}
init() {
self.init(
name: "Apple",
detail: "ringo",
tag: "noun",
memo: "",
fav: false,
celllNo: 0
)
}
}
struct SectionModel: Codable {
var list: [Item]
}
extension SectionModel: SectionModelType {
var items: [Item] {
return list
}
init(original: SectionModel, items: [Item]) {
self = original
self.list = items
}
}
Singleton share class
final class Sharing {
static let shared = Sharing()
var items: [Item] = [Item()]
var list: [SectionModel] = [SectionModel(list: [Item()])] {
didSet {
UserDefault.shared.saveList(list: list)
}
}
let listItems = BehaviorRelay<[SectionModel]>(value: [])
}
extension Sharing {
func calcFavCount(array: [Item]) -> Int {
var count = 0
if array.count > 0 {
for i in 0...array.count - 1 {
if array[i].fav {
count += 1
}
}
}
return count
}
}
// MainTabViewController
class MainTabViewController: UIViewController {
@IBOutlet weak var listTextField: UITextField!
@IBOutlet weak var tagTextField: UITextField!
@IBOutlet weak var itemCountLabel: UILabel!
@IBOutlet weak var favCountLabel: UILabel!
@IBOutlet weak var favIcon: UIImageView!
@IBOutlet weak var infoButton: UIButton!
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
private lazy var viewModel = MainTabViewModel(
searchTextObservable: searchTextObservable
)
private let disposeBag = DisposeBag()
private var dataSource: RxTableViewSectionedReloadDataSource<SectionModel>!
override func viewDidLoad() {
super.viewDidLoad()
setupTableViewDataSource()
tableViewSetup()
listDetailSetup()
}
// create Observable searchBar.text to pass to ViewModel
var searchTextObservable: Observable<String> {
let debounceValue = 200
// observable to get the incremental search text
let incrementalTextObservable = rx
.methodInvoked(#selector(UISearchBarDelegate.searchBar(_:shouldChangeTextIn:replacementText:)))
.debounce(.milliseconds(debounceValue), scheduler: MainScheduler.instance)
.flatMap { [unowned self] _ in Observable.just(self.searchBar.text ?? "") }
// observable to get the text when the clear button or enter are tapped
let textObservable = searchBar.rx.text.orEmpty.asObservable()
// merge these two above
let searchTextObservable = Observable.merge(incrementalTextObservable, textObservable)
.skip(1)
.debounce(.milliseconds(debounceValue), scheduler: MainScheduler.instance)
.distinctUntilChanged()
return searchTextObservable
}
func setupTableViewDataSource() {
dataSource = RxTableViewSectionedReloadDataSource<SectionModel>(configureCell: {(_, tableView, indexPath, item) in
let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell
cell.selectionStyle = .none
cell.backgroundColor = .clear
cell.configure(item: item)
return cell
})
}
func tableViewSetup() {
tableView.rx.itemDeleted
.subscribe {
print("delete")
}
.disposed(by: disposeBag)
viewModel.dispItems.asObservable()
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
func listDetailSetup() {
viewModel.itemCountObservable
.bind(to: itemCountLabel.rx.text)
.disposed(by: disposeBag)
viewModel.favCountObservable
.bind(to: favCountLabel.rx.text)
.disposed(by: disposeBag)
}
}
MainTabViewModel
final class MainTabViewModel {
private let disposeBag = DisposeBag()
private let userDefault: UserDefaultManager
var dispItems = BehaviorRelay<[SectionModel]>(value: [])
private let shared = Sharing.shared
// lazy var itemCount = shared.list[0].list.count
// lazy var favCount = shared.calcFavCount
var itemCountObservable: Observable<String>
var favCountObservable: Observable<String>
init(searchTextObservable: Observable<String>,
userDefault: UserDefaultManager = UserDefault()) {
self.userDefault = userDefault
let initialValue = shared.list
shared.listItems.accept(initialValue)
dispItems = shared.listItems
// this part is to display the initil number -> success
var itemCount = shared.list[0].list.count
itemCountObservable = .just(itemCount.description + " items")
var favCount = shared.calcFavCount(array: shared.list[0].list)
favCountObservable = .just(favCount.description)
// this part is based on the searching result -> failure
searchTextObservable.subscribe(onNext: { text in
if text.isEmpty {
let initialValue = self.shared.list
self.shared.listItems.accept(initialValue)
self.dispItems = self.shared.listItems
}else{
let filteredItems: [Item] = self.shared.list[0].list.filter {
$0.name.contains(text)
}
let filteredList = [SectionModel(list: filteredItems)]
self.shared.listItems.accept(filteredList)
self.dispItems = self.shared.listItems
itemCount = filteredItems.count
self.itemCountObservable = .just(itemCount.description + " items")
favCount = self.shared.calcFavCount(array: filteredItems)
self.favCountObservable = .just(favCount.description)
print("\(itemCount) items") // the ideal number is in but not shown in the view
}
})
.disposed(by: disposeBag)
}
}
I removed unnecessary code but I mostly pasted a whole code for your understanding.
Hope you could help me.
Thank you.
I solved this issue anyway; the value was reflected.
the issue was that itemCountObservable
was declared as observable
and .just
was used.
How .just
works is to throw onNext
once and it is completed, which means the change I did in searchTextObservable.subscribe(onNext~
is unacceptable.
So I shifted the itemCountObservable: Observable<String>
to BehaviorRelay<String>
that only onNext
is thrown and not completed, then it works.
My understanding of this issue is that itemCountObservable: Observable<String>
stopped throwing a value due to .just
as I've written above.
Am I correct??
If you're familiar with the difference between Observable
and BehaviorRelay
, it would be appreciated if you could tell me.
Thanks.