This is my first time using rxSwift
I've been trying to use rxswift to observe a data collection and update the view according but i'm doing something wrong
I made a timer to add an item to the data collection, which starts with 1 item by default, but the collection view doesn't seem to update.
import Foundation
import UIKit
import MaterialComponents
import RxSwift
import RxCocoa
class MainFeedViewController : BaseViewController
{
var loveData: [FeedDataModel] = []
var collectionData : Observable<[FeedDataModel]>? = nil
var collectionView : UICollectionView? = nil
var cellId = "FeedCollectionViewCell"
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
loadDummyData()
initCollectionView()
self.view.addConstraint(NSLayoutConstraint(item: self.view, attribute: .height, relatedBy: .equal, toItem: collectionView, attribute: .height, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.view, attribute: .width, relatedBy: .equal, toItem: collectionView, attribute: .width, multiplier: 1, constant: 0))
}
private func initCollectionView()
{
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
collectionView!.register(UINib(nibName: cellId, bundle: nil), forCellWithReuseIdentifier: cellId)
collectionView!.delegate = self
//collectionView!.dataSource = self
collectionView!.backgroundColor = UIColor.groupTableViewBackground
collectionView?.translatesAutoresizingMaskIntoConstraints = false
collectionData!.bind(to: collectionView!.rx.items(cellIdentifier: cellId))
{
_, data, cell in
let a = cell as! FeedCollectionViewCell
a.imgMAIN.downloaded(link: (data.image?.absoluteString)! )
a.lblTitle.text = data.title
}.disposed(by: disposeBag)
collectionView?.rx.modelSelected(FeedDataModel.self).subscribe {
next in
}.disposed(by: disposeBag)
self.view.addSubview(collectionView!)
}
override func viewDidAppear(_ animated: Bool)
{
// DispatchQueue.main.async {
// self.collectionView?.reloadData()
// }
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(addObj), userInfo: nil, repeats: true)
}
private func loadDummyData()
{
addObj()
collectionData = Observable.just(loveData)
}
@objc func addObj()
{
let obj1 = FeedDataModel.init(image: "https://via.placeholder.com/150",title: "Placeholder")
print("adding object")
loveData.append(obj1)
}
}
can you point me in the right direction?
thank you
Okay...
The constructor Observable.just(loveData)
will create an Observable that emits the array once and then completes. Ultimately, what you will want to do is itemize all the inputs that would cause the collection view to change and use them as inputs into the collectionData
Observable.
But for now, to get your feet wet, you can just use a Subject:
class MainFeedViewController : BaseViewController
{
var loveData: [FeedDataModel] = []
let collectionData = PublishSubject<[FeedDataModel]>()
// ...
collectionData.asObservable().bind(to: collectionView!.rx.items(cellIdentifier: cellId))
// ...
private func loadDummyData()
{
addObj()
collectionData.onNext(loveData)
}
@objc func addObj()
{
let obj1 = FeedDataModel.init(image: "https://via.placeholder.com/150",title: "Placeholder")
print("adding object")
loveData.append(obj1)
collectionData.onNext(loveData)
}
The PublishSubject
class is the most basic Subject type. It acts as both an Observable and an Observer. So you can push values into it with onNext()
and when you do, it will emit those values to any observer that subscribes or binds to it. Since your collection view is bound to it, that means the collection view will receive the events as they are emitted.
But this is only the beginning! Using a Subject like this is only a way to convert imperative code into the declarative style that Rx pushes you toward.
In order to take full advantage of the system, you will have to think a bit differently. Instead of thinking, "When the user does X, the app should do Y." you will have to organize your thoughts more along the lines of, "This output changes whenever these things happen."
Maybe that's as simple as, "The collection view should update from a network request whenever the user taps the refresh button. If the request fails, then don't update the list." Which would look something like:
refreshButton.rx.tap
.flatMap {
getRequestResponse()
.catchError { _ in Observable.empty() }
}
.bind(to: collectionView!.rx.items(cellIdentifier: cellId, cellType: FeedCollectionViewCell.self)) { _, data, cell in
cell.imgMAIN.downloaded(link: (data.image?.absoluteString)! )
cell.lblTitle.text = data.title
}
.disposed(by: disposeBag)
Or what your code seemed to want, "The collection view should start with a single entry and receive an additional entry every 5 seconds." Which would look something like:
Observable<Int>.interval(5, scheduler: MainScheduler.instance)
.scan(into: [FeedDataModel.init(image: "https://via.placeholder.com/150",title: "Placeholder")]) { current, _ in
current.append(FeedDataModel.init(image: "https://via.placeholder.com/150",title: "Placeholder"))
}
.bind(to: collectionView!.rx.items(cellIdentifier: cellId, cellType: FeedCollectionViewCell.self)) { _, data, cell in
cell.imgMAIN.downloaded(link: (data.image?.absoluteString)! )
cell.lblTitle.text = data.title
}
.disposed(by: disposeBag)